18

I have those specific requirements :

  • Need to be able to log in FATAL level
  • Need to use SLF4J
  • Need to use Log4j2

Right now, here's my implementation:

final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
final Marker marker = MarkerFactory.getMarker("FATAL");
logger.error(marker, "!!! Fatal World !!!");

Here's my PatternLayout (in yaml):

PatternLayout:
  Pattern: "%d{ISO8601_BASIC} %-5level %marker [%t] %logger{3.} - %msg%n"

Here's my log output :

20150506T155705,158 ERROR FATAL [main] - !!! Fatal World !!!

Do you have any idea about how to efficiently to remove the "ERROR" from the log output?

Thank you very much

Daniel Marcotte
  • 1,270
  • 3
  • 16
  • 27

6 Answers6

13

Marker is not really what you want here. Marker is for "enriching" log messages, making them more easily searchable. You are trying to change the log level/priority, which is a little different.

You're using logger.error() which will log the message as an ERROR level.

If there is no FATAL level pre-defined (usually there is, such as logger.fatal()), then use the generic logger.log() which allows you to specify the log level.

logger.fatal(yourMessage);

OR

logger.log(priorityLevel, yourMessage);

UPDATE:

From the SLF4J website:

The Marker interface, part of the org.slf4j package, renders the FATAL level largely redundant. If a given error requires attention beyond that allocated for ordinary errors, simply mark the logging statement with a specially designated marker which can be named "FATAL" or any other name to your liking.

http://www.slf4j.org/faq.html#fatal

So, with SLF4J, it is not possible to have a FATAL log level. I strongly disagree with the rationale behind this decision, but it is what it is.

SnakeDoc
  • 13,611
  • 17
  • 65
  • 97
  • SLF4J doesn't support any of those solution. I know that Log4j2 supports them but I need to keep working with the facade for legacy code. – Daniel Marcotte May 06 '15 at 20:15
  • 1
    @DanielMarcotte Unfortunately then there appears to be no way to do what you ask. SLF4J seems to think the `FATAL` level is "redundant", although I strongly disagree. http://www.slf4j.org/faq.html#fatal – SnakeDoc May 06 '15 at 20:20
8

Here's the closest working solution I came with some colleagues :

  1. Create a Fatal Marker class using SLF4J Markers.
  2. Using a RoutingAppender, use the Marker as the routing pattern: "$${marker:}"
  3. Configure a Fatal-specific appender that has its own PatternLayout that doesn't include the LogLevel but a hardcoded FATAL level.

Here's the Java sample :

Marker fatal = MarkerFactory.getMarker("FATAL");
// Usage example
final Logger logger = LoggerFactory.getLogger(FatalLogger.class);
logger.log(fatal, "this is a fatal message");

// Log sample : 
20150514T115144,279  FATAL [main] FatalLogger - this is a fatal message

Here's the YAML sample :

Configuration:
  status: debug

  Appenders:
    RandomAccessFile:
      - name: APPLICATION_APPENDER
        fileName: logs/application.log
        PatternLayout:
          Pattern: "%d{ISO8601_BASIC} %-5level %msg%n"
      - name: FATAL_APPENDER
        fileName: logs/application.log
        PatternLayout:
          Pattern: "%d{ISO8601_BASIC} FATAL %msg%n"

    Routing:
      name: ROUTING_APPENDER
      Routes:
        pattern: "$${marker:}"
        Route:
        - key: FATAL
          ref: FATAL_APPENDER
        - ref: APPLICATION_APPENDER #DefaultRoute

  Loggers:
    Root:
      level: trace
      AppenderRef:
        - ref: ROUTING_APPENDER
Daniel Marcotte
  • 1,270
  • 3
  • 16
  • 27
  • Note that if you are going to create a wrapper (FatalLogger) anyway, you may as well wrap a Log4j2 Logger instead of an SLF4J logger. That way you get the fatal() method without having to jump through the MARKER hoops in your code, and you only need one file appender in your configuration. Log4j2 contains a tool to generate custom logger wrappers that may be useful: http://logging.apache.org/log4j/2.x/manual/customloglevels.html#CustomLoggers (This tool is meant for custom log levels, but you can just generate a wrapper without adding custom log levels.) – Remko Popma May 15 '15 at 12:32
  • Thanks. that's a good idea too! At the moment I'm trying to write a custom RewritePolicy (http://stackoverflow.com/questions/30247323/how-to-create-custom-rewritepolicy-in-log4j2) to append the SLF4J Marker to the ThreadContextMap. With that, I would be able to use the RoutingAppender with $${ctx:_myMarker} and only use Markers from SLF4J. I would be totally SLF4J compliant with that solution. – Daniel Marcotte May 15 '15 at 13:32
  • I just thought it seems like you are doing a tremendous amount of work... 3rd party tools that use SLF4J will not have FATAL level log messages, so all the work you are doing to support FATAL is for logs generated by your own application. Since you are hiding the log4j2 dependency inside your wrapper class anyway, is there still any benefit to being SLF4J compliant? – Remko Popma May 15 '15 at 14:09
  • Well there are more than just "Fatal" in my requirements. The optimal solution would be to be able to Route messages based on the SLF4J Marker, which seems impossible at the moment in Log4j2 (http://stackoverflow.com/questions/30111754/log4j2-is-there-a-way-to-route-logs-based-on-marker-with-the-routingappender). Using SLF4J enables us to stick with known open source standards, no brainer integration and no implementation understanding from the other Developer point of view. – Daniel Marcotte May 15 '15 at 14:20
  • Sorry, perhaps I am missing something, but once you create things like custom RewritePolicies, you are firmly binding yourself to the log4j2 implementation... I would recommend that instead you at least only depend on the log4j2 API. You can accomplish this by simply using the log4j2 API inside your FatalLogger wrapper. This eliminates the need for custom RewritePolicies, eliminates the need for multiple appenders pointing to the same file, and thus also eliminates the need for the RoutingAppender and Markers. Sounds like an ideal solution to me (unless I am missing something). – Remko Popma May 15 '15 at 14:48
  • Thank you very much for your input. I'll follow your advice. – Daniel Marcotte May 15 '15 at 14:51
  • Your solution above defines two appenders when they only difference between them is the definition of the pattern in the PatternLayout. For that exact situation you should only have a single appender and use a PatternSelector instead. – rgoers Feb 26 '17 at 03:19
3

I know the question is for log4j. I found this page when looking in relation to logback. Here's what sl4j recommends: https://www.slf4j.org/faq.html#fatal.

thebiggestlebowski
  • 2,610
  • 1
  • 33
  • 30
2

Here is what I did for log4j which I also recommend for log4j2....

Write your own custom slf4j static binder aka bridge. This requires a little work but is well worth it for a variety of complicated reasons 1 that I will blog about one day.

Here is what you do.

  1. You copy this code here: https://github.com/apache/logging-log4j2/tree/master/log4j-slf4j-impl
  2. You then want to edit the Log4jLogger class and change the marker methods (trace,error,warn,info, etc) to dispatch appropriately. ie if (marker.contains("FATAL")) fatal(....);
  3. Exclude the original log4j-slf4j-impl from your project and use your new code in place.

<soap-box-rant>

I honestly think slf4j is severely flawed because

  • it makes it very difficult/impossible to override hardcore static initialization along with also not providing logger.fatal(...).
  • Markers are not needed and are inherently complex:
    • Very very very few projects use markers. I actually looked/grepped open source projects and marker usage is close to zero low.
    • The ones that use marker are because fatal is missing. It is not 80/20. Markers are for the 1% and fatal is for the 99%.
    • Many developers think that somehow using the marker "fatal" will map it to fatal.
    • Few know what a detached marker is including myself.
    • There is overlap with what the MDC context provides. Given event dimension oriented databases (elasticsearch, druid, etc) the MDC context is superior (name/value pair).

</soap-box-rant>

1 one of them being able to actually be part of the boot process of your logging framework opposed to hard to determine almost arbitrary static initialization

Community
  • 1
  • 1
Adam Gent
  • 47,843
  • 23
  • 153
  • 203
1

The only solution I've found so far is to use have 5 markers :

final Marker traceMarker = MarkerFactory.getMarker("TRACE");
final Marker debugMarker = MarkerFactory.getMarker("DEBUG");
final Marker infoMarker = MarkerFactory.getMarker("INFO");
final Marker warnMarker = MarkerFactory.getMarker("WARN");
final Marker errorMarker = MarkerFactory.getMarker("ERROR");
final Marker fatalMarker = MarkerFactory.getMarker("FATAL");

And log passing the marker everytime :

logger.info(infoMarker, "!!! INFO World !!!");
logger.error(errorMarker, "!!! ERROR World !!!");
logger.error(fatalMarker, "!!! FATAL World !!!");

And modify the PatternLayout to totally remove the LogLevel and always log the Marker, such as :

PatternLayout:
  Pattern: "%d{ISO8601_BASIC} %marker [%t] %logger{3.} - %msg%n"

I kind of think this solution is a hack... It would also remove the log level of any external library using the LogLevel the right way.

Summary : This solution isn't a good solution.

UPDATE : I tried another solution, writing a RewritePolicy :

public class FatalRewritePolicy implements RewritePolicy {

    public static final String FATAL = "FATAL";

    @Override
    public LogEvent rewrite(final LogEvent logEvent) {

        final Marker marker = logEvent.getMarker();
        if (marker == null)
            return logEvent;

        // Log Level is final in the LogEvent, there's no way we can modify it.
        Level level = logEvent.getLevel();

        return null;
    }
}

There seems to be no way to change the LogLevel of a LogEvent in Log4j2 (which make sense).

Summary : Still no solution.

Daniel Marcotte
  • 1,270
  • 3
  • 16
  • 27
  • In the rewrite policy you can create a new LogEvent and return that new instance (that is how the RewritePolicy is intended to be used). So in your new LogEvent instance you can set the FATAL level. An example is in my answer to this question: http://stackoverflow.com/questions/30247323/how-to-create-custom-rewritepolicy-in-log4j2 – Remko Popma May 15 '15 at 14:40
1

You could add "FATAL" at the beginning of your message. For example:

LOGGER.error("FATAL: database connection lost.");

You do lose some things, like filtering based on level, but this may be ok for many people, especially since you're unlikely to filter out FATAL statements (debug and trace, sure).

thebiggestlebowski
  • 2,610
  • 1
  • 33
  • 30