Avishai Ish-Shalom (@nukemberg)
Software Fairy @ Wix.com
Information theory protip:
Different log events do not have the same value
Performance | Meh |
Extensions | Porn grade |
Features | Essentials + a little extra |
Degradation | what, u don't like blocking? |
Mature, battle tested
Performance | Good |
Extensions | Porn grade |
Features | Carnaval |
Degradation | Good |
Mature, battle tested, featureful
Performance | Insane |
Extensions | HBO |
Features | Carnaval |
Degradation | Very good |
Complete redesign, featureful
Most awesome logger yet
Performance | batshit crazy |
Extensions | What extensions? |
Features | This is Sparta |
Degradation | Very good |
The logger no one knows about
Fast, minimal
TLDR: use SLF4J for libraries
// log4j 2.x
ThreadContext.put("transactionID", UUID.RandomUUID().toString());
// log4j 1.x, logback
MDC.put("userID", userID);
Map m = HashMap();
m.put("extra_data", "extra_value");
logger.info(new StructuredDataMessage("id", "message", "type", m));
m.put("more_data", "more_value");
logger.info(new MapMessage(m));
Logs may still need secondary parsing
Why?
Logs are the primary telemetry channel from production
Ideally we would have close to zero debug telemetry
But we don't know in advance what we'll need
TRACE, DEBUG, INFO, WARN, ERROR
+ FATAL on Log4j
Fastest filtering mechanism
// logback, log4j 1.x
Logger logger = LoggerFactory.getLogger(this.class);
Logger logger = LoggerFactory.getLogger("audit." + this.class);
Logger rootLogger = LoggerFactory.getRootLogger();
// log4j 2.x
Logger logger = LogManager.getLogger(this.class);
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.util.Map;
public class MyApp {
private Logger logger = LogManager.getLogger(MyApp.class.getName());
private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");
private static final Marker UPDATE_MARKER =
MarkerManager.getMarker("SQL_UPDATE").setParents(SQL_MARKER);
private static final Marker QUERY_MARKER =
MarkerManager.getMarker("SQL_QUERY").setParents(SQL_MARKER);
public String doQuery(String table) {
logger.traceEntry(param);
logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);
...
return logger.traceExit(ret);
}
public String doUpdate(String table, Map<String, String> params) {
logger.traceEntry(param);
logger.debug(UPDATE_MARKER, "UPDATE {} SET {}", table, formatCols());
...
return logger.traceExit(ret);
}
}
public int exampleException(String arg) {
logger.traceEntry(arg);
int n;
try {
String msg = messages[messages.length];
n = someFunction(msg);
logger.error("An exception should have been thrown");
} catch (Exception ex) {
logger.catching(ex);
}
return logger.traceExit(n);
}
Probably a bad idea for high performance servers
ThreadContext.put("userFraction", Random.nextInt(100));
<ThreadContextMapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or">
<KeyValuePair key="userFraction" value="1"/>
</ThreadContextMapFilter>
Alternatively, log only userID XXX
ThreadContext.put("userId", requestContext.getUserID());
// trace suspicious requests
if (requestContext.isTrace)
ThreadContext.put("trace", "true");
<DynamicThresholdFilter key="UserId" defaultThreshold="ERROR"
onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="avishai" value="DEBUG"/>
</DynamicThresholdFilter>
<DynamicThresholdFilter key="trace" defaultThreshold="ERROR"
onMatch="ACCEPT" onMismatch="NEUTRAL">
<KeyValuePair key="true" value="TRACE"/>
</DynamicThresholdFilter>
void Constructor() {
CircularFifoBuffer logBuffer = new CircularFifoBuffer(1000);
}
void someMethod() {
logBuffer.add("some message");
logBuffer.add(() -> "lazy debug message");
try {
...
} catch (Exception ex) {
flushBuffer(logBuffer);
}
logBuffer.clear();
}
void flushBuffer(CircularFifoBuffer buffer) {
for (Object o: buffer) {
logger.debug(o);
}
}
Common: GELF, json, msgpack, thrift
huge performance boost, fault tolerance
IMPORTANT: properly shutdown logging subsystem
Wraps another appender
Events placed in queue and consumed by worker thread
Logback - Drop TRACE, DEBUG and INFO when 80% full
Log4j2 - Fail over to secondary error appender
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/debug.log"
filePattern="logs/debug-%d{MM-dd-yyyy}.log.gz">
<BurstFilter level="INFO" rate="100" maxBurst="1000"/>
<PatternLayout>
<pattern>%d %p %c{1.} [%t] %m%n</pattern>
</PatternLayout>
<TimeBasedTriggeringPolicy />
</RollingFile>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
if (logger.isDebugEnabled()) {
logger.debug("some debug message, value: " + object.toString());
}
What's wrong with this picture?
logger.debug("some debug message, value: {}", object);
// or with log4j 2.x and java 8
logger.debug("some debug message, value: {}", () -> object.expensiveMethod());
When level>=DEBUG: 2 functions calls per log message
Better*:
jobs@wix.com
jobs@wix.com