The Art of Logging: An Exploration with Apache Log4j 2 by Gary Gregory
Delve into the world of logging with Apache Log4j 2 through the insightful exploration presented by Gary Gregory, a Principal Software Engineer at Rocket Software. Discover the importance of logging, key concepts like logging architecture and APIs, and the significance of modern logging frameworks such as Apache Log4j 2 and SLF4J with Logback.
Download Presentation
Please find below an Image/Link to download the presentation.
The content on the website is provided AS IS for your information and personal use only. It may not be sold, licensed, or shared on other websites without obtaining consent from the author. Download presentation by click this link. If you encounter any issues during the download, it is possible that the publisher has removed the file from their server.
E N D
Presentation Transcript
The Art of Logging An exploration with Apache Log4j 2 Gary Gregory garydgregory@gmail.com
Gary Gregory Principal Software Engineer, Rocket Software C Smalltalk Java Apache: Commons, Log4j, HttpComponents, Xalan Books
Thank you Dealer.com for sponsoring this meeting and the Pumpkin Race!
Why log? Logs are like car insurance. Nobody wants to pay for it, but when something goes wrong everyone wants the best available Pablo Kraan
The Road to Nowhere (0) System.out.println("Starting " + new Date()); System.console().printf("Starting %s , new Date());
The Road to Nowhere (1) public void test() { println("Starting " + new Date()); } private void println(String message) { System.out.println(message); }
The Road to Nowhere (2) public static boolean DEBUG = true; public void test() { println("Starting " + new Date()); } private void println(String message) { if (DEBUG) { System.out.println(message); } }
The Road to Nowhere (3) public static boolean DEBUG = Boolean.getBoolean( EBUG"); public void test() { println("Starting " + new Date()); } private void println(String message) { if (DEBUG) { System.out.println(message); } }
The Road to Nowhere (4) Destinations Console out, err. Files, what format(s)? And so on Event levels Source location? Timestamp formats Thread info? Configuration On and on and on
Key Concepts: architecture Split Logging API and implementations Pluggable implementations Modern logging frameworks do this Apache Log4j 2 SLF4J + Logback Apache Commons Logging (API only) JUL Log4j 1 did not, all-in-one
Key concepts API Configuration Customization/Pluggability
Key concepts APIs LogManager Level Logger Marker More: Flow tracing with fish tagging Context stack and context map
LogManager Creates Loggers LogManager.getLogger( com.example.SomeClass ); LogManager.getLogger(SomeClass.class); LogManager.getLogger();
LogManager and Message Factories Message factories Login OK for user {} from terminal {} Login OK for user {0} from terminal {1} Login OK for user %s from terminal %s ETL processed %,d records
Message Factories org.apache.logging.log4j.LogManager.getLogger(Class|String|Object, MessageFactory) FormattedMessageFactory LocalizedMessageFactory MessageFormatMessageFactory ParameterizedMessageFactory ParameterizedNoReferenceMessageFactory SimpleMessageFactory StringFormatterMessageFactory
Levels How important is this event? Who needs to know about this event? Custom levels
Using Levels in applications INFO : User Alice logged in. WARN : User Bob entered an invalid password. ERROR: User Bob entered an invalid password three times, user locked out. DEBUG: Loading user Bob from database jdbc:... INFO : Emailed user Bob: three login attempts rejected, user locked out. ERROR: Email to Alice bounced back; subject: Your weekly summary.
Understanding Levels vs. Loggers Message: What it is I am telling my audience? Levels How important is this? Loggers Who s talking now?
Hierachical Loggers Loggers are named Names are hierarchical: a.b.c, a,b, a,
Hierachical loggers example Kitchen Kitchen.Refrigerator Kitchen.Oven Kitchen.Refrigerator.Door Kitchen.Oven.BottomOven Kitchen.Refrigerator.Filter Kitchen.Oven.BottomOven.Door Kitchen.Refrigerator.Light Kitchen.Oven.BottomOven.Timer Kitchen.Refrigerator.Freezer.Door Kitchen.Oven.BottomOven.Light Kitchen.Refrigerator.Freezer.Light Kitchen.Oven.TopOven.Door And so on Kitchen.Oven.TopOven.Timer Kitchen.Oven.TopOven.Light Kitchen.Dishwasher
Logger configuration Hierarchical names: com.example.app.foo Configure a logger or whole hierarchy: <Root level= warn"> <Logger name="javax.management" level="INFO" /> <Logger name= com.example.app" level="INFO" /> <Logger name= com.example.app.foo" level= DEBUG" /> <Logger name= com.example.app.bar" level= TRACE" />
Using a Logger logger.debug( User {} logged in , user); logger.info( User {} logged in , user);
Logger granularity One global logger The root logger Nah. One per class Use the FQCN One per instance? In specific cases Use the FQCN + instance info Watch for lots of short lived objects Examples
Clever logger names One per instance Must use the logger name to uniquely identify a logger com.example.server.socket.8080 com.example.rest.get.GetCustomer
Getting to advanced logging (0) logger.debug("There are " + getUserCount() + " users logged in now."); You are always building the full string before calling debug() You are always calling getUserCount() Both of the above happens even if debug logging is disabled!
Getting to advanced logging (1) Guard it: if (logger.isDebugEnabled()) { logger.debug("There are " + getUserCount() + " users logged in now."); }
Getting to advanced logging (3) Parameterize it: logger.debug("There are {} users logged in now.", getUserCount());
Getting to advanced logging (4) Use lambdas! logger.debug("There are {} users logged in now.", () -> getUserCount());
Getting to advanced message formatting (0) From There are 2147483647 users logged in now. To There are 2,147,483,647 users logged in now.
Getting to advanced message formatting (1) Use a formatter logger: Logger logger = LogManager.getFormatterLogger(); logger.debug("There are %,d users logged in now.", () -> getUserCount());
Getting to advanced message formatting (2) You can format numbers like this as well: logger.debug("Gain or loss: $ %(,.2f", delta); Gives you: Gain or loss: $ (12,582.19)
Getting to advanced message formatting (3) You can get extra fancy with dates: Calendar c = new GregorianCalendar(1918, MAY, 11); logger.info("Richard Feynman's Birthday: %1$tb %1$te, %1$tY", c); Gives you: Richard Feynman's: May 11, 1918
Noodle on that one for a minute How would you only log open and close door events using loggers and levels? You can assume that door events are logged at what? Maybe some appliances log door events at the INFO level and others at the DEBUG level. What if doors from a third party library log this kind of event at TRACE, another twist!
Understanding Markers Instead of: logger.info("Door opened"); ... logger.info("Door closed"); Do this: logger.info(DOOR, "Door opened"); ... logger.info(DOOR, "Door closed"); Where DOOR is a constant that defines a marker: public static final Marker DOOR = MarkerManager.getMarker( DOOR );
Configuring Makers (inclusive) <?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN" monitorInterval="5"> <Appenders> <Console name="Console" target="SYSTEM_OUT" /> </Appenders> <Loggers> <Root level="WARN"> <AppenderRef ref="Console" /> </Root> <Logger name="com.example.oven" level="DEBUG" /> <Logger name="com.example.garage" level="INFO" /> </Loggers> <Filters> <MarkerFilter marker="DOOR" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> </Configuration>
Hiding Marked events If instead, we wanted to hide DOOR events, we d say: <MarkerFilter marker= DOOR" onMatch="DENY" onMismatch="NEUTRAL" />
Marker usage examples I ve used markers for: Different kinds of hex dumps: Compressed buffers Decompressed buffers IO buffers Different kinds of SQL statements: Queries Batches Prepared statements Flow tracing (built-in Log4j) and more
Coding Flow Tracing public String doIt() { logger.entry(); String testMsg = compute() return logger.exit(testMsg); }
Configuring Flow Tracing <File name="log" fileName="target/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File>
Reading Flow Tracing 19:08:07.060 TRACE com.test.TestService 38 doIt - entry parms(0) 19:08:07.060 TRACE com.test.TestService 42 doIt - exit with (Hello, World)
Flow Tracing and Better Stack Traces 19:08:07.077 DEBUG com.test.TestService 32 exampleException - catching java.lang.ArrayIndexOutOfBoundsException: 3 at com.test.TestService.exampleException(TestService.java:29) [classes/:?] at com.test.App.main(App.java:9) [classes/:?] at com.test.AppTest.testApp(AppTest.java:15) [test-classes/:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29] at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29] at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99) [junit-4.3.1.jar:?] at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81) [junit-4.3.1.jar:?] at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?] at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75) [junit-4.3.1.jar:?] at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45) [junit-4.3.1.jar:?] at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66) [junit-4.3.1.jar:?]
Flow Tracing Gotcha Class Method Line All comes from a stack snapshot
Fish Tagging Many clients Many threads One logger per unique client
Fish tagging with context stack ThreadContext.push(UUID.randomUUID().toString()); // Add the fishtag; logger.debug("Message 1"); ... logger.debug("Message 2"); ThreadContext.pop();
Fish tagging with context map ThreadContext.put("id", UUID.randomUUID().toString(); // Add the fishtag; ThreadContext.put("ipAddress", request.getRemoteAddr()); ThreadContext.put("loginId", session.getAttribute("loginId")); ThreadContext.put("hostName", request.getServerName()); ... logger.debug("Message 1"); ... logger.debug("Message 2"); ... ThreadContext.clear();
Fish tagging with pattern layout Use %X by itself to include the full contents of the Map. Use %X{key} to include the specified key. Use %x to include the full contents of the Stack.