Commit | Line | Data |
---|---|---|
970ed795 | 1 | /****************************************************************************** |
d44e3c4f | 2 | * Copyright (c) 2000-2016 Ericsson Telecom AB |
970ed795 EL |
3 | * All rights reserved. This program and the accompanying materials |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
d44e3c4f | 7 | * |
8 | * Contributors: | |
9 | * Balasko, Jeno | |
10 | * Lovassy, Arpad | |
11 | * | |
970ed795 EL |
12 | ******************************************************************************/ |
13 | package org.eclipse.titan.executorapi.util; | |
14 | ||
15 | import java.text.SimpleDateFormat; | |
16 | import java.util.Date; | |
17 | import java.util.HashMap; | |
18 | import java.util.Map; | |
19 | ||
20 | /** | |
21 | * Logger utility class. | |
22 | * <p> | |
23 | * Example usage: | |
24 | * <pre> | |
25 | * class A { | |
26 | * | |
27 | * void f1() { | |
28 | * Log.fi(); | |
29 | * f2("abc"); | |
30 | * Log.fo(); | |
31 | * } | |
32 | * | |
33 | * void f2( String s ) { | |
34 | * Log.fi(s); | |
35 | * Log.f("Call doSomething()"); | |
36 | * doSomeThing(); | |
37 | * Log.fo(); | |
38 | * } | |
39 | * | |
40 | * ... | |
41 | * } | |
42 | * </pre> | |
43 | * | |
44 | * <p> | |
45 | * Output: | |
46 | * <pre> | |
47 | * 2014-04-24 15:36:51.203 1 -> A.f1() | |
48 | * 2014-04-24 15:36:51.203 1 -> A.f2( "abc" ) | |
49 | * 2014-04-24 15:36:51.203 1 Call doSomething() | |
50 | * 2014-04-24 15:36:51.203 1 <- A.f2() | |
51 | * 2014-04-24 15:36:51.204 1 <- A.f1() | |
52 | * </pre> | |
53 | */ | |
54 | public class Log { | |
55 | ||
56 | /** | |
57 | * Severity of the log message, log level | |
58 | * @see "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html" | |
59 | */ | |
60 | private enum Severity { | |
61 | /** The OFF has the highest possible rank and is intended to turn off logging. */ | |
62 | OFF, | |
63 | ||
64 | /** The FATAL level designates very severe error events that will presumably lead the application to abort. */ | |
65 | FATAL, | |
66 | ||
67 | /** The ERROR level designates error events that might still allow the application to continue running. */ | |
68 | ERROR, | |
69 | ||
70 | /** The WARN level designates potentially harmful situations. */ | |
71 | WARN, | |
72 | ||
73 | /** The INFO level designates informational messages that highlight the progress of the application at coarse-grained level. */ | |
74 | INFO, | |
75 | ||
76 | /** The DEBUG Level designates fine-grained informational events that are most useful to debug an application. */ | |
77 | DEBUG, | |
78 | ||
79 | /** The TRACE Level designates finer-grained informational events than the DEBUG */ | |
80 | TRACE, | |
81 | ||
82 | /** The ALL has the lowest possible rank and is intended to turn on all logging. */ | |
83 | ALL; | |
84 | }; | |
85 | ||
86 | /** | |
87 | * Global log level. | |
88 | * It effects all the projects that use Log. | |
89 | */ | |
90 | private static Severity sLogLevel = Severity.OFF; | |
91 | ||
92 | /** | |
93 | * Date format for function related logging functions: fi(), fo(), f() | |
94 | */ | |
95 | private static final SimpleDateFormat sFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); | |
96 | ||
97 | /** | |
98 | * Keeps track the call level for each thread. | |
99 | * Call level is a number, that is increased, when a function entry point is logged (fi() is called), | |
100 | * decreased, when a function exit point is logged (fo() is called). | |
101 | * The indentation of the log message depends on the call level, higher call level means more indentation. | |
102 | * Negative call level is treated as 0. | |
103 | */ | |
104 | private static Map<Long, Integer> sCallLevels = new HashMap<Long, Integer>(); | |
105 | ||
106 | /** | |
107 | * Logging states separately for each thread. | |
108 | * Logging can be switched off separately for each thread. true: on, false: off. Default value: true. | |
109 | */ | |
110 | private static Map<Long, Boolean> sLoggingStates = new HashMap<Long, Boolean>(); | |
111 | ||
112 | /** | |
113 | * Function in. | |
114 | * Logs the entry and the given parameter(s) of the method in DEBUG level. | |
115 | * It must be placed at the entry point(s) of the method. | |
116 | * @param aParams array of logged function parameters, it must be added one-by-one, | |
117 | * it doesn't be resolved automatically like class and method name | |
118 | */ | |
119 | public static void fi( final Object... aParams ) { | |
120 | if ( !checkLogLevel( Severity.DEBUG ) || isThreadLoggingOff() ) { | |
121 | return; | |
122 | } | |
123 | ||
124 | StringBuilder sb = new StringBuilder(); | |
125 | logDateAndThreadId(sb); | |
126 | sb.append(" -> "); | |
127 | ||
128 | final int callLevel = getCallLevel(); | |
129 | setCallLevel( callLevel + 1 ); | |
130 | for (int i = 0; i < callLevel; i++) { | |
131 | sb.append(" "); | |
132 | } | |
133 | ||
134 | // 0: getStackTrace(), 1: logMethodName(), 2: fi(), 3: this is the caller function we want to log | |
135 | logMethodName( sb, 3 ); | |
136 | ||
137 | // log parameters | |
138 | sb.append("("); | |
139 | if ( aParams.length > 0 ) { | |
140 | sb.append(" "); | |
141 | for ( int i = 0; i < aParams.length; i++ ) { | |
142 | if ( i > 0 ) { | |
143 | sb.append(", "); | |
144 | } | |
145 | StringUtil.appendObject(sb, aParams[i]); | |
146 | } | |
147 | sb.append(" "); | |
148 | } | |
149 | sb.append(")"); | |
150 | ||
151 | printLog( sb ); | |
152 | } | |
153 | ||
154 | /** | |
155 | * Function out. | |
156 | * Logs the exit and the given return value of the method in DEBUG level. | |
157 | * It must be placed at the exit point(s) of the method. | |
158 | * @param aHasReturnValue true to log return value | |
159 | * @param aReturnValue the return value to log, it can be null if aHasReturnValue == false | |
160 | */ | |
161 | private static void fo( final boolean aHasReturnValue, final Object aReturnValue ) { | |
162 | if ( !checkLogLevel( Severity.DEBUG ) || isThreadLoggingOff() ) { | |
163 | return; | |
164 | } | |
165 | ||
166 | StringBuilder sb = new StringBuilder(); | |
167 | logDateAndThreadId(sb); | |
168 | sb.append( " <- " ); | |
169 | ||
170 | int callLevel = getCallLevel(); | |
171 | setCallLevel( --callLevel ); | |
172 | for (int i = 0; i < callLevel; i++) { | |
173 | sb.append(" "); | |
174 | } | |
175 | ||
176 | // 0: getStackTrace(), 1: logMethodName(), 2: fo (this private), 3: fo (one of the public ones), 4: this is caller the function we want to log | |
177 | logMethodName( sb, 4 ); | |
178 | ||
179 | // log return value (if any) | |
180 | sb.append("()"); | |
181 | if ( aHasReturnValue ) { | |
182 | sb.append(" "); | |
183 | StringUtil.appendObject(sb, aReturnValue); | |
184 | } | |
185 | printLog( sb ); | |
186 | } | |
187 | ||
188 | /** | |
189 | * Function out. | |
190 | * Logs the exit of the method (but not the return value) in DEBUG level. | |
191 | * It must be placed at the exit point(s) of the method. | |
192 | */ | |
193 | public static void fo() { | |
194 | fo( false, null ); | |
195 | } | |
196 | ||
197 | /** | |
198 | * Function out. | |
199 | * Logs the exit and the given return value of the method in DEBUG level. | |
200 | * It must be placed at the exit point(s) of the method. | |
201 | * @param aReturnValue return value to log | |
202 | */ | |
203 | public static void fo( final Object aReturnValue ) { | |
204 | fo( true, aReturnValue ); | |
205 | } | |
206 | ||
207 | /** | |
208 | * function log (just a general log within the function) | |
209 | * @param aMsg log message | |
210 | */ | |
211 | public static void f( final String aMsg ) { | |
212 | if ( !checkLogLevel( Severity.TRACE ) || isThreadLoggingOff() ) { | |
213 | return; | |
214 | } | |
215 | ||
216 | StringBuilder sb = new StringBuilder(); | |
217 | logDateAndThreadId(sb); | |
218 | sb.append( " " ); | |
219 | ||
220 | final int callLevel = getCallLevel(); | |
221 | for (int i = 0; i < callLevel; i++) { | |
222 | sb.append(" "); | |
223 | } | |
224 | ||
225 | if ( aMsg != null && aMsg.contains( "\n" ) ) { | |
226 | // in case of multiline message other lines are also indented with the same number of spaces | |
227 | final int len = sb.length(); | |
228 | StringBuilder sbMsg = new StringBuilder( aMsg ); | |
229 | //replacement instead of \n | |
230 | StringBuilder sbSpaces = new StringBuilder( len ); | |
231 | sbSpaces.append("\n"); | |
232 | for (int i = 0; i < len; i++) { | |
233 | sbSpaces.append(" "); | |
234 | } | |
235 | ||
236 | // replace \n -> sbSpaces in sbMsg | |
237 | StringUtil.replaceString( sbMsg, "\n", sbSpaces.toString() ); | |
238 | sb.append( sbMsg ); | |
239 | } | |
240 | else { | |
241 | sb.append( aMsg ); | |
242 | } | |
243 | ||
244 | printLog( sb ); | |
245 | } | |
246 | ||
247 | /** | |
248 | * info | |
249 | * @param aMsg log message | |
250 | */ | |
251 | public static void i( final String aMsg ) { | |
252 | if ( !checkLogLevel( Severity.INFO ) || isThreadLoggingOff() ) { | |
253 | return; | |
254 | } | |
255 | StringBuilder sb = new StringBuilder(); | |
256 | logDateAndThreadId(sb); | |
257 | sb.append( " " + aMsg ); | |
258 | printLog( sb ); | |
259 | } | |
260 | ||
261 | /** | |
262 | * info unformatted (without datetime) | |
263 | * @param aMsg log message | |
264 | */ | |
265 | public static void iu( final String aMsg ) { | |
266 | if ( !checkLogLevel( Severity.INFO ) || isThreadLoggingOff() ) { | |
267 | return; | |
268 | } | |
269 | StringBuilder sb = new StringBuilder( aMsg ); | |
270 | printLog( sb ); | |
271 | } | |
272 | ||
273 | /** | |
274 | * Switch on logging for the current thread | |
275 | */ | |
276 | public static void on() { | |
277 | final long threadId = Thread.currentThread().getId(); | |
278 | sLoggingStates.put(threadId, true); | |
279 | } | |
280 | ||
281 | /** | |
282 | * Switch off logging for the current thread | |
283 | */ | |
284 | public static void off() { | |
285 | final long threadId = Thread.currentThread().getId(); | |
286 | sLoggingStates.put(threadId, false); | |
287 | } | |
288 | ||
289 | /** | |
290 | * Adds full datetime and thread id to the log string. | |
291 | * @param aSb [in/out] the log string, where new strings are added | |
292 | */ | |
293 | private static void logDateAndThreadId( final StringBuilder aSb ) { | |
294 | final long threadId = Thread.currentThread().getId(); | |
295 | aSb.append( sFormat.format( new Date() ) + " " + String.format( "%3d", threadId ) ); | |
296 | } | |
297 | ||
298 | /** | |
299 | * Checks if the global static log level reaches the minimum required log level, | |
300 | * @param aMinRequired minimum required log level | |
301 | * @return true, if the global log level >= required log level | |
302 | */ | |
303 | private static boolean checkLogLevel( final Severity aMinRequired ) { | |
304 | return sLogLevel.ordinal() >= aMinRequired.ordinal(); | |
305 | } | |
306 | ||
307 | /** | |
308 | * Adds class and method name to the log string. | |
309 | * Short class name is used without full qualification. | |
310 | * @param aSb [in/out] the log string, where new strings are added | |
311 | * @param aStackTraceElementIndex [in] the call stack index, where we get the class and method name. | |
312 | * + 1 must be added, because logMethodName() increases the call stack size by 1 | |
313 | */ | |
314 | private static void logMethodName( final StringBuilder aSb, final int aStackTraceElementIndex ) { | |
315 | final StackTraceElement ste = Thread.currentThread().getStackTrace()[ aStackTraceElementIndex ]; | |
316 | final String className = ste.getClassName(); | |
317 | final String shortClassName = className.substring( className.lastIndexOf('.') + 1 ); | |
318 | final String methodName = ste.getMethodName(); | |
319 | aSb.append( shortClassName + "." + methodName ); | |
320 | } | |
321 | ||
322 | /** | |
323 | * @return true if logging for the current thread switched off. | |
324 | * If there is no info for this thread, a new (<current thread>, true) item is added to the map | |
325 | */ | |
326 | private static boolean isThreadLoggingOff() { | |
327 | final long threadId = Thread.currentThread().getId(); | |
328 | ||
329 | if(!sLoggingStates.containsKey(threadId)) { | |
330 | sLoggingStates.put(threadId, true); | |
331 | } | |
332 | ||
333 | return !sLoggingStates.get( threadId ); | |
334 | } | |
335 | ||
336 | /** | |
337 | * @return Call level for the current thread. | |
338 | * If there is no info for this thread, a new (<current thread>, 0) item is added to the map | |
339 | */ | |
340 | private static int getCallLevel() { | |
341 | final long threadId = Thread.currentThread().getId(); | |
342 | if(!sCallLevels.containsKey( threadId ) ) { | |
343 | sCallLevels.put( threadId, 0 ); | |
344 | } | |
345 | final int callLevel = sCallLevels.get( threadId ); | |
346 | return callLevel; | |
347 | } | |
348 | ||
349 | /** | |
350 | * Sets the call level of the current thread. | |
351 | * If sCallLevels does NOT contain current thread as key, it creates it. | |
352 | * @param aNewValue new value of the logging level for the thread, in can be negative | |
353 | */ | |
354 | private static void setCallLevel( final int aNewValue ) { | |
355 | final long threadId = Thread.currentThread().getId(); | |
356 | sCallLevels.put( threadId, aNewValue ); | |
357 | } | |
358 | ||
359 | /** | |
360 | * prints the log string to the output | |
361 | * @param aLogString the log string, it can be multiline | |
362 | */ | |
363 | private static void printLog( final StringBuilder aLogString ) { | |
364 | System.out.println( aLogString ); | |
365 | } | |
366 | } |