日誌的位置信息包含哪些?
- %C or $class:類名
- %F or %file:文件名
- %L or %line:打印日誌的方法所在文件的行數
- %M or %method:打印日誌的方法名
- %l or %location:包含以上的位置信息
打印日誌位置有什麼影響?
官方文檔上強調了三遍,位置使用要慎用慎用。相比於不使用位置信息:
- 對於同步日誌來說,速度要慢3~5倍
- 對於異步日誌來說,速度要慢30~100倍
日誌位置是怎麼獲取到的?
提示:includeLocation必須得設置爲true
以AsyncLogger爲例, 從處理日誌事件開始看起:
- AsyncLoggerConfig
protected void callAppenders(final LogEvent event) {
//填充屬性
populateLazilyInitializedFields(event);
///把日誌事件入隊
if (!delegate.tryEnqueue(event, this)) {
handleQueueFull(event);
}
}
private void populateLazilyInitializedFields(final LogEvent event) {
//這裏
event.getSource();
event.getThreadName();
}
- MutableLogEvent
public StackTraceElement getSource() {
if (source != null) {
return source;
}
//檢查是否includeLocation爲true
if (loggerFqcn == null || !includeLocation) {
return null;
}
//獲取位置信息的步驟
source = StackLocatorUtil.calcLocation(loggerFqcn);
return source;
}
- StackLocatorUtil
public static StackTraceElement calcLocation(final String fqcnOfLogger) {
return stackLocator.calcLocation(fqcnOfLogger);
}
- StackLocator
public StackTraceElement calcLocation(final String fqcnOfLogger) {
if (fqcnOfLogger == null) {
return null;
}
// LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
//這個就是獲取位置的真正方法了,通過創建一個異常來獲取棧信息
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
StackTraceElement last = null;
for (int i = stackTrace.length - 1; i > 0; i--) {
final String className = stackTrace[i].getClassName();
if (fqcnOfLogger.equals(className)) {
return last;
}
last = stackTrace[i];
}
return null;
}
- StackTraceElement: 我們想要的位置信息
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
//省略部分代碼...
}
- Throwable
public StackTraceElement[] getStackTrace() {
return getOurStackTrace().clone();
}
private synchronized StackTraceElement[] getOurStackTrace() {
//初始化棧信息
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
//填充棧信息
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
//每一層的棧信息是通過本地方法來調用的
native StackTraceElement getStackTraceElement(int index);
小結
日誌位置以前是通過Thread.currentThread().getStackTrace()獲取的, 在2.5版本之後,採用new Throwable().getStackTrace()的方式來獲取,性能上有了一定的提升的。改動見jira, 裏邊有bench mark。