Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
107 views
in Technique[技术] by (71.8m points)

log4j2 - How do I write an appender that only handles exceptions and still have all other logs logged normally through a root ConsoleAppender

I have a situation where when log.error("message", exception); is called, I want some logic to happen around sending the exception to an external tool, while still maintaining the regular logging for the line to the root appender.

As an example, here would be some code and the expected outcome:

try {
   ...
} catch (Exception ex) {
   LOG.info("abcd");
   LOG.error("failed to XYZ", ex);
}

Rough Outcome:

2019-03-05 13:00:20 INFO  Main:75 - abcd
2019-03-05 13:00:20 ERROR  Main:76 - failed to XYZ - 
Exception: exception message
  stacktrace
  stacktrace
  ...

While that is logged, I also want the exception sent through another code path.

How can I do this? I'm a bit stuck, does anyone know any good guides for this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I don't think you really want an Appender here. It would be simpler to write a Filter instead. For reference you can find information regarding creating extensions for log4j2 on the Extending Log4j2 page of the manual

In the example below I created a simple filter that matches when the log event has a Throwable associated with it and mismatches when there is no Throwable (i.e. the Throwable is null or the log was generated from a method call that did not include a Throwable parameter).

In the example I send all matching log events to a simple file appender to illustrate that in fact it does capture only events with a Throwable. You could modify this filter to do whatever you need. You could change it to always be NEUTRAL to every event, but when a non-null Throwable is found it then triggers some special logic to send that Throwable to your external tool. Basically you would be using the filter as more of an interceptor. I will describe that change at the end.

First, here's some basic Java code to generate some logging including a log event that has a Throwable.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class SomeClass {

    private static final Logger log = LogManager.getLogger();   

    public static void main(String[] args){

        if(log.isDebugEnabled())
            log.debug("This is some debug!");
        log.info("Here's some info!");
        log.error("Some error happened!");

        try{
            specialLogic();
        }catch(RuntimeException e){
            log.error("Woops, an exception was detected.", e);
        }
    }

    public static void specialLogic(){
        throw new RuntimeException("Hey an exception happened! Oh no!");
    }
}

Next, here's the class I call ThrowableFilter:

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.filter.AbstractFilter;
import org.apache.logging.log4j.message.Message;

@Plugin(name = "ThrowableFilter", category = "Core", elementType = "filter", printObject = true)
public final class ThrowableFilter extends AbstractFilter {


    private ThrowableFilter(Result onMatch, Result onMismatch) {
        super(onMatch, onMismatch);
    }

    public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) {
        return onMismatch;
    }

    public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
        return filter(t);
    }

    public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
        return filter(t);
    }

    @Override
    public Result filter(LogEvent event) {
        return filter(event.getThrown());
    }

    private Result filter(Throwable t) {
        return t != null ? onMatch : onMismatch;
    }

    /**
     * Create a ThrowableFilter.
     * @param match The action to take on a match.
     * @param mismatch The action to take on a mismatch.
     * @return The created ThrowableFilter.
     */
    @PluginFactory
    public static ThrowableFilter createFilter(@PluginAttribute(value = "onMatch", defaultString = "NEUTRAL") Result onMatch,
                                               @PluginAttribute(value = "onMismatch", defaultString = "DENY") Result onMismatch) {
        return new ThrowableFilter(onMatch, onMismatch);
    }
}

Finally, here is the log4j2.xml configuration file I used to test this:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <File name="ExceptionFile" fileName="logs/exception.log" immediateFlush="true"
            append="true">
            <ThrowableFilter onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout
                pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </File>
    </Appenders>

    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console" />
            <AppenderRef ref="ExceptionFile" />
        </Root>
    </Loggers>
</Configuration>

Running the logic in SomeClass produces the following output:

On the console:

23:23:25.931 [main] DEBUG example.SomeClass - This is some debug!
23:23:25.946 [main] INFO  example.SomeClass - Here's some info!
23:23:25.946 [main] ERROR example.SomeClass - Some error happened!
23:23:25.946 [main] ERROR example.SomeClass - Woops, an exception was detected.
java.lang.RuntimeException: Hey an exception happened! Oh no!
    at example.SomeClass.specialLogic(SomeClass.java:25) ~[classes/:?]
    at example.SomeClass.main(SomeClass.java:18) [classes/:?]

In the logs/exception.log file:

2019-03-06 23:23:25.946 [main] ERROR example.SomeClass - Woops, an exception was detected.
java.lang.RuntimeException: Hey an exception happened! Oh no!
    at example.SomeClass.specialLogic(SomeClass.java:25) ~[classes/:?]
    at example.SomeClass.main(SomeClass.java:18) [classes/:?]

Now to change the filter to act more as an interceptor you could alter the following methods:

//Remove parameters from constructor as they will not be used.
private ThrowableFilter() {
    super();
}
...

public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) {
    //Changed to always return NEUTRAL result
    return Result.NEUTRAL;
    //old logic: return onMismatch;
}

...

private Result filter(Throwable t) {
    //TODO: trigger the external tool here when t != null, pass t if needed.

    //Changed to always return NEUTRAL result
    return Result.NEUTRAL;
    //old logic: return t != null ? onMatch : onMismatch;
}

/**
 * Create a ThrowableFilter.
 * @return The created ThrowableFilter.
 */
@PluginFactory
public static ThrowableFilter createFilter() {
    return new ThrowableFilter();
}

Then in the configuration you would remove the parameters to the filter. It would now look like this:

<ThrowableFilter/>

Hope this helps you.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...