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

时间:2011-10-13 15:11:57

标签: java logging log4j

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

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

是否还有其他人需要在自定义附加程序中修改已记录的消息?

4 个答案:

答案 0 :(得分:13)

我不完全确定为什么创建一个新的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());
    }

}

和这个配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="MyDailyRollingFileAppender" class="test.logging.MyDailyRollingFileAppender">
        <param name="Append" value="true"/>
        <param name="datePattern" value="'.'yyyy-MM-dd"/>
        <param name="File" value="mine.log"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p (%x) [%t] %c{1} - %m%n" />
        </layout>
    </appender>

    <root>
        <priority value="debug"/>
        <appender-ref ref="MyDailyRollingFileAppender"/>
    </root>

</log4j:configuration>

我得到以下输出:

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<Appender> appenders = new ArrayList<Appender>();

    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<Appender> i = appenders.iterator(); i.hasNext(); ) {
                if (i.next().equals(appender)) {
                    i.remove();
                }
            }
        }
    }

    public void removeAppender(String name) {
        synchronized (appenders) {
            for (Iterator<Appender> 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);
            }
        }
    }

}

您可以配置如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="StdOut" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p (%x) [%t] %c{1} - %m%n" />
        </layout>
    </appender>

    <appender name="FileAppender" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="Append" value="true"/>
        <param name="datePattern" value="'.'yyyy-MM-dd"/>
        <param name="File" value="mine.log"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p (%x) [%t] %c{1} - %m%n" />
        </layout>
    </appender>

    <appender name="AppenderWrapper" class="test.logging.MyAppenderWrapper">
        <appender-ref ref="StdOut"/>
        <appender-ref ref="FileAppender"/>
    </appender>

    <root>
        <priority value="debug"/>
        <appender-ref ref="AppenderWrapper"/>
    </root>

</log4j:configuration>

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

答案 1 :(得分:5)

这就是我使用旧版本的log4j

后我的表现

我没有自定义appender,而是自定义布局,并在我需要此功能的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=a.b.c.package.LogValidatorLayout

答案 2 :(得分:4)

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

答案 3 :(得分:0)

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

我浏览了代码,在版本1.2.15之前,从现有版本创建新的LoggingEvent是不可能的,如果不挖掘一半的库。从1.2.15开始就没问题了。