0.背景
对于安全要求严格的系统,转储的压缩日志文件,要求文件权限为只读,而找遍了log4j2的资料,都没发现有这样配置,log4j2框架对于日志的默认权限都是640,无论如何也为日志文件保留可写权限,貌似无法单独为压缩后的日志配置权限。不过log4j2是支持自定义日志滚动策略的,干脆直接从源代码研究如何扩展log4j2的日志插件。
1.分析解决思路
一般情况我们选择rolling.strategy.type的时候选择的都是DefaultRolloverStrategy,整个滚动策略的实现还是挺复杂的,我只想以最小的改动实现这个功能。DefaultRolloverStrategy核心逻辑在rollover中,该方法是在构造一个RolloverDescription的实例,观察构造RolloverDescription实例需要的参数
最后一个参数正是压缩文件的处理类,前三个参数不用管。当我们配置的压缩日志格式为gz时,第四个参数的实现类是GzCompressAction,其核心方法execute就是在将.log文件压缩为.gz文件。整个日志的滚动策略我都不想修改,只想在GzCompressAction的execute方法之后插入一段修改压缩文件权限的方法。
2、扩展GzCompressAction
GzCompressAction由final修饰无法继承,最简单的办法就是继承它的父类,并将GzCompressAction作为成员。实现execute方法时不用关系原GzCompressAction具体的逻辑,直接调用成员的execute方法,然后调用我提供的修改文件权限的方法。这样便实现我刚说的不改动原有逻辑,只在压缩后修改文件权限。
public class CustomGzCompressAction extends AbstractAction {
private final GzCompressAction gzCompressAction;
public CustomGzCompressAction(GzCompressAction gzCompressAction) {
this.gzCompressAction = gzCompressAction;
}
@Override
public boolean execute() throws IOException {
boolean result = gzCompressAction.execute();
File destination = gzCompressAction.getDestination();
if (destination != null) {
FilePermissionUtil.chmodLogFile(destination);
}
return result;
}
}
FilePermissionUtil
public class FilePermissionUtil {
private static final Logger LOG = LoggerFactory.getLogger(FilePermissionUtil.class);
/**
* 修改文件权限为只读
*
* @param fileName 文件路径
*/
public static void chmodLogFile(File fileName) {
try {
LOG.info("change log file permission");
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, GROUP_READ);
if (Files.readAttributes(fileName.toPath(), PosixFileAttributes.class) != null) {
PosixFileAttributes attr = Files.readAttributes(fileName.toPath(), PosixFileAttributes.class);
attr.permissions().clear();
}
Files.setPosixFilePermissions(fileName.toPath(), perms);
} catch (FileNotFoundException e) {
LOG.error("file nod found ,fileName={}", fileName);
} catch (IOException e) {
LOG.error("change file permission error", e);
}
}
}
3、扩展DefaultRolloverStrategy
由于log4j2是以插件的形式加载滚动策略的,因此我们要像DefaultRolloverStrategy一样将自定义的滚动策略定义为一个插件。这里直接将DefaultRolloverStrategy的代码拷贝过来,利用DefaultRolloverStrategy的实例构造CustomRolloverStrategy。
@Plugin(name = "CustomRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true)
public class CustomRolloverStrategy extends DefaultRolloverStrategy {
private static final Logger LOG = LoggerFactory.getLogger(CustomRolloverStrategy.class);
protected CustomRolloverStrategy(int minIndex, int maxIndex,
boolean useMax, int compressionLevel,
StrSubstitutor strSubstitutor, Action[] customActions,
boolean stopCustomActionsOnError,
String tempCompressedFilePatternString) {
super(minIndex, maxIndex, useMax,
compressionLevel, strSubstitutor,
customActions, stopCustomActionsOnError,
tempCompressedFilePatternString);
}
protected CustomRolloverStrategy(
int minIndex, int maxIndex, boolean useMax,
int compressionLevel, StrSubstitutor strSubstitutor,
Action[] customActions, boolean stopCustomActionsOnError) {
super(minIndex, maxIndex, useMax,
compressionLevel, strSubstitutor,
customActions, stopCustomActionsOnError);
}
/**
* Strateg的构造factory
*
* @param max 最大文件个数
* @param min 最小文件个数
* @param fileIndex 最大文件索引
* @param compressionLevelStr 压缩等级
* @param customActions 自定义Action
* @param stopCustomActionsOnError 自定义Action发生错误时是否停止
* @param config 其他配置
* @return CustomRolloverStrategy
*/
@PluginFactory
public static CustomRolloverStrategy createStrategy(
// @formatter:off
@PluginAttribute("max") final String max,
@PluginAttribute("min") final String min,
@PluginAttribute("fileIndex") final String fileIndex,
@PluginAttribute("compressionLevel") final String compressionLevelStr,
@PluginElement("Actions") final Action[] customActions,
@PluginAttribute(value = "stopCustomActionsOnError",
defaultBoolean = true)
final boolean stopCustomActionsOnError,
@PluginConfiguration final Configuration config) {
DefaultRolloverStrategy defaultRolloverStrategy = DefaultRolloverStrategy.newBuilder()
.withMin(min)
.withMax(max)
.withFileIndex(fileIndex)
.withCompressionLevelStr(compressionLevelStr)
.withCustomActions(customActions)
.withStopCustomActionsOnError(stopCustomActionsOnError)
.withConfig(config)
.build();
CustomRolloverStrategy customRolloverStrategy
= new CustomRolloverStrategy(defaultRolloverStrategy.getMinIndex(),
defaultRolloverStrategy.getMaxIndex(), defaultRolloverStrategy.isUseMax(),
defaultRolloverStrategy.getCompressionLevel(),
defaultRolloverStrategy.getStrSubstitutor(),
customActions, defaultRolloverStrategy.isStopCustomActionsOnError());
return customRolloverStrategy;
}
/**
* 重写的DefaultRolloverStrategy的rollover
*
* @param manager manager
* @return RolloverDescription
* @throws SecurityException
*/
@Override
public RolloverDescription rollover(RollingFileManager manager) throws SecurityException {
RolloverDescription result = super.rollover(manager);
Action asynchronous = result.getAsynchronous();
if (asynchronous instanceof GzCompressAction) {
asynchronous = new CustomGzCompressAction((GzCompressAction) asynchronous);
}
return new RolloverDescriptionImpl(result.getActiveFileName(), false, result.getSynchronous(), asynchronous);
}
}
rollover的实现也是调用父类的rollover,然后将父类返回对象中的asynchronous替换为我们扩展的CustomGzCompressAction,其他参数不变。
4、配置
将log4j2.properties中的rolling.strategy.type全部替换为CustomRolloverStrategy,便实现我们要的修改压缩日志文件权限的功能。
5、效果
记录中的日志权限为6xx,压缩后的gz为440