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