/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.agent.plugin.logger;
import javax.annotation.Nullable;
import org.glowroot.agent.plugin.api.Agent;
import org.glowroot.agent.plugin.api.MessageSupplier;
import org.glowroot.agent.plugin.api.ThreadContext;
import org.glowroot.agent.plugin.api.TimerName;
import org.glowroot.agent.plugin.api.TraceEntry;
import org.glowroot.agent.plugin.api.weaving.BindClassMeta;
import org.glowroot.agent.plugin.api.weaving.BindParameter;
import org.glowroot.agent.plugin.api.weaving.BindReceiver;
import org.glowroot.agent.plugin.api.weaving.BindTraveler;
import org.glowroot.agent.plugin.api.weaving.OnAfter;
import org.glowroot.agent.plugin.api.weaving.OnBefore;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import org.glowroot.agent.plugin.api.weaving.Shim;
public class LogbackAspect {
private static final String TIMER_NAME = "logging";
// constants from ch.qos.logback.classic.Level
private static final int OFF_INT = Integer.MAX_VALUE;
private static final int ERROR_INT = 40000;
private static final int WARN_INT = 30000;
private static final int INFO_INT = 20000;
private static final int DEBUG_INT = 10000;
private static final int TRACE_INT = 5000;
private static final int ALL_INT = Integer.MIN_VALUE;
@Shim("ch.qos.logback.classic.spi.ILoggingEvent")
public interface ILoggingEvent {
@Shim("ch.qos.logback.classic.Level getLevel()")
@Nullable
Level glowroot$getLevel();
@Nullable
String getFormattedMessage();
@Nullable
String getLoggerName();
@Shim("ch.qos.logback.classic.spi.IThrowableProxy getThrowableProxy()")
@Nullable
Object glowroot$getThrowableProxy();
}
@Shim("ch.qos.logback.classic.Level")
public interface Level {
int toInt();
}
@Shim("ch.qos.logback.classic.spi.ThrowableProxy")
public interface ThrowableProxy {
@Nullable
Throwable getThrowable();
}
@Pointcut(className = "ch.qos.logback.classic.Logger", methodName = "callAppenders",
methodParameterTypes = {"ch.qos.logback.classic.spi.ILoggingEvent"},
nestingGroup = "logging", timerName = TIMER_NAME)
public static class CallAppendersAdvice {
private static final TimerName timerName = Agent.getTimerName(CallAppendersAdvice.class);
@OnBefore
public static @Nullable LogAdviceTraveler onBefore(ThreadContext context,
@BindParameter @Nullable ILoggingEvent loggingEvent) {
if (loggingEvent == null) {
return null;
}
String formattedMessage = nullToEmpty(loggingEvent.getFormattedMessage());
Level level = loggingEvent.glowroot$getLevel();
int lvl = level == null ? 0 : level.toInt();
Object throwableProxy = loggingEvent.glowroot$getThrowableProxy();
Throwable t = null;
if (throwableProxy instanceof ThrowableProxy) {
// there is only one other subclass of ch.qos.logback.classic.spi.IThrowableProxy
// and it is only used for logging exceptions over the wire
t = ((ThrowableProxy) throwableProxy).getThrowable();
}
if (LoggerPlugin.markTraceAsError(lvl >= ERROR_INT, lvl >= WARN_INT, t != null)) {
context.setTransactionError(formattedMessage, t);
}
TraceEntry traceEntry;
String loggerName = LoggerPlugin.getAbbreviatedLoggerName(loggingEvent.getLoggerName());
traceEntry = context.startTraceEntry(MessageSupplier.create("log {}: {} - {}",
getLevelStr(lvl), loggerName, formattedMessage), timerName);
return new LogAdviceTraveler(traceEntry, lvl, formattedMessage, t);
}
@OnAfter
public static void onAfter(@BindTraveler @Nullable LogAdviceTraveler traveler) {
if (traveler == null) {
return;
}
Throwable t = traveler.throwable;
if (t != null) {
// intentionally not passing message since it is already the trace entry message
if (traveler.level >= WARN_INT) {
traveler.traceEntry.endWithError(t);
} else {
traveler.traceEntry.endWithInfo(t);
}
} else if (traveler.level >= WARN_INT) {
traveler.traceEntry.endWithError(traveler.formattedMessage);
} else {
traveler.traceEntry.end();
}
}
private static String nullToEmpty(@Nullable String s) {
return s == null ? "" : s;
}
}
// this is for logback prior to 0.9.16
@Pointcut(className = "ch.qos.logback.classic.Logger", methodName = "callAppenders",
methodParameterTypes = {"ch.qos.logback.classic.spi.LoggingEvent"},
nestingGroup = "logging", timerName = TIMER_NAME)
public static class CallAppenders0xAdvice {
private static final TimerName timerName = Agent.getTimerName(CallAppenders0xAdvice.class);
@OnBefore
public static @Nullable LogAdviceTraveler onBefore(ThreadContext context,
@BindReceiver Object logger, @BindParameter @Nullable Object loggingEvent,
@BindClassMeta LoggingEventInvoker invoker) {
if (loggingEvent == null) {
return null;
}
String formattedMessage = invoker.getFormattedMessage(loggingEvent);
int lvl = invoker.getLevel(loggingEvent);
Throwable t = invoker.getThrowable(loggingEvent);
if (LoggerPlugin.markTraceAsError(lvl >= ERROR_INT, lvl >= WARN_INT, t != null)) {
context.setTransactionError(formattedMessage, t);
}
String loggerName =
LoggerPlugin.getAbbreviatedLoggerName(invoker.getLoggerName(logger));
TraceEntry traceEntry =
context.startTraceEntry(MessageSupplier.create("log {}: {} - {}",
getLevelStr(lvl), loggerName, formattedMessage), timerName);
return new LogAdviceTraveler(traceEntry, lvl, formattedMessage, t);
}
@OnAfter
public static void onAfter(@BindTraveler @Nullable LogAdviceTraveler traveler) {
if (traveler == null) {
return;
}
Throwable t = traveler.throwable;
if (t != null) {
// intentionally not passing message since it is already the trace entry message
if (traveler.level >= WARN_INT) {
traveler.traceEntry.endWithError(t);
} else {
traveler.traceEntry.endWithInfo(t);
}
} else if (traveler.level >= WARN_INT) {
traveler.traceEntry.endWithError(traveler.formattedMessage);
} else {
traveler.traceEntry.end();
}
}
}
private static String getLevelStr(int lvl) {
switch (lvl) {
case ALL_INT:
return "all";
case TRACE_INT:
return "trace";
case DEBUG_INT:
return "debug";
case INFO_INT:
return "info";
case WARN_INT:
return "warn";
case ERROR_INT:
return "error";
case OFF_INT:
return "off";
default:
return "unknown (" + lvl + ")";
}
}
private static class LogAdviceTraveler {
private final TraceEntry traceEntry;
private final int level;
private final String formattedMessage;
private final @Nullable Throwable throwable;
private LogAdviceTraveler(TraceEntry traceEntry, int level, String formattedMessage,
@Nullable Throwable throwable) {
this.traceEntry = traceEntry;
this.level = level;
this.formattedMessage = formattedMessage;
this.throwable = throwable;
}
}
}