1 /* $Id$ */
2 /*
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 #include <pj/types.h>
21 #include <pj/log.h>
22 #include <pj/string.h>
23 #include <pj/os.h>
24 #include <pj/compat/stdarg.h>
25 
26 #if PJ_LOG_MAX_LEVEL >= 1
27 
28 #if 0
29 PJ_DEF_DATA(int) pj_log_max_level = PJ_LOG_MAX_LEVEL;
30 #else
31 static int pj_log_max_level = PJ_LOG_MAX_LEVEL;
32 #endif
33 
34 static void *g_last_thread;
35 
36 #if PJ_HAS_THREADS
37 static long thread_suspended_tls_id = -1;
38 #  if PJ_LOG_ENABLE_INDENT
39 static long thread_indent_tls_id = -1;
40 #  endif
41 #endif
42 
43 #if !PJ_LOG_ENABLE_INDENT || !PJ_HAS_THREADS
44 static int log_indent;
45 #endif
46 
47 static pj_log_func *log_writer = &pj_log_write;
48 static unsigned log_decor = PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC |
49 			    PJ_LOG_HAS_SENDER | PJ_LOG_HAS_NEWLINE |
50 			    PJ_LOG_HAS_SPACE | PJ_LOG_HAS_THREAD_SWC |
51 			    PJ_LOG_HAS_INDENT
52 #if (defined(PJ_WIN32) && PJ_WIN32!=0) || \
53     (defined(PJ_WIN64) && PJ_WIN64!=0)
54 			    | PJ_LOG_HAS_COLOR
55 #endif
56 			    ;
57 
58 static pj_color_t PJ_LOG_COLOR_0 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R;
59 static pj_color_t PJ_LOG_COLOR_1 = PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_R;
60 static pj_color_t PJ_LOG_COLOR_2 = PJ_TERM_COLOR_BRIGHT |
61 				   PJ_TERM_COLOR_R |
62 				   PJ_TERM_COLOR_G;
63 static pj_color_t PJ_LOG_COLOR_3 = PJ_TERM_COLOR_BRIGHT |
64 				   PJ_TERM_COLOR_R |
65 				   PJ_TERM_COLOR_G |
66 				   PJ_TERM_COLOR_B;
67 static pj_color_t PJ_LOG_COLOR_4 = PJ_TERM_COLOR_R |
68 				   PJ_TERM_COLOR_G |
69 				   PJ_TERM_COLOR_B;
70 static pj_color_t PJ_LOG_COLOR_5 = PJ_TERM_COLOR_R |
71 				   PJ_TERM_COLOR_G |
72 				   PJ_TERM_COLOR_B;
73 static pj_color_t PJ_LOG_COLOR_6 = PJ_TERM_COLOR_R |
74 				   PJ_TERM_COLOR_G |
75 				   PJ_TERM_COLOR_B;
76 /* Default terminal color */
77 static pj_color_t PJ_LOG_COLOR_77 = PJ_TERM_COLOR_R |
78 				    PJ_TERM_COLOR_G |
79 				    PJ_TERM_COLOR_B;
80 
81 #if PJ_LOG_USE_STACK_BUFFER==0
82 static char log_buffer[PJ_LOG_MAX_SIZE];
83 #endif
84 
85 #define LOG_MAX_INDENT		80
86 
87 #if PJ_HAS_THREADS
logging_shutdown(void)88 static void logging_shutdown(void)
89 {
90     if (thread_suspended_tls_id != -1) {
91 	pj_thread_local_free(thread_suspended_tls_id);
92 	thread_suspended_tls_id = -1;
93     }
94 #  if PJ_LOG_ENABLE_INDENT
95     if (thread_indent_tls_id != -1) {
96 	pj_thread_local_free(thread_indent_tls_id);
97 	thread_indent_tls_id = -1;
98     }
99 #  endif
100 }
101 #endif	/* PJ_HAS_THREADS */
102 
103 #if PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS
log_set_indent(int indent)104 static void log_set_indent(int indent)
105 {
106     if (indent < 0) indent = 0;
107     pj_thread_local_set(thread_indent_tls_id, (void*)(pj_ssize_t)indent);
108 }
109 
log_get_raw_indent(void)110 static int log_get_raw_indent(void)
111 {
112     return (long)(pj_ssize_t)pj_thread_local_get(thread_indent_tls_id);
113 }
114 
115 #else
log_set_indent(int indent)116 static void log_set_indent(int indent)
117 {
118     log_indent = indent;
119     if (log_indent < 0) log_indent = 0;
120 }
121 
log_get_raw_indent(void)122 static int log_get_raw_indent(void)
123 {
124     return log_indent;
125 }
126 #endif	/* PJ_LOG_ENABLE_INDENT && PJ_HAS_THREADS */
127 
log_get_indent(void)128 static int log_get_indent(void)
129 {
130     int indent = log_get_raw_indent();
131     return indent > LOG_MAX_INDENT ? LOG_MAX_INDENT : indent;
132 }
133 
pj_log_add_indent(int indent)134 PJ_DEF(void) pj_log_add_indent(int indent)
135 {
136     log_set_indent(log_get_raw_indent() + indent);
137 }
138 
pj_log_push_indent(void)139 PJ_DEF(void) pj_log_push_indent(void)
140 {
141     pj_log_add_indent(PJ_LOG_INDENT_SIZE);
142 }
143 
pj_log_pop_indent(void)144 PJ_DEF(void) pj_log_pop_indent(void)
145 {
146     pj_log_add_indent(-PJ_LOG_INDENT_SIZE);
147 }
148 
pj_log_init(void)149 pj_status_t pj_log_init(void)
150 {
151 #if PJ_HAS_THREADS
152     if (thread_suspended_tls_id == -1) {
153 	pj_status_t status;
154 	status = pj_thread_local_alloc(&thread_suspended_tls_id);
155 	if (status != PJ_SUCCESS)
156 	    return status;
157 
158 #  if PJ_LOG_ENABLE_INDENT
159 	status = pj_thread_local_alloc(&thread_indent_tls_id);
160 	if (status != PJ_SUCCESS) {
161 	    pj_thread_local_free(thread_suspended_tls_id);
162 	    thread_suspended_tls_id = -1;
163 	    return status;
164 	}
165 #  endif
166 	pj_atexit(&logging_shutdown);
167     }
168 #endif
169     g_last_thread = NULL;
170     return PJ_SUCCESS;
171 }
172 
pj_log_set_decor(unsigned decor)173 PJ_DEF(void) pj_log_set_decor(unsigned decor)
174 {
175     log_decor = decor;
176 }
177 
pj_log_get_decor(void)178 PJ_DEF(unsigned) pj_log_get_decor(void)
179 {
180     return log_decor;
181 }
182 
pj_log_set_color(int level,pj_color_t color)183 PJ_DEF(void) pj_log_set_color(int level, pj_color_t color)
184 {
185     switch (level)
186     {
187 	case 0: PJ_LOG_COLOR_0 = color;
188 	    break;
189 	case 1: PJ_LOG_COLOR_1 = color;
190 	    break;
191 	case 2: PJ_LOG_COLOR_2 = color;
192 	    break;
193 	case 3: PJ_LOG_COLOR_3 = color;
194 	    break;
195 	case 4: PJ_LOG_COLOR_4 = color;
196 	    break;
197 	case 5: PJ_LOG_COLOR_5 = color;
198 	    break;
199 	case 6: PJ_LOG_COLOR_6 = color;
200 	    break;
201 	/* Default terminal color */
202 	case 77: PJ_LOG_COLOR_77 = color;
203 	    break;
204 	default:
205 	    /* Do nothing */
206 	    break;
207     }
208 }
209 
pj_log_get_color(int level)210 PJ_DEF(pj_color_t) pj_log_get_color(int level)
211 {
212     switch (level) {
213 	case 0:
214 	    return PJ_LOG_COLOR_0;
215 	case 1:
216 	    return PJ_LOG_COLOR_1;
217 	case 2:
218 	    return PJ_LOG_COLOR_2;
219 	case 3:
220 	    return PJ_LOG_COLOR_3;
221 	case 4:
222 	    return PJ_LOG_COLOR_4;
223 	case 5:
224 	    return PJ_LOG_COLOR_5;
225 	case 6:
226 	    return PJ_LOG_COLOR_6;
227 	default:
228 	    /* Return default terminal color */
229 	    return PJ_LOG_COLOR_77;
230     }
231 }
232 
pj_log_set_level(int level)233 PJ_DEF(void) pj_log_set_level(int level)
234 {
235     pj_log_max_level = level;
236 }
237 
238 #if 1
pj_log_get_level(void)239 PJ_DEF(int) pj_log_get_level(void)
240 {
241     return pj_log_max_level;
242 }
243 #endif
244 
pj_log_set_log_func(pj_log_func * func)245 PJ_DEF(void) pj_log_set_log_func( pj_log_func *func )
246 {
247     log_writer = func;
248 }
249 
pj_log_get_log_func(void)250 PJ_DEF(pj_log_func*) pj_log_get_log_func(void)
251 {
252     return log_writer;
253 }
254 
255 /* Temporarily suspend logging facility for this thread.
256  * If thread local storage/variable is not used or not initialized, then
257  * we can only suspend the logging globally across all threads. This may
258  * happen e.g. when log function is called before PJLIB is fully initialized
259  * or after PJLIB is shutdown.
260  */
suspend_logging(int * saved_level)261 static void suspend_logging(int *saved_level)
262 {
263 	/* Save the level regardless, just in case PJLIB is shutdown
264 	 * between suspend and resume.
265 	 */
266 	*saved_level = pj_log_max_level;
267 
268 #if PJ_HAS_THREADS
269     if (thread_suspended_tls_id != -1)
270     {
271 	pj_thread_local_set(thread_suspended_tls_id,
272 			    (void*)(pj_ssize_t)PJ_TRUE);
273     }
274     else
275 #endif
276     {
277 	pj_log_max_level = 0;
278     }
279 }
280 
281 /* Resume logging facility for this thread */
resume_logging(int * saved_level)282 static void resume_logging(int *saved_level)
283 {
284 #if PJ_HAS_THREADS
285     if (thread_suspended_tls_id != -1)
286     {
287 	pj_thread_local_set(thread_suspended_tls_id,
288 			    (void*)(pj_size_t)PJ_FALSE);
289     }
290     else
291 #endif
292     {
293 	/* Only revert the level if application doesn't change the
294 	 * logging level between suspend and resume.
295 	 */
296 	if (pj_log_max_level==0 && *saved_level)
297 	    pj_log_max_level = *saved_level;
298     }
299 }
300 
301 /* Is logging facility suspended for this thread? */
is_logging_suspended(void)302 static pj_bool_t is_logging_suspended(void)
303 {
304 #if PJ_HAS_THREADS
305     if (thread_suspended_tls_id != -1)
306     {
307 	return pj_thread_local_get(thread_suspended_tls_id) != NULL;
308     }
309     else
310 #endif
311     {
312 	return pj_log_max_level == 0;
313     }
314 }
315 
pj_log(const char * sender,int level,const char * format,va_list marker)316 PJ_DEF(void) pj_log( const char *sender, int level,
317 		     const char *format, va_list marker)
318 {
319     pj_time_val now;
320     pj_parsed_time ptime;
321     char *pre;
322 #if PJ_LOG_USE_STACK_BUFFER
323     char log_buffer[PJ_LOG_MAX_SIZE];
324 #endif
325     int saved_level, len, print_len, indent;
326 
327     PJ_CHECK_STACK();
328 
329     if (level > pj_log_max_level)
330 	return;
331 
332     if (is_logging_suspended())
333 	return;
334 
335     /* Temporarily disable logging for this thread. Some of PJLIB APIs that
336      * this function calls below will recursively call the logging function
337      * back, hence it will cause infinite recursive calls if we allow that.
338      */
339     suspend_logging(&saved_level);
340 
341     /* Get current date/time. */
342     pj_gettimeofday(&now);
343     pj_time_decode(&now, &ptime);
344 
345     pre = log_buffer;
346     if (log_decor & PJ_LOG_HAS_LEVEL_TEXT) {
347 	static const char *ltexts[] = { "FATAL:", "ERROR:", " WARN:",
348 			      " INFO:", "DEBUG:", "TRACE:", "DETRC:"};
349 	pj_ansi_strcpy(pre, ltexts[level]);
350 	pre += 6;
351     }
352     if (log_decor & PJ_LOG_HAS_DAY_NAME) {
353 	static const char *wdays[] = { "Sun", "Mon", "Tue", "Wed",
354 				       "Thu", "Fri", "Sat"};
355 	pj_ansi_strcpy(pre, wdays[ptime.wday]);
356 	pre += 3;
357     }
358     if (log_decor & PJ_LOG_HAS_YEAR) {
359 	if (pre!=log_buffer) *pre++ = ' ';
360 	pre += pj_utoa(ptime.year, pre);
361     }
362     if (log_decor & PJ_LOG_HAS_MONTH) {
363 	*pre++ = '-';
364 	pre += pj_utoa_pad(ptime.mon+1, pre, 2, '0');
365     }
366     if (log_decor & PJ_LOG_HAS_DAY_OF_MON) {
367 	*pre++ = '-';
368 	pre += pj_utoa_pad(ptime.day, pre, 2, '0');
369     }
370     if (log_decor & PJ_LOG_HAS_TIME) {
371 	if (pre!=log_buffer) *pre++ = ' ';
372 	pre += pj_utoa_pad(ptime.hour, pre, 2, '0');
373 	*pre++ = ':';
374 	pre += pj_utoa_pad(ptime.min, pre, 2, '0');
375 	*pre++ = ':';
376 	pre += pj_utoa_pad(ptime.sec, pre, 2, '0');
377     }
378     if (log_decor & PJ_LOG_HAS_MICRO_SEC) {
379 	*pre++ = '.';
380 	pre += pj_utoa_pad(ptime.msec, pre, 3, '0');
381     }
382     if (log_decor & PJ_LOG_HAS_SENDER) {
383 	enum { SENDER_WIDTH = PJ_LOG_SENDER_WIDTH };
384 	pj_size_t sender_len = strlen(sender);
385 	if (pre!=log_buffer) *pre++ = ' ';
386 	if (sender_len <= SENDER_WIDTH) {
387 	    while (sender_len < SENDER_WIDTH)
388 		*pre++ = ' ', ++sender_len;
389 	    while (*sender)
390 		*pre++ = *sender++;
391 	} else {
392 	    int i;
393 	    for (i=0; i<SENDER_WIDTH; ++i)
394 		*pre++ = *sender++;
395 	}
396     }
397     if (log_decor & PJ_LOG_HAS_THREAD_ID) {
398 	enum { THREAD_WIDTH = PJ_LOG_THREAD_WIDTH };
399 	const char *thread_name = pj_thread_get_name(pj_thread_this());
400 	pj_size_t thread_len = strlen(thread_name);
401 	*pre++ = ' ';
402 	if (thread_len <= THREAD_WIDTH) {
403 	    while (thread_len < THREAD_WIDTH)
404 		*pre++ = ' ', ++thread_len;
405 	    while (*thread_name)
406 		*pre++ = *thread_name++;
407 	} else {
408 	    int i;
409 	    for (i=0; i<THREAD_WIDTH; ++i)
410 		*pre++ = *thread_name++;
411 	}
412     }
413 
414     if (log_decor != 0 && log_decor != PJ_LOG_HAS_NEWLINE)
415 	*pre++ = ' ';
416 
417     if (log_decor & PJ_LOG_HAS_THREAD_SWC) {
418 	void *current_thread = (void*)pj_thread_this();
419 	if (current_thread != g_last_thread) {
420 	    *pre++ = '!';
421 	    g_last_thread = current_thread;
422 	} else {
423 	    *pre++ = ' ';
424 	}
425     } else if (log_decor & PJ_LOG_HAS_SPACE) {
426 	*pre++ = ' ';
427     }
428 
429 #if PJ_LOG_ENABLE_INDENT
430     if (log_decor & PJ_LOG_HAS_INDENT) {
431 	indent = log_get_indent();
432 	if (indent > 0) {
433 	    pj_memset(pre, PJ_LOG_INDENT_CHAR, indent);
434 	    pre += indent;
435 	}
436     }
437 #endif
438 
439     len = (int)(pre - log_buffer);
440 
441     /* Print the whole message to the string log_buffer. */
442     print_len = pj_ansi_vsnprintf(pre, sizeof(log_buffer)-len, format,
443 				  marker);
444     if (print_len < 0) {
445 	level = 1;
446 	print_len = pj_ansi_snprintf(pre, sizeof(log_buffer)-len,
447 				     "<logging error: msg too long>");
448     }
449     if (print_len < 1 || print_len >= (int)(sizeof(log_buffer)-len)) {
450 	print_len = sizeof(log_buffer) - len - 1;
451     }
452     len = len + print_len;
453     if (len > 0 && len < (int)sizeof(log_buffer)-2) {
454 	if (log_decor & PJ_LOG_HAS_CR) {
455 	    log_buffer[len++] = '\r';
456 	}
457 	if (log_decor & PJ_LOG_HAS_NEWLINE) {
458 	    log_buffer[len++] = '\n';
459 	}
460 	log_buffer[len] = '\0';
461     } else {
462 	len = sizeof(log_buffer)-1;
463 	if (log_decor & PJ_LOG_HAS_CR) {
464 	    log_buffer[sizeof(log_buffer)-3] = '\r';
465 	}
466 	if (log_decor & PJ_LOG_HAS_NEWLINE) {
467 	    log_buffer[sizeof(log_buffer)-2] = '\n';
468 	}
469 	log_buffer[sizeof(log_buffer)-1] = '\0';
470     }
471 
472     /* It should be safe to resume logging at this point. Application can
473      * recursively call the logging function inside the callback.
474      */
475     resume_logging(&saved_level);
476 
477     if (log_writer)
478 	(*log_writer)(level, log_buffer, len);
479 }
480 
481 /*
482 PJ_DEF(void) pj_log_0(const char *obj, const char *format, ...)
483 {
484     va_list arg;
485     va_start(arg, format);
486     pj_log(obj, 0, format, arg);
487     va_end(arg);
488 }
489 */
490 
pj_log_1(const char * obj,const char * format,...)491 PJ_DEF(void) pj_log_1(const char *obj, const char *format, ...)
492 {
493     va_list arg;
494     va_start(arg, format);
495     pj_log(obj, 1, format, arg);
496     va_end(arg);
497 }
498 #endif	/* PJ_LOG_MAX_LEVEL >= 1 */
499 
500 #if PJ_LOG_MAX_LEVEL >= 2
pj_log_2(const char * obj,const char * format,...)501 PJ_DEF(void) pj_log_2(const char *obj, const char *format, ...)
502 {
503     va_list arg;
504     va_start(arg, format);
505     pj_log(obj, 2, format, arg);
506     va_end(arg);
507 }
508 #endif
509 
510 #if PJ_LOG_MAX_LEVEL >= 3
pj_log_3(const char * obj,const char * format,...)511 PJ_DEF(void) pj_log_3(const char *obj, const char *format, ...)
512 {
513     va_list arg;
514     va_start(arg, format);
515     pj_log(obj, 3, format, arg);
516     va_end(arg);
517 }
518 #endif
519 
520 #if PJ_LOG_MAX_LEVEL >= 4
pj_log_4(const char * obj,const char * format,...)521 PJ_DEF(void) pj_log_4(const char *obj, const char *format, ...)
522 {
523     va_list arg;
524     va_start(arg, format);
525     pj_log(obj, 4, format, arg);
526     va_end(arg);
527 }
528 #endif
529 
530 #if PJ_LOG_MAX_LEVEL >= 5
pj_log_5(const char * obj,const char * format,...)531 PJ_DEF(void) pj_log_5(const char *obj, const char *format, ...)
532 {
533     va_list arg;
534     va_start(arg, format);
535     pj_log(obj, 5, format, arg);
536     va_end(arg);
537 }
538 #endif
539 
540 #if PJ_LOG_MAX_LEVEL >= 6
pj_log_6(const char * obj,const char * format,...)541 PJ_DEF(void) pj_log_6(const char *obj, const char *format, ...)
542 {
543     va_list arg;
544     va_start(arg, format);
545     pj_log(obj, 6, format, arg);
546     va_end(arg);
547 }
548 #endif
549 
550