LOG4J:使用自定义追加器修改记录的消息

2022-09-03 01:30:32

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

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

是否有其他人需要修改自定义追加器中记录的消息?


答案 1

我不完全确定为什么创造一个新的如此繁重。这似乎对我有用: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)

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

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>

这样,消息仍会发送到原始追加器,但会与修改后的消息一起发送。


答案 2

这就是我所做的,因为我正在使用旧版本的log4j

我没有自定义追加器,而是自定义了布局,并在我需要此功能的任何追加器中引用了该布局

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

推荐