/* * 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 java.util.Enumeration; 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.BindParameter; import org.glowroot.agent.plugin.api.weaving.BindReceiver; import org.glowroot.agent.plugin.api.weaving.BindTraveler; import org.glowroot.agent.plugin.api.weaving.IsEnabled; 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 Log4jAspect { private static final String TIMER_NAME = "logging"; // constants from org.apache.log4j.Priority private static final int OFF_INT = Integer.MAX_VALUE; private static final int FATAL_INT = 50000; 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 ALL_INT = Integer.MIN_VALUE; @Shim("org.apache.log4j.Category") public interface Logger { @Nullable String getName(); @Shim("org.apache.log4j.Category getParent()") @Nullable Logger glowroot$getParent(); @Nullable Enumeration<?> getAllAppenders(); } @Shim("org.apache.log4j.Priority") public interface Level { int toInt(); } @Pointcut(className = "org.apache.log4j.Category", methodName = "forcedLog", methodParameterTypes = {"java.lang.String", "org.apache.log4j.Priority", "java.lang.Object", "java.lang.Throwable"}, nestingGroup = "logging", timerName = TIMER_NAME) public static class ForcedLogAdvice { private static final TimerName timerName = Agent.getTimerName(ForcedLogAdvice.class); @IsEnabled @SuppressWarnings("unboxing.of.nullable") public static boolean isEnabled(@BindReceiver Logger logger) { // check to see if no appenders, then don't capture (this is just to avoid confusion) // log4j itself will log a warning: // "No appenders could be found for logger, Please initialize the log4j system properly" // (see org.apache.log4j.Hierarchy.emitNoAppenderWarning()) Logger curr = logger; while (true) { Enumeration<?> e = curr.getAllAppenders(); if (e != null && e.hasMoreElements()) { // has at least one appender return true; } curr = curr.glowroot$getParent(); if (curr == null) { return false; } } } @OnBefore @SuppressWarnings("unused") public static TraceEntry onBefore(ThreadContext context, @BindReceiver Logger logger, @BindParameter @Nullable String fqcn, @BindParameter @Nullable Level level, @BindParameter @Nullable Object message, @BindParameter @Nullable Throwable t) { String messageText = String.valueOf(message); int lvl = level == null ? 0 : level.toInt(); if (LoggerPlugin.markTraceAsError(lvl >= ERROR_INT, lvl >= WARN_INT, t != null)) { context.setTransactionError(messageText, t); } String loggerName = LoggerPlugin.getAbbreviatedLoggerName(logger.getName()); return context.startTraceEntry(MessageSupplier.create("log {}: {} - {}", getLevelStr(lvl), loggerName, messageText), timerName); } @OnAfter @SuppressWarnings("unused") public static void onAfter(@BindTraveler TraceEntry traceEntry, @BindParameter @Nullable String fqcn, @BindParameter @Nullable Level level, @BindParameter @Nullable Object message, @BindParameter @Nullable Throwable t) { int lvl = level == null ? 0 : level.toInt(); if (t != null) { // intentionally not passing message since it is already the trace entry message if (lvl >= WARN_INT) { traceEntry.endWithError(t); } else { traceEntry.endWithInfo(t); } } else if (lvl >= WARN_INT) { traceEntry.endWithError(String.valueOf(message)); } else { traceEntry.end(); } } private static String getLevelStr(int lvl) { switch (lvl) { case ALL_INT: return "all"; case DEBUG_INT: return "debug"; case INFO_INT: return "info"; case WARN_INT: return "warn"; case ERROR_INT: return "error"; case FATAL_INT: return "fatal"; case OFF_INT: return "off"; default: return "unknown (" + lvl + ")"; } } } }