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