Tizen 2.1 base
[framework/system/dlog.git] / logprint.c
1 /*
2  * DLOG
3  * Copyright (c) 2005-2008, The Android Open Source Project
4  * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <unistd.h>
26
27 #include <logprint.h>
28
29
30 typedef struct FilterInfo_t {
31     char *mTag;
32     log_priority mPri;
33     struct FilterInfo_t *p_next;
34 } FilterInfo;
35
36 struct log_format_t {
37     log_priority global_pri;
38     FilterInfo *filters;
39     log_print_format format;
40 };
41
42 static FilterInfo * filterinfo_new(const char *tag, log_priority pri)
43 {
44         FilterInfo *p_ret;
45         p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo));
46         p_ret->mTag = strdup(tag);
47         p_ret->mPri = pri;
48
49         return p_ret;
50 }
51
52 static void filterinfo_free(FilterInfo *p_info)
53 {
54         if (p_info == NULL) {
55                 return;
56         }
57
58         free(p_info->mTag);
59         p_info->mTag = NULL;
60 }
61
62 /*
63  * Note: also accepts 0-9 priorities
64  * returns DLOG_UNKNOWN if the character is unrecognized
65  */
66 static log_priority filter_char_to_pri (char c)
67 {
68         log_priority pri;
69
70         c = tolower(c);
71
72         if (c >= '0' && c <= '9') {
73                 if (c >= ('0'+DLOG_SILENT)) {
74                         pri = DLOG_VERBOSE;
75                 } else {
76                         pri = (log_priority)(c - '0');
77                 }
78         } else if (c == 'v') {
79                 pri = DLOG_VERBOSE;
80         } else if (c == 'd') {
81                 pri = DLOG_DEBUG;
82         } else if (c == 'i') {
83                 pri = DLOG_INFO;
84         } else if (c == 'w') {
85                 pri = DLOG_WARN;
86         } else if (c == 'e') {
87                 pri = DLOG_ERROR;
88         } else if (c == 'f') {
89                 pri = DLOG_FATAL;
90         } else if (c == 's') {
91                 pri = DLOG_SILENT;
92         } else if (c == '*') {
93                 pri = DLOG_DEFAULT;
94         } else {
95                 pri = DLOG_UNKNOWN;
96         }
97
98         return pri;
99 }
100
101 static char filter_pri_to_char (log_priority pri)
102 {
103         switch (pri) {
104                 case DLOG_VERBOSE:       return 'V';
105                 case DLOG_DEBUG:         return 'D';
106                 case DLOG_INFO:          return 'I';
107                 case DLOG_WARN:          return 'W';
108                 case DLOG_ERROR:         return 'E';
109                 case DLOG_FATAL:         return 'F';
110                 case DLOG_SILENT:        return 'S';
111
112                 case DLOG_DEFAULT:
113                 case DLOG_UNKNOWN:
114                 default:                        return '?';
115         }
116 }
117
118 static log_priority filter_pri_for_tag(log_format *p_format, const char *tag)
119 {
120     FilterInfo *p_curFilter;
121 //      log_priority pri = DLOG_SILENT;
122     for (p_curFilter = p_format->filters; p_curFilter != NULL; p_curFilter = p_curFilter->p_next )
123         {
124                 if (0 == strcmp(tag, p_curFilter->mTag))
125                 {
126                         if (p_curFilter->mPri == DLOG_DEFAULT) {
127                                 return p_format->global_pri;
128                         } else {
129                                 return p_curFilter->mPri;
130                         }
131                 }
132         }
133         return p_format->global_pri;
134 }
135
136 /** for debugging */
137 void dump_filters(log_format *p_format)
138 {
139     FilterInfo *p_fi;
140
141     for (p_fi = p_format->filters ; p_fi != NULL ; p_fi = p_fi->p_next) {
142         char cPri = filter_pri_to_char(p_fi->mPri);
143         if (p_fi->mPri == DLOG_DEFAULT) {
144             cPri = filter_pri_to_char(p_format->global_pri);
145         }
146         fprintf(stderr,"%s:%c\n", p_fi->mTag, cPri);
147     }
148
149     fprintf(stderr,"*:%c\n", filter_pri_to_char(p_format->global_pri));
150
151 }
152
153 /**
154  * returns 1 if this log line should be printed based on its priority
155  * and tag, and 0 if it should not
156  */
157 int log_should_print_line (log_format *p_format, const char *tag, log_priority pri)
158 {
159     return pri >= filter_pri_for_tag(p_format, tag);
160 }
161
162 log_format *log_format_new()
163 {
164     log_format *p_ret;
165
166     p_ret = calloc(1, sizeof(log_format));
167
168     p_ret->global_pri = DLOG_SILENT;
169     p_ret->format = FORMAT_BRIEF;
170
171     return p_ret;
172 }
173
174 void log_format_free(log_format *p_format)
175 {
176     FilterInfo *p_info, *p_info_old;
177
178     p_info = p_format->filters;
179
180     while (p_info != NULL) {
181         p_info_old = p_info;
182         p_info = p_info->p_next;
183
184         free(p_info_old);
185     }
186
187     free(p_format);
188 }
189
190 void log_set_print_format(log_format *p_format,log_print_format format)
191 {
192     p_format->format=format;
193 }
194
195
196
197 /**
198  * Returns FORMAT_OFF on invalid string
199  */
200 log_print_format log_format_from_string(const char * formatString)
201 {
202     static log_print_format format;
203
204     if (strcmp(formatString, "brief") == 0) format = FORMAT_BRIEF;
205     else if (strcmp(formatString, "process") == 0) format = FORMAT_PROCESS;
206     else if (strcmp(formatString, "tag") == 0) format = FORMAT_TAG;
207     else if (strcmp(formatString, "thread") == 0) format = FORMAT_THREAD;
208     else if (strcmp(formatString, "raw") == 0) format = FORMAT_RAW;
209     else if (strcmp(formatString, "time") == 0) format = FORMAT_TIME;
210     else if (strcmp(formatString, "threadtime") == 0) format = FORMAT_THREADTIME;
211     else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
212     else format = FORMAT_OFF;
213
214     return format;
215 }
216
217 /**
218  * filterExpression: a single filter expression
219  * eg "AT:d"
220  *
221  * returns 0 on success and -1 on invalid expression
222  *
223  * Assumes single threaded execution
224  */
225
226 int log_add_filter_rule(log_format *p_format,
227         const char *filterExpression)
228 {
229 //    size_t i=0;
230     size_t tagNameLength;
231     log_priority pri = DLOG_DEFAULT;
232
233     tagNameLength = strcspn(filterExpression, ":");
234
235     if (tagNameLength == 0) {
236         goto error;
237     }
238
239     if(filterExpression[tagNameLength] == ':') {
240         pri = filter_char_to_pri(filterExpression[tagNameLength+1]);
241
242         if (pri == DLOG_UNKNOWN) {
243             goto error;
244         }
245     }
246
247     if(0 == strncmp("*", filterExpression, tagNameLength)) {
248         // This filter expression refers to the global filter
249         // The default level for this is DEBUG if the priority
250         // is unspecified
251         if (pri == DLOG_DEFAULT) {
252             pri = DLOG_DEBUG;
253         }
254
255         p_format->global_pri = pri;
256     } else {
257         // for filter expressions that don't refer to the global
258         // filter, the default is verbose if the priority is unspecified
259         if (pri == DLOG_DEFAULT) {
260             pri = DLOG_VERBOSE;
261         }
262
263         char *tagName;
264         tagName = strndup(filterExpression, tagNameLength);
265
266         FilterInfo *p_fi = filterinfo_new(tagName, pri);
267         free(tagName);
268
269         p_fi->p_next = p_format->filters;
270         p_format->filters = p_fi;
271     }
272
273     return 0;
274 error:
275     return -1;
276 }
277
278
279 /**
280  * filterString: a comma/whitespace-separated set of filter expressions
281  *
282  * eg "AT:d *:i"
283  *
284  * returns 0 on success and -1 on invalid expression
285  *
286  * Assumes single threaded execution
287  *
288  */
289
290 int log_add_filter_string(log_format *p_format,
291         const char *filterString)
292 {
293     char *filterStringCopy = strdup (filterString);
294     char *p_cur = filterStringCopy;
295     char *p_ret;
296     int err;
297
298     // Yes, I'm using strsep
299     // FIXME : strtok is more portable than strsep
300     while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
301         // ignore whitespace-only entries
302         if(p_ret[0] != '\0') {
303             err = log_add_filter_rule(p_format, p_ret);
304
305             if (err < 0) {
306                 goto error;
307             }
308         }
309     }
310
311     free (filterStringCopy);
312     return 0;
313 error:
314     free (filterStringCopy);
315     return -1;
316 }
317
318 static inline char * strip_end(char *str)
319 {
320     char *end = str + strlen(str) - 1;
321
322     while (end >= str && isspace(*end))
323         *end-- = '\0';
324     return str;
325 }
326
327
328 /**
329  * Splits a wire-format buffer into an LogEntry
330  * entry allocated by caller. Pointers will point directly into buf
331  *
332  * Returns 0 on success and -1 on invalid wire format (entry will be
333  * in unspecified state)
334  */
335 int log_process_log_buffer(struct logger_entry *buf,log_entry *entry)
336 {
337     size_t tag_len;
338
339     entry->tv_sec = buf->sec;
340     entry->tv_nsec = buf->nsec;
341     entry->pid = buf->pid;
342     entry->tid = buf->tid;
343
344     if (buf->msg[0] < 0 || buf->msg[0] > DLOG_SILENT) { /* char can be signed too */
345
346          /* There is no tag in this message - which is an error, but it might
347           * happen when sombody redirects stdout/err to /dev/log_*.
348           *
349           * Pick ERROR priority as this shouldn't happen.
350           */
351          entry->priority = DLOG_ERROR;
352          entry->tag = "[NO TAG]";
353          entry->messageLen = buf->len;
354          entry->message = buf->msg;
355     } else {
356          entry->priority = buf->msg[0];
357          entry->tag = buf->msg + 1;
358          tag_len = strlen(entry->tag);
359          entry->messageLen = buf->len - tag_len - 3;
360          entry->message = entry->tag + tag_len + 1;
361     }
362
363     return 0;
364 }
365
366
367 /**
368  * Formats a log message into a buffer
369  *
370  * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer
371  * If return value != defaultBuffer, caller must call free()
372  * Returns NULL on malloc error
373  */
374
375 char *log_format_log_line (
376     log_format *p_format,
377     char *defaultBuffer,
378     size_t defaultBufferSize,
379     const log_entry *entry,
380     size_t *p_outLength)
381 {
382 #if defined(HAVE_LOCALTIME_R)
383     struct tm tmBuf;
384 #endif
385     struct tm* ptm;
386     char timeBuf[32];
387  //   char headerBuf[128];
388     char prefixBuf[128], suffixBuf[128];
389     char priChar;
390     int prefixSuffixIsHeaderFooter = 0;
391     char * ret = NULL;
392
393     priChar = filter_pri_to_char(entry->priority);
394
395     /*
396      * Get the current date/time in pretty form
397      *
398      * It's often useful when examining a log with "less" to jump to
399      * a specific point in the file by searching for the date/time stamp.
400      * For this reason it's very annoying to have regexp meta characters
401      * in the time stamp.  Don't use forward slashes, parenthesis,
402      * brackets, asterisks, or other special chars here.
403      */
404 #if defined(HAVE_LOCALTIME_R)
405     ptm = localtime_r(&(entry->tv_sec), &tmBuf);
406 #else
407     ptm = localtime(&(entry->tv_sec));
408 #endif
409     //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
410     strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
411
412     /*
413      * Construct a buffer containing the log header and log message.
414      */
415     size_t prefixLen, suffixLen;
416
417     switch (p_format->format) {
418         case FORMAT_TAG:
419             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
420                 "%c/%-8s: ", priChar, entry->tag);
421             strcpy(suffixBuf, "\n"); suffixLen = 1;
422             break;
423         case FORMAT_PROCESS:
424             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
425                 "%c(%5d) ", priChar, entry->pid);
426             suffixLen = snprintf(suffixBuf, sizeof(suffixBuf),
427                 "  (%s)\n", entry->tag);
428             break;
429         case FORMAT_THREAD:
430             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
431                 "%c(%5d:%5d) ", priChar, entry->pid, entry->tid);
432             strcpy(suffixBuf, "\n");
433             suffixLen = 1;
434             break;
435         case FORMAT_RAW:
436             prefixBuf[0] = 0;
437             prefixLen = 0;
438             strcpy(suffixBuf, "\n");
439             suffixLen = 1;
440             break;
441         case FORMAT_TIME:
442             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
443                 "%s.%03ld %c/%-8s(%5d): ", timeBuf, entry->tv_nsec / 1000000,
444                 priChar, entry->tag, entry->pid);
445             strcpy(suffixBuf, "\n");
446             suffixLen = 1;
447             break;
448         case FORMAT_THREADTIME:
449             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
450                 "%s.%03ld %5d %5d %c %-8s: ", timeBuf, entry->tv_nsec / 1000000,
451                 (int)entry->pid, (int)entry->tid, priChar, entry->tag);
452             strcpy(suffixBuf, "\n");
453             suffixLen = 1;
454             break;
455         case FORMAT_LONG:
456             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
457                 "[ %s.%03ld %5d:%5d %c/%-8s ]\n",
458                 timeBuf, entry->tv_nsec / 1000000, entry->pid,
459                 entry->tid, priChar, entry->tag);
460             strcpy(suffixBuf, "\n\n");
461             suffixLen = 2;
462             prefixSuffixIsHeaderFooter = 1;
463             break;
464         case FORMAT_BRIEF:
465         default:
466             prefixLen = snprintf(prefixBuf, sizeof(prefixBuf),
467                 "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
468             strcpy(suffixBuf, "\n");
469             suffixLen = 1;
470             break;
471     }
472     /* snprintf has a weird return value.   It returns what would have been
473      * written given a large enough buffer.  In the case that the prefix is
474      * longer then our buffer(128), it messes up the calculations below
475      * possibly causing heap corruption.  To avoid this we double check and
476      * set the length at the maximum (size minus null byte)
477      */
478     if(prefixLen >= sizeof(prefixBuf))
479         prefixLen = sizeof(prefixBuf) - 1;
480     if(suffixLen >= sizeof(suffixBuf))
481         suffixLen = sizeof(suffixBuf) - 1;
482
483     /* the following code is tragically unreadable */
484
485     size_t numLines;
486 //    size_t i;
487     char *p;
488     size_t bufferSize;
489     const char *pm;
490
491     if (prefixSuffixIsHeaderFooter) {
492         // we're just wrapping message with a header/footer
493         numLines = 1;
494     } else {
495         pm = entry->message;
496         numLines = 0;
497
498         // The line-end finding here must match the line-end finding
499         // in for ( ... numLines...) loop below
500         while (pm < (entry->message + entry->messageLen)) {
501             if (*pm++ == '\n') numLines++;
502         }
503         // plus one line for anything not newline-terminated at the end
504         if (pm > entry->message && *(pm-1) != '\n') numLines++;
505     }
506
507     // this is an upper bound--newlines in message may be counted
508     // extraneously
509     bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1;
510
511     if (defaultBufferSize >= bufferSize) {
512         ret = defaultBuffer;
513     } else {
514         ret = (char *)malloc(bufferSize);
515
516         if (ret == NULL) {
517             return ret;
518         }
519     }
520
521     ret[0] = '\0';       /* to start strcat off */
522
523     p = ret;
524     pm = entry->message;
525
526     if (prefixSuffixIsHeaderFooter) {
527         strcat(p, prefixBuf);
528 //        strncat(p, prefixBuf, sizeof(prefixBuf));
529         p += prefixLen;
530         strncat(p, entry->message, entry->messageLen);
531         p += entry->messageLen;
532         strcat(p, suffixBuf);
533 //        strncat(p, suffixBuf, sizeof(suffixBuf));
534         p += suffixLen;
535     } else {
536         while(pm < (entry->message + entry->messageLen)) {
537             const char *lineStart;
538             size_t lineLen;
539
540             lineStart = pm;
541
542             // Find the next end-of-line in message
543             while (pm < (entry->message + entry->messageLen)
544                     && *pm != '\n') pm++;
545             lineLen = pm - lineStart;
546
547             strcat(p, prefixBuf);
548             //strncat(p, prefixBuf, sizeof(prefixBuf));
549             p += prefixLen;
550             strncat(p, lineStart, lineLen);
551             p += lineLen;
552             strcat(p, suffixBuf);
553             //strncat(p, suffixBuf, sizeof(suffixBuf));
554             p += suffixLen;
555
556             if (*pm == '\n') pm++;
557         }
558     }
559
560     if (p_outLength != NULL) {
561         *p_outLength = p - ret;
562     }
563
564     return ret;
565 }
566
567 /**
568  * Either print or do not print log line, based on filter
569  *
570  * Returns count bytes written
571  */
572
573 int log_print_log_line(
574     log_format *p_format,
575     int fd,
576     const log_entry *entry)
577 {
578         int ret;
579         char defaultBuffer[512];
580         char *outBuffer = NULL;
581         size_t totalLen;
582
583         outBuffer = log_format_log_line(p_format, defaultBuffer,sizeof(defaultBuffer), entry, &totalLen);
584
585         if (!outBuffer)
586                 return -1;
587
588         do {
589                 ret = write(fd, outBuffer, totalLen);
590         } while (ret < 0 && errno == EINTR);
591
592         if (ret < 0) {
593                 fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno);
594                 ret = 0;
595                 goto done;
596         }
597
598         if (((size_t)ret) < totalLen) {
599                 fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret,
600                 (int)totalLen);
601                 goto done;
602         }
603
604 done:
605         if (outBuffer != defaultBuffer) {
606                 free(outBuffer);
607         }
608
609     return ret;
610 }
611
612
613
614 void logprint_run_tests()
615 {
616 #if 0
617
618     fprintf(stderr, "tests disabled\n");
619
620 #else
621
622     int err;
623     const char *tag;
624     log_format *p_format;
625
626     p_format = log_format_new();
627
628     fprintf(stderr, "running tests\n");
629
630     tag = "random";
631
632     log_add_filter_rule(p_format,"*:i");
633
634     assert (DLOG_INFO == filter_pri_for_tag(p_format, "random"));
635     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0);
636     log_add_filter_rule(p_format, "*");
637     assert (DLOG_DEBUG == filter_pri_for_tag(p_format, "random"));
638     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0);
639     log_add_filter_rule(p_format, "*:v");
640     assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random"));
641     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0);
642     log_add_filter_rule(p_format, "*:i");
643     assert (DLOG_INFO == filter_pri_for_tag(p_format, "random"));
644     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0);
645
646     log_add_filter_rule(p_format, "random");
647     assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random"));
648     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0);
649     log_add_filter_rule(p_format, "random:v");
650     assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random"));
651     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0);
652     log_add_filter_rule(p_format, "random:d");
653     assert (DLOG_DEBUG == filter_pri_for_tag(p_format, "random"));
654     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0);
655     log_add_filter_rule(p_format, "random:w");
656     assert (DLOG_WARN == filter_pri_for_tag(p_format, "random"));
657     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0);
658
659     log_add_filter_rule(p_format, "crap:*");
660     assert (DLOG_VERBOSE== filter_pri_for_tag(p_format, "crap"));
661     assert(log_should_print_line(p_format, "crap", DLOG_VERBOSE) > 0);
662
663     // invalid expression
664     err = log_add_filter_rule(p_format, "random:z");
665     assert (err < 0);
666     assert (DLOG_WARN == filter_pri_for_tag(p_format, "random"));
667     assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0);
668
669     // Issue #550946
670     err = log_add_filter_string(p_format, " ");
671     assert(err == 0);
672     assert(DLOG_WARN == filter_pri_for_tag(p_format, "random"));
673
674     // note trailing space
675     err = log_add_filter_string(p_format, "*:s random:d ");
676     assert(err == 0);
677     assert(DLOG_DEBUG == filter_pri_for_tag(p_format, "random"));
678
679     err = log_add_filter_string(p_format, "*:s random:z");
680     assert(err < 0);
681
682     log_format_free(p_format);
683
684
685 #if 0
686     char *ret;
687     char defaultBuffer[512];
688
689     ret = log_formatLogLine(p_format,
690         defaultBuffer, sizeof(defaultBuffer), 0, DLOG_ERROR, 123,
691         123, 123, "random", "nofile", strlen("Hello"), "Hello", NULL);
692 #endif
693
694
695     fprintf(stderr, "tests complete\n");
696 #endif
697 }
698