001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2015 ForgeRock AS. 016 */ 017package org.opends.server.loggers; 018 019import static org.opends.server.loggers.TraceSettings.Level.*; 020 021import java.util.Map; 022 023import org.forgerock.i18n.slf4j.LocalizedLogger; 024 025/** 026 * Class for source-code tracing at the method level. 027 * 028 * One DebugTracer instance exists for each Java class using tracing. 029 * Tracer must be registered with the DebugLogger. 030 * 031 * Logging is always done at a level basis, with debug log messages 032 * exceeding the trace threshold being traced, others being discarded. 033 */ 034public class DebugTracer 035{ 036 /** 037 * We have to hardcode this value because we cannot import 038 * {@code org.opends.server.loggers.slf4j.OpenDJLoggerAdapter.class.getName()} 039 * to avoid OSGI split package issues. 040 * @see OPENDJ-2226 041 */ 042 private static final String OPENDJ_LOGGER_ADAPTER_CLASS_NAME = "org.opends.server.loggers.slf4j.OpenDJLoggerAdapter"; 043 044 /** The class this aspect traces. */ 045 private String className; 046 047 /** A class that represents a settings cache entry. */ 048 private class PublisherSettings 049 { 050 private final DebugLogPublisher<?> debugPublisher; 051 private final TraceSettings classSettings; 052 private final Map<String, TraceSettings> methodSettings; 053 054 private PublisherSettings(String className, DebugLogPublisher<?> publisher) 055 { 056 debugPublisher = publisher; 057 classSettings = publisher.getClassSettings(className); 058 methodSettings = publisher.getMethodSettings(className); 059 } 060 061 @Override 062 public String toString() 063 { 064 return getClass().getSimpleName() + "(" 065 + "className=" + className 066 + ", classSettings=" + classSettings 067 + ", methodSettings=" + methodSettings 068 + ")"; 069 } 070 } 071 072 private PublisherSettings[] publisherSettings; 073 074 /** 075 * Construct a new DebugTracer object with cached settings obtained from 076 * the provided array of publishers. 077 * 078 * @param className The class name to use as category for logging. 079 * @param publishers The array of publishers to obtain the settings from. 080 */ 081 DebugTracer(String className, DebugLogPublisher<?>[] publishers) 082 { 083 this.className = className; 084 publisherSettings = toPublisherSettings(publishers); 085 } 086 087 /** 088 * Log the provided message. 089 * 090 * @param msg 091 * message to log. 092 */ 093 public void trace(String msg) 094 { 095 traceException(msg, null); 096 } 097 098 /** 099 * Log the provided message and exception. 100 * 101 * @param msg 102 * the message 103 * @param exception 104 * the exception caught. May be {@code null}. 105 */ 106 public void traceException(String msg, Throwable exception) 107 { 108 StackTraceElement[] stackTrace = null; 109 StackTraceElement[] filteredStackTrace = null; 110 StackTraceElement callerFrame = null; 111 final boolean hasException = exception != null; 112 for (PublisherSettings settings : publisherSettings) 113 { 114 TraceSettings activeSettings = settings.classSettings; 115 Map<String, TraceSettings> methodSettings = settings.methodSettings; 116 117 if (shouldLog(activeSettings, hasException) || methodSettings != null) 118 { 119 if (stackTrace == null) 120 { 121 stackTrace = Thread.currentThread().getStackTrace(); 122 } 123 if (callerFrame == null) 124 { 125 callerFrame = getCallerFrame(stackTrace); 126 } 127 128 String signature = callerFrame.getMethodName(); 129 130 // Specific method settings still could exist. Try getting 131 // the settings for this method. 132 if (methodSettings != null) 133 { 134 TraceSettings mSettings = methodSettings.get(signature); 135 if (mSettings == null) 136 { 137 // Try looking for an undecorated method name 138 int idx = signature.indexOf('('); 139 if (idx != -1) 140 { 141 mSettings = methodSettings.get(signature.substring(0, idx)); 142 } 143 } 144 145 // If this method does have a specific setting 146 // and it is not supposed to be logged, continue. 147 if (!shouldLog(mSettings, hasException)) 148 { 149 continue; 150 } 151 activeSettings = mSettings; 152 } 153 154 String sourceLocation = callerFrame.getFileName() + ":" + callerFrame.getLineNumber(); 155 156 if (filteredStackTrace == null && activeSettings.getStackDepth() > 0) 157 { 158 StackTraceElement[] trace = hasException ? exception.getStackTrace() : stackTrace; 159 filteredStackTrace = DebugStackTraceFormatter.SMART_FRAME_FILTER.getFilteredStackTrace(trace); 160 } 161 162 if (hasException) 163 { 164 settings.debugPublisher.traceException(activeSettings, signature, 165 sourceLocation, msg, exception, filteredStackTrace); 166 } 167 else 168 { 169 settings.debugPublisher.trace(activeSettings, signature, 170 sourceLocation, msg, filteredStackTrace); 171 } 172 } 173 } 174 } 175 176 /** 177 * Gets the name of the class this tracer traces. 178 * 179 * @return The name of the class this tracer traces. 180 */ 181 String getTracedClassName() 182 { 183 return className; 184 } 185 186 /** 187 * Indicates if logging is enabled for at least one category 188 * in a publisher. 189 * 190 * @return {@code true} if logging is enabled, false otherwise. 191 */ 192 public boolean enabled() 193 { 194 for (PublisherSettings settings : publisherSettings) 195 { 196 if (shouldLog(settings.classSettings) || settings.methodSettings != null) 197 { 198 return true; 199 } 200 } 201 return false; 202 } 203 204 /** 205 * Update the cached settings of the tracer with the settings from the 206 * provided publishers. 207 * 208 * @param publishers The array of publishers to obtain the settings from. 209 */ 210 void updateSettings(DebugLogPublisher<?>[] publishers) 211 { 212 publisherSettings = toPublisherSettings(publishers); 213 } 214 215 private PublisherSettings[] toPublisherSettings(DebugLogPublisher<?>[] publishers) 216 { 217 // Get the settings from all publishers. 218 PublisherSettings[] newSettings = new PublisherSettings[publishers.length]; 219 for(int i = 0; i < publishers.length; i++) 220 { 221 newSettings[i] = new PublisherSettings(className, publishers[i]); 222 } 223 return newSettings; 224 } 225 226 /** 227 * Return the caller stack frame. 228 * 229 * @param stackTrace 230 * The stack trace frames of the caller. 231 * @return the caller stack frame or null if none is found on the stack trace. 232 */ 233 private StackTraceElement getCallerFrame(StackTraceElement[] stackTrace) 234 { 235 if (stackTrace != null && stackTrace.length > 0) 236 { 237 // Skip all logging related classes 238 for (StackTraceElement aStackTrace : stackTrace) 239 { 240 if(!isLoggingStackTraceElement(aStackTrace)) 241 { 242 return aStackTrace; 243 } 244 } 245 } 246 247 return null; 248 } 249 250 /** 251 * Checks if element belongs to a class responsible for logging 252 * (includes the Thread class that may be used to get the stack trace). 253 * 254 * @param trace 255 * the trace element to check. 256 * @return {@code true} if element corresponds to logging 257 */ 258 static boolean isLoggingStackTraceElement(StackTraceElement trace) 259 { 260 String name = trace.getClassName(); 261 return name.startsWith(Thread.class.getName()) 262 || name.startsWith(DebugTracer.class.getName()) 263 || name.startsWith(OPENDJ_LOGGER_ADAPTER_CLASS_NAME) 264 || name.startsWith(LocalizedLogger.class.getName()); 265 } 266 267 /** Indicates if there is something to log. */ 268 private boolean shouldLog(TraceSettings settings, boolean hasException) 269 { 270 return settings != null 271 && (settings.getLevel() == ALL 272 || (hasException && settings.getLevel() == EXCEPTIONS_ONLY)); 273 } 274 275 /** Indicates if there is something to log. */ 276 private boolean shouldLog(TraceSettings settings) 277 { 278 return settings.getLevel() != DISABLED; 279 } 280}