/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2014-2015 ForgeRock AS */ package org.opends.server.loggers; import static org.opends.server.loggers.TraceSettings.Level.*; import java.util.Map; import org.forgerock.i18n.slf4j.LocalizedLogger; /** * Class for source-code tracing at the method level. * * One DebugTracer instance exists for each Java class using tracing. * Tracer must be registered with the DebugLogger. * * Logging is always done at a level basis, with debug log messages * exceeding the trace threshold being traced, others being discarded. */ public class DebugTracer { /** * We have to hardcode this value because we cannot import * {@code org.opends.server.loggers.slf4j.OpenDJLoggerAdapter.class.getName()} * to avoid OSGI split package issues. * @see OPENDJ-2226 */ private static final String OPENDJ_LOGGER_ADAPTER_CLASS_NAME = "org.opends.server.loggers.slf4j.OpenDJLoggerAdapter"; /** The class this aspect traces. */ private String className; /** A class that represents a settings cache entry. */ private class PublisherSettings { private final DebugLogPublisher<?> debugPublisher; private final TraceSettings classSettings; private final Map<String, TraceSettings> methodSettings; private PublisherSettings(String className, DebugLogPublisher<?> publisher) { debugPublisher = publisher; classSettings = publisher.getClassSettings(className); methodSettings = publisher.getMethodSettings(className); } @Override public String toString() { return getClass().getSimpleName() + "(" + "className=" + className + ", classSettings=" + classSettings + ", methodSettings=" + methodSettings + ")"; } } private PublisherSettings[] publisherSettings; /** * Construct a new DebugTracer object with cached settings obtained from * the provided array of publishers. * * @param className The class name to use as category for logging. * @param publishers The array of publishers to obtain the settings from. */ DebugTracer(String className, DebugLogPublisher<?>[] publishers) { this.className = className; publisherSettings = toPublisherSettings(publishers); } /** * Log the provided message. * * @param msg * message to log. */ public void trace(String msg) { traceException(msg, null); } /** * Log the provided message and exception. * * @param msg * the message * @param exception * the exception caught. May be {@code null}. */ public void traceException(String msg, Throwable exception) { StackTraceElement[] stackTrace = null; StackTraceElement[] filteredStackTrace = null; StackTraceElement callerFrame = null; final boolean hasException = exception != null; for (PublisherSettings settings : publisherSettings) { TraceSettings activeSettings = settings.classSettings; Map<String, TraceSettings> methodSettings = settings.methodSettings; if (shouldLog(activeSettings, hasException) || methodSettings != null) { if (stackTrace == null) { stackTrace = Thread.currentThread().getStackTrace(); } if (callerFrame == null) { callerFrame = getCallerFrame(stackTrace); } String signature = callerFrame.getMethodName(); // Specific method settings still could exist. Try getting // the settings for this method. if (methodSettings != null) { TraceSettings mSettings = methodSettings.get(signature); if (mSettings == null) { // Try looking for an undecorated method name int idx = signature.indexOf('('); if (idx != -1) { mSettings = methodSettings.get(signature.substring(0, idx)); } } // If this method does have a specific setting // and it is not supposed to be logged, continue. if (!shouldLog(mSettings, hasException)) { continue; } activeSettings = mSettings; } String sourceLocation = callerFrame.getFileName() + ":" + callerFrame.getLineNumber(); if (filteredStackTrace == null && activeSettings.getStackDepth() > 0) { StackTraceElement[] trace = hasException ? exception.getStackTrace() : stackTrace; filteredStackTrace = DebugStackTraceFormatter.SMART_FRAME_FILTER.getFilteredStackTrace(trace); } if (hasException) { settings.debugPublisher.traceException(activeSettings, signature, sourceLocation, msg, exception, filteredStackTrace); } else { settings.debugPublisher.trace(activeSettings, signature, sourceLocation, msg, filteredStackTrace); } } } } /** * Gets the name of the class this tracer traces. * * @return The name of the class this tracer traces. */ String getTracedClassName() { return className; } /** * Indicates if logging is enabled for at least one category * in a publisher. * * @return {@code true} if logging is enabled, false otherwise. */ public boolean enabled() { for (PublisherSettings settings : publisherSettings) { if (shouldLog(settings.classSettings) || settings.methodSettings != null) { return true; } } return false; } /** * Update the cached settings of the tracer with the settings from the * provided publishers. * * @param publishers The array of publishers to obtain the settings from. */ void updateSettings(DebugLogPublisher<?>[] publishers) { publisherSettings = toPublisherSettings(publishers); } private PublisherSettings[] toPublisherSettings(DebugLogPublisher<?>[] publishers) { // Get the settings from all publishers. PublisherSettings[] newSettings = new PublisherSettings[publishers.length]; for(int i = 0; i < publishers.length; i++) { newSettings[i] = new PublisherSettings(className, publishers[i]); } return newSettings; } /** * Return the caller stack frame. * * @param stackTrace * The stack trace frames of the caller. * @return the caller stack frame or null if none is found on the stack trace. */ private StackTraceElement getCallerFrame(StackTraceElement[] stackTrace) { if (stackTrace != null && stackTrace.length > 0) { // Skip all logging related classes for (StackTraceElement aStackTrace : stackTrace) { if(!isLoggingStackTraceElement(aStackTrace)) { return aStackTrace; } } } return null; } /** * Checks if element belongs to a class responsible for logging * (includes the Thread class that may be used to get the stack trace). * * @param trace * the trace element to check. * @return {@code true} if element corresponds to logging */ static boolean isLoggingStackTraceElement(StackTraceElement trace) { String name = trace.getClassName(); return name.startsWith(Thread.class.getName()) || name.startsWith(DebugTracer.class.getName()) || name.startsWith(OPENDJ_LOGGER_ADAPTER_CLASS_NAME) || name.startsWith(LocalizedLogger.class.getName()); } /** Indicates if there is something to log. */ private boolean shouldLog(TraceSettings settings, boolean hasException) { return settings != null && (settings.getLevel() == ALL || (hasException && settings.getLevel() == EXCEPTIONS_ONLY)); } /** Indicates if there is something to log. */ private boolean shouldLog(TraceSettings settings) { return settings.getLevel() != DISABLED; } }