Java Logging API for Detailed (and multiline) Logs

2019-08-24 05:54发布

问题:

I'd like find an API that will allow me to provide a specific canonical set of information in all of my critical log points of my application. More specifically, it would be a multi-line message with the following information (in addition to the basics):

  1. Logging Category
  2. Short Description Title
  3. Description
  4. Response Action (damage control) my application will take
  5. Details (exception information, etc.)

With a single multi-line log looking, for example, like so:

2017-11-10 14:26:59,156 [main] WARN o.s.t.c.s.ExampleClass:
    Caption: Unconformable data
    Description: The data provided from the X datasource in order to perform Y operation could not be translated
    Response Action: Application will discard unusable data into this component's DLQ
    Details: The data string "x" was not of expected Integer type
        <Some stacktrace>....

This is a verbose statement that would be very informative about exactly what occurred, where it occurred, and what the application is doing in response to the event of the exception.

The closest I could find was the JBoss logging API and an example of some code I found in the ActiveMQ Artemis source. The message format declaration can be defined in a single file like so:

   @LogMessage(level = Logger.Level.WARN)
   @Message(id = 202008, value = "Failed to check Address list {0}.",
      format = Message.Format.MESSAGE_FORMAT)
   void failedToParseAddressList(@Cause Exception e, String addressList);

And one would log a line with this message in their code by writing:

ActiveMQUtilLogger.LOGGER.failedToParseAddressList(e, addressList);

That's the closest I could find to what I was looking for. Very cool. However, I'm not using JBoss (and also don't want to lock into that API).

I can use LOG4J, which has a StructuredDataMessage and Structured Data Lookup which can be used in a Layout, and I'll by default end up using that; my code could delegate to some StructuredDataMessage factory to solve this. However, it's a little bit bulkier than using something like this JBoss API.

Does anyone have any suggestions for this problem -- whether it's another API, a code patterns, or a nifty trick?

回答1:

You haven't stated any specific reasons why you wouldn't use log4j2 so I'd suggest going that route. As you point out it provides the StructuredDataMessage which you can use to fit your needs. Although in this case I would suggest using MapMessage since your example didn't include things like id and type which are built into the StructuredDataMessage class.

Here's some quick sample code:

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

public class MapMessageExample {

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

    public static void main(String[] args){
        log.info(buildMsg("My title", "This is the description", 
                "No response needed", "Some details here"));        
    }

    // This could be moved into a factory class
    public static MapMessage buildMsg(String title, String description, 
            String responseAction, String details)
    {
        MapMessage mapMsg = new MapMessage();
        mapMsg.put("title", title);
        mapMsg.put("desc", description);
        mapMsg.put("response", responseAction);
        mapMsg.put("details", details);
        return mapMsg;
    }
}

and a log4j2.xml configuration file to go along with it:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%M] %-5level %logger{36}%n\tCaption: ${map:title}%n\tDescription: ${map:desc}%n\tResponse Action: ${map:response}%n\tDetails: ${map:details}%n" />
        </Console>
    </Appenders>

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

Here is some sample output:

00:40:45.810 [main] INFO  example.MapMessageExample
    Caption: My title
    Description: This is the description
    Response Action: No response needed
    Details: Some details here

Some final thoughts:

my code could delegate to some StructuredDataMessage factory to solve this

I agree, the factory pattern would be a good choice here. As I commented in the sample code you could move the buildMsg method to a factory class.

However, it's a little bit bulkier than using something like this JBoss API.

I don't really see how it's any bulkier. If you find that most of the time only one or two of the items in the MapMessage are changing you could easily write very specific methods similar to the API you mentioned. To add to the above example:

public static MapMessage reallySpecificLogMessage(String details){
    return buildMsg("My specific message title", "My specific description",
            "My specific response action", details);
}

You would probably want to assign constants to the strings being used in this method, but as I said this is just a quick example.