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):
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?
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.