LOG4J:使用自定义appender修改记录的消息

出于安全原因,我需要查看我的应用程序中的每条记录消息,并可能在它转到日志文件之前对其进行修改。 我想我可以写一个自定义appender(扩展DailyRollingFileAppender)并覆盖subAppend(LoggingEvent事件)。 问题是,LoggingEvent中没有消息文本的setter,消息是私有属性。 我可以使用修改后的消息创建一个新的LoggingEvent,但API不能轻松复制原始LoggingEvent的其余部分。 这一切似乎都旨在阻止在自定义appender中插入消息。

我能看到的唯一另一个选项是修改数百个日志语句来调用一个新的全局方法,该方法可以先修改文本然后进行Log4J调用。 我宁愿不!

有没有其他人需要修改自定义appender中记录的消息?

我不完全确定为什么创建一个新的LoggingEvent是如此繁重。 这似乎对我有用:

 package test.logging; import org.apache.log4j.DailyRollingFileAppender; import org.apache.log4j.spi.LoggingEvent; public class MyDailyRollingFileAppender extends DailyRollingFileAppender { @Override protected void subAppend(LoggingEvent event) { String modifiedMessage = String.format("**** Message modified by MyDailyRollingFileAppender ****\n\n%s\n\n**** Finished modified message ****", event.getMessage()); LoggingEvent modifiedEvent = new LoggingEvent(event.getFQNOfLoggerClass(), event.getLogger(), event.getTimeStamp(), event.getLevel(), modifiedMessage, event.getThreadName(), event.getThrowableInformation(), event.getNDC(), event.getLocationInformation(), event.getProperties()); super.subAppend(modifiedEvent); } } 

通过此测试:

 package test; import org.apache.log4j.Logger; public class TestLogging { public static void main(String[] args) { Logger log = Logger.getLogger(TestLogging.class); log.info("I am testing my logging"); log.info("Here is an exception", new Exception()); } } 

而这个配置:

                 

我得到以下输出:

 2011-10-14 10:09:09,322 INFO () [main] TestLogging - **** Message modified by MyDailyRollingFileAppender **** I am testing my logging **** Finished modified message **** 2011-10-14 10:09:09,333 INFO () [main] TestLogging - **** Message modified by MyDailyRollingFileAppender **** Here is an exception **** Finished modified message **** java.lang.Exception at test.TestLogging.main(TestLogging.java:10) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 

虽然我做了类似的事情,但我采用了略微不同的方法。 我没有编写我想使用的每种类型的Appender子类,而是创建了一个包装其他Appender对象的Appender ,并在发送到包装的Appender之前修改了消息。 像这样的东西:

 package test.logging; import org.apache.log4j.Appender; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.AppenderAttachable; import org.apache.log4j.spi.LoggingEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; public class MyAppenderWrapper extends AppenderSkeleton implements AppenderAttachable { private final List appenders = new ArrayList(); public void close() { synchronized (appenders) { for (Appender appender : appenders) { appender.close(); } } } public boolean requiresLayout() { return false; } public void addAppender(Appender appender) { synchronized (appenders) { appenders.add(appender); } } public Enumeration getAllAppenders() { return Collections.enumeration(appenders); } public Appender getAppender(String name) { synchronized (appenders) { for (Appender appender : appenders) { if (appender.getName().equals(name)) { return appender; } } } return null; } public boolean isAttached(Appender appender) { synchronized (appenders) { for (Appender wrapped : appenders) { if (wrapped.equals(appender)) { return true; } } return false; } } public void removeAllAppenders() { synchronized (appenders) { appenders.clear(); } } public void removeAppender(Appender appender) { synchronized (appenders) { for (Iterator i = appenders.iterator(); i.hasNext(); ) { if (i.next().equals(appender)) { i.remove(); } } } } public void removeAppender(String name) { synchronized (appenders) { for (Iterator i = appenders.iterator(); i.hasNext(); ) { if (i.next().getName().equals(name)) { i.remove(); } } } } @Override protected void append(LoggingEvent event) { String modifiedMessage = String.format("**** Message modified by MyAppenderWrapper ****\n\n%s\n\n**** Finished modified message ****", event.getMessage()); LoggingEvent modifiedEvent = new LoggingEvent(event.getFQNOfLoggerClass(), event.getLogger(), event.getTimeStamp(), event.getLevel(), modifiedMessage, event.getThreadName(), event.getThrowableInformation(), event.getNDC(), event.getLocationInformation(), event.getProperties()); synchronized (appenders) { for (Appender appender : appenders) { appender.doAppend(modifiedEvent); } } } } 

您可以配置如下:

                          

这样,消息仍然会被发送到原始的appender但是带有修改后的消息。

另一个选项是自定义appender使用的布局。 由于layout负责将日志事件序列化为字符串,因此我会检查抽象布局是否比更改appender和事件要复杂得多。 只是一个想法……

多数民众赞成我自从使用旧版本的log4j以来的表现

我没有自定义appender,而是定制了布局,并在我需要此function的Appender中引用了该布局

 public class LogValidatorLayout extends PatternLayout { public LogValidatorLayout() { super(); } public LogValidatorLayout(String pattern) { super(pattern); } @Override public String format(LoggingEvent event) { // only process String type messages if (event.getMessage() != null && event.getMessage() instanceof String) { String message = event.getMessage().toString(); message = StringUtils.trim("Some custom text --->>"+message); // earlier versions of log4j don't provide any way to update messages, // so use reflections to do this try { Field field = LoggingEvent.class.getDeclaredField("message"); field.setAccessible(true); field.set(event, message); } catch (Exception e) { // Dont log it as it will lead to infinite loop. Simply print the trace e.printStackTrace(); } } return super.format(event); } } 

在log4j.properties或xml中,注册此布局

 log4j.appender.STDOUT.layout=abcpackage.LogValidatorLayout 

我将为Logger创建一个委托类,并将所有从org.apache.log4j.Logger导入的更改为your.own.Logger 。 这是一个简单而自动的过程 – 因为您考虑在代码中更改记录器调用,我认为您可以完全访问源代码。 在委托中,您将完全调用log4j Logger中的方法,但首先将字符串插入到您心中的内容中。

我浏览了代码,在版本1.2.15之前,从现有版本创建一个新的LoggingEvent是不可能的,而不需要挖掘一半的库。 从1.2.15起,它没有任何问题。