The Art of Logging - Gary Gregory

Download Report

Transcript The Art of Logging - Gary Gregory

The Art of Logging
An exploration with Apache Log4j 2
Gary Gregory
[email protected]
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
Log4j API architecture
Pluggable implementation: SLF4J
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
6 built-in 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:


Levels


What it is I am telling my audience?
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.
Key Concepts: Configuration

Appenders

Layouts

Filters
Appenders – where log events go

Async

Kafka

SMTP

Console

Memory-mapped file

Socket

Failover


Syslog

File
NoSQL: CouchDB,
MongoDB

ZeroMQ

Flume

Random-access file

JDBC

Rewrite

JMS Queue

Rolling file

JMS Topic


JPA
Rolling randomaccess file

Routing
Layouts: What log events look like

Some appenders need a layout

Pattern layout
%d %-5level [%t][%logger] %msg%n%throwable
2015-10-25 19:23:10,652 DEBUG [main][org.jboss.logging] Logging Provider: org.jboss.logging.Log4j2LoggerProvider
2015-10-25 19:23:10,802 INFO
[main][org.hibernate.Version] HHH000412: Hibernate Core {5.0.2.Final}
2015-10-25 19:23:10,802 INFO
[main][org.hibernate.cfg.Environment] HHH000206: hibernate.properties not found
2015-10-25 19:23:10,802 INFO
[main][org.hibernate.cfg.Environment] HHH000021: Bytecode provider name : javassist
When to use %logger

Development, show who does the logging

Advanced configuration, for carefully control amount of logging
Layouts

CSV

Syslog

GELF

XML

HTML

JSON

Pattern

RFC 5424 (enhanced Syslog)

Serialized
Pattern Layout

%m%n

%d %-5level [%t][%logger]%notEmpty{[%markerSimpleName]} %msg%n%throwable
PatternLayout %logger tricks
Conversion Pattern
Logger Name
Result
%c{1}
org.apache.commons.Foo
Foo
%c{2}
org.apache.commons.Foo
commons.Foo
%c{1.}
org.apache.commons.Foo
o.a.c.Foo
%c{1.1.~.~}
org.apache.commons.test.Foo o.a.~.~.Foo
%c{.}
org.apache.commons.test.Foo ....Foo
PatternLayout timestamp formats
Pattern
Example
%d{DEFAULT}
2012-11-02 14:34:02,781
%d{ISO8601}
2012-11-02T14:34:02,781
%d{ISO8601_BASIC}
20121102T143402,781
%d{ABSOLUTE}
14:34:02,781
%d{DATE}
02 Nov 2012 14:34:02,781
%d{COMPACT}
20121102143402781
%d{HH:mm:ss,SSS}
14:34:02,781
%d{dd MMM yyyy HH:mm:ss,SSS}
02 Nov 2012 14:34:02,781
%d{HH:mm:ss}{GMT+0}
18:34:02
%d{UNIX}
1351866842
%d{UNIX_MILLIS}
1351866842781
CSV columns

Time Nanos

Source

Time Millis

Context Map

Level

Context Stack

Thread Name

Formatted Message

Logger FQCN

Logger Name

Marker

Thrown Proxy
CSV example
0,1441617184044,DEBUG,main,"one=1, two=2,
three=3",org.apache.logging.log4j.spi.AbstractLogger,,,,org.apache.logging.log4j.c
ore.layout.CsvLogEventLayoutTest.testLayout(CsvLogEventLayoutTest.java:98),{},[
]
JSON example
[
{
"logger":"com.foo.Bar",
"timestamp":"1376681196470",
"level":"INFO",
"thread":"main",
"message":"Message flushed with immediate flush=true"
},
{
"logger":"com.foo.Bar",
"timestamp":"1376681196471",
"level":"ERROR",
"thread":"main",
"message":"Message flushed with immediate flush=true"
}
]
XML example
1.
<?xml version="1.0" encoding="UTF-8"?>
2.
<Events xmlns="http://logging.apache.org/log4j/2.0/events">
3.
<Event logger="com.foo.Bar" timestamp="1373436580419" level="INFO" thread="main">
4.
<Message><![CDATA[This is a log message 1]]></Message>
5.
<Marker parent="Parent Marker"><Child Marker></Marker>
6.
</Event>
7.
<Event logger="com.foo.Baz" timestamp="1373436580420" level="INFO" thread="main">
8.
<Message><![CDATA[This is a log message 2]]></Message>
9.
<Marker><The Marker Name></Marker>
10.
</Event>
11.
</Events>
Know you audience

You’re a developer


Debug and trace your own code

DEBUG Opening file config.xml…

DEBUG Reading account [email protected] from…

TRACE Enter connectJdbc(“jdbc:…”)

TRACE Exit connectJdbc(“jdbc:…”): true
You inform users:

Display status:

INFO Starting server on port 80

INFO Listening for requests…
Know you audience

No clear cut rules

A warning to a developer may not be appropriate for a user

WARN Failed to connect to database DB1, failing over to DB2.

WARN Failed logon attempt #2, one more try before account lock out.
Asynchronous logging

Asynchronous Logging

Asynchronous Loggers
Synchronous logging
Asynchronous logging
LMAX Disruptor library
Mule ESB latency vs. concurrency
“how much latency was
added as we added more
concurrent messages. The
blue line are the results of
using Mule 3.5. The green
line represent the latency of
doing no logging at all. The
read line are the results
using the new logging
infrastructure. Hey wait?
Where’s the red line?! Look
closely, it’s almost on top of
the green line, which means
that now logging doesn’t add
any significant latency.“
http://blogs.mulesoft.com/dev/mule-dev/mule-3-6-asynchronous-logging/
Mule ESB throughput vs. concurrency
“we see how many TPS
(transactions per second)
were delivered as the
concurrency increased. Again,
we see our new asynchronous
logging infrastructure
performing very closely to not
logging at all and showing a
huge improvement from our
prior solution.”
http://blogs.mulesoft.com/dev/mule-dev/mule-3-6-asynchronous-logging/
What we did not talk about

Custom loggers

Custom levels

Custom appenders

Custom layouts

Custom message factories

Using multiple-inheritance with Markers

The plugin system

Logger contexts

Web app configuration
Learning more


Gary Gregory

[email protected]

https://garygregory.wordpress.com/
Log4j

https://logging.apache.org/log4j/2.x

https://logging.apache.org/log4j/2.x/articles.html
Q&A