1/*
2 * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16/**
17 * @file picodbg.c
18 *
19 * Provides functions and macros to debug the Pico system and to trace
20 * the execution of its code.
21 *
22 * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
23 * All rights reserved.
24 *
25 * History:
26 * - 2009-04-20 -- initial version
27 */
28
29#ifdef __cplusplus
30extern "C" {
31#endif
32#if 0
33}
34#endif
35
36
37#if defined(PICO_DEBUG)
38
39/* Two variants of colored console output are implemented:
40   COLOR_MODE_WINDOWS
41      uses the Windows API function SetConsoleTextAttribute
42   COLOR_MODE_ANSI
43      uses ANSI escape codes */
44#if defined(_WIN32)
45#define COLOR_MODE_WINDOWS
46#else
47#define COLOR_MODE_ANSI
48#endif
49
50
51#include <stdio.h>
52#include <stdlib.h>
53
54#include <stdarg.h>
55#include <string.h>
56
57#include "picodbg.h"
58
59
60/* Maximum length of a formatted tracing message */
61#define MAX_MESSAGE_LEN         999
62
63/* Maximum length of contextual information */
64#define MAX_CONTEXT_LEN         499
65
66/* Maximum length of filename filter */
67#define MAX_FILTERFN_LEN         16
68
69/* Delimiter used in debug messages */
70#define MSG_DELIM               "|"
71
72/* Standard output file for debug messages */
73#define STDDBG                  stdout /* or stderr */
74
75/* Default setup */
76#define PICODBG_DEFAULT_LEVEL   PICODBG_LOG_LEVEL_WARN
77#define PICODBG_DEFAULT_FILTERFN   ""
78#define PICODBG_DEFAULT_FORMAT  \
79    (PICODBG_SHOW_LEVEL | PICODBG_SHOW_SRCNAME | PICODBG_SHOW_FUNCTION)
80#define PICODBG_DEFAULT_COLOR   1
81
82
83/* Current log level */
84static int logLevel = PICODBG_DEFAULT_LEVEL;
85
86/* Current log filter (filename) */
87static char logFilterFN[MAX_FILTERFN_LEN + 1];
88
89/* Current log file or NULL if no log file is set */
90static FILE *logFile = NULL;
91
92/* Current output format */
93static int logFormat = PICODBG_DEFAULT_FORMAT;
94
95/* Color mode for console output (0 : disable colors, != 0 : enable colors */
96static int optColor = 0;
97
98/* Buffer for context information */
99static char ctxbuf[MAX_CONTEXT_LEN + 1];
100
101/* Buffer to format tracing messages */
102static char msgbuf[MAX_MESSAGE_LEN + 1];
103
104
105/* *** Support for colored text output to console *****/
106
107
108/* Console text colors */
109enum color_t {
110    /* order matches Windows color codes */
111    ColorBlack,
112    ColorBlue,
113    ColorGreen,
114    ColorCyan,
115    ColorRed,
116    ColorPurple,
117    ColorBrown,
118    ColorLightGray,
119    ColorDarkGray,
120    ColorLightBlue,
121    ColorLightGreen,
122    ColorLightCyan,
123    ColorLightRed,
124    ColorLightPurple,
125    ColorYellow,
126    ColorWhite
127};
128
129
130static enum color_t picodbg_getLevelColor(int level)
131{
132    switch (level) {
133        case PICODBG_LOG_LEVEL_ERROR: return ColorLightRed;
134        case PICODBG_LOG_LEVEL_WARN : return ColorYellow;
135        case PICODBG_LOG_LEVEL_INFO : return ColorGreen;
136        case PICODBG_LOG_LEVEL_DEBUG: return ColorLightGray;
137        case PICODBG_LOG_LEVEL_TRACE: return ColorDarkGray;
138    }
139    return ColorWhite;
140}
141
142
143#if defined(COLOR_MODE_WINDOWS)
144
145#define WIN32_LEAN_AND_MEAN
146#include <windows.h>
147
148static int picodbg_setTextAttr(FILE *stream, int attr)
149{
150    HANDLE hConsole;
151
152    if (stream == stdout) {
153        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
154    } else if (stream == stderr) {
155        hConsole = GetStdHandle(STD_ERROR_HANDLE);
156    } else {
157        hConsole = INVALID_HANDLE_VALUE;
158    }
159
160    if (hConsole != INVALID_HANDLE_VALUE) {
161        /* do nothing if console output is redirected to a file */
162        if (GetFileType(hConsole) == FILE_TYPE_CHAR) {
163            CONSOLE_SCREEN_BUFFER_INFO csbi;
164            GetConsoleScreenBufferInfo(hConsole, &csbi);
165            SetConsoleTextAttribute(hConsole, (WORD) attr);
166            return (int) csbi.wAttributes;
167        }
168    }
169
170    return 0;
171}
172
173#elif defined(COLOR_MODE_ANSI)
174
175static int picodbg_setTextAttr(FILE *stream, int attr)
176{
177    const char *c = "";
178
179    if (attr == -1) {
180        c = "0";
181    } else switch (attr) {
182        case ColorBlack:       c = "0;30"; break;
183        case ColorRed:         c = "0;31"; break;
184        case ColorGreen:       c = "0;32"; break;
185        case ColorBrown:       c = "0;33"; break;
186        case ColorBlue:        c = "0;34"; break;
187        case ColorPurple:      c = "0;35"; break;
188        case ColorCyan:        c = "0;36"; break;
189        case ColorLightGray:   c = "0;37"; break;
190        case ColorDarkGray:    c = "1;30"; break;
191        case ColorLightRed:    c = "1;31"; break;
192        case ColorLightGreen:  c = "1;32"; break;
193        case ColorYellow:      c = "1;33"; break;
194        case ColorLightBlue:   c = "1;34"; break;
195        case ColorLightPurple: c = "1;35"; break;
196        case ColorLightCyan:   c = "1;36"; break;
197        case ColorWhite:       c = "1;37"; break;
198    }
199
200    fprintf(stream, "\x1b[%sm", c);
201    return -1;
202}
203
204#else
205
206static int picodbg_setTextAttr(FILE *stream, int attr)
207{
208    /* avoid 'unreferenced formal parameter' */
209    (void) stream;
210    (void) attr;
211    return 0;
212}
213
214#endif
215
216
217/* *** Auxiliary routines *****/
218
219
220static const char *picodbg_fileTitle(const char *file)
221{
222    const char *name = file, *str = file;
223
224    /* try to extract file name without path in a platform independent
225       way, i.e., skip all chars preceding path separator chars like
226       '/' (Unix, MacOSX), '\' (Windows, DOS), and ':' (MacOS9) */
227    while (*str) {
228        if ((*str == '\\') || (*str == '/') || (*str == ':')) {
229            name = str + 1;
230        }
231        str++;
232    }
233
234    return name;
235}
236
237
238static void picodbg_logToStream(int level, int donewline,
239                                const char *context, const char *msg)
240{
241    int oldAttr = 0;
242
243    if (optColor) {
244        oldAttr = picodbg_setTextAttr(STDDBG, picodbg_getLevelColor(level));
245    }
246
247    fprintf(STDDBG, "%s%s", context, msg);
248    if (donewline) fprintf(STDDBG, "\n");
249    if (logFile != NULL) {
250        fprintf(logFile, "%s%s", context, msg);
251        if (donewline) fprintf(logFile, "\n");
252    }
253
254    if (optColor) {
255        picodbg_setTextAttr(STDDBG, oldAttr);
256    }
257}
258
259
260/* *** Exported routines *****/
261
262
263void picodbg_initialize(int level)
264{
265    logLevel  = level;
266    strcpy(logFilterFN, PICODBG_DEFAULT_FILTERFN);
267    logFile   = NULL;
268    logFormat = PICODBG_DEFAULT_FORMAT;
269    optColor  = PICODBG_DEFAULT_COLOR;
270    PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
271}
272
273
274void picodbg_terminate()
275{
276    if (logFile != NULL) {
277        fclose(logFile);
278    }
279
280    logLevel = 0;
281    logFile  = NULL;
282}
283
284
285void picodbg_setLogLevel(int level)
286{
287    PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
288    logLevel = level;
289}
290
291
292void picodbg_setLogFilterFN(const char *name)
293{
294    strcpy(logFilterFN, name);
295}
296
297
298void picodbg_setLogFile(const char *name)
299{
300    if (logFile != NULL) {
301        fclose(logFile);
302    }
303
304    if ((name != NULL) && (strlen(name) > 0)) {
305        logFile = fopen(name, "wt");
306    } else {
307        logFile = NULL;
308    }
309}
310
311
312void picodbg_enableColors(int flag)
313{
314    optColor = (flag != 0);
315}
316
317
318void picodbg_setOutputFormat(unsigned int format)
319{
320    logFormat = format;
321}
322
323
324const char *picodbg_varargs(const char *format, ...)
325{
326    int len;
327
328    va_list argptr;
329    va_start(argptr, format);
330
331    len = vsprintf(msgbuf, format, argptr);
332    PICODBG_ASSERT_RANGE(len, 0, MAX_MESSAGE_LEN);
333
334    return msgbuf;
335}
336
337
338void picodbg_log(int level, int donewline, const char *file, int line,
339                 const char *func, const char *msg)
340{
341    char cb[MAX_CONTEXT_LEN + 1];
342
343    PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
344
345    if ((level <= logLevel) &&
346        ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
347        /* compose output format string */
348        strcpy(ctxbuf, "*** ");
349        if (logFormat & PICODBG_SHOW_LEVEL) {
350            switch (level) {
351                case PICODBG_LOG_LEVEL_ERROR:
352                    strcat(ctxbuf, "error" MSG_DELIM);
353                    break;
354                case PICODBG_LOG_LEVEL_WARN:
355                    strcat(ctxbuf, "warn " MSG_DELIM);
356                    break;
357                case PICODBG_LOG_LEVEL_INFO:
358                    strcat(ctxbuf, "info " MSG_DELIM);
359                    break;
360                case PICODBG_LOG_LEVEL_DEBUG:
361                    strcat(ctxbuf, "debug" MSG_DELIM);
362                    break;
363                case PICODBG_LOG_LEVEL_TRACE:
364                    strcat(ctxbuf, "trace" MSG_DELIM);
365                    break;
366                default:
367                    break;
368            }
369        }
370        if (logFormat & PICODBG_SHOW_DATE) {
371            /* nyi */
372        }
373        if (logFormat & PICODBG_SHOW_TIME) {
374            /* nyi */
375        }
376        if (logFormat & PICODBG_SHOW_SRCNAME) {
377            sprintf(cb, "%-10s", picodbg_fileTitle(file));
378            strcat(ctxbuf, cb);
379            if (logFormat & PICODBG_SHOW_SRCLINE) {
380                sprintf(cb, "(%d)", line);
381                strcat(ctxbuf, cb);
382            }
383            strcat(ctxbuf, MSG_DELIM);
384        }
385        if (logFormat & PICODBG_SHOW_FUNCTION) {
386            if (strlen(func) > 0) {
387                sprintf(cb, "%-18s", func);
388                strcat(ctxbuf, cb);
389                strcat(ctxbuf, MSG_DELIM);
390            }
391        }
392
393        picodbg_logToStream(level, donewline, ctxbuf, msg);
394    }
395}
396
397
398void picodbg_log_msg(int level, const char *file, const char *msg)
399{
400    PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE);
401
402    if ((level <= logLevel) &&
403        ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) {
404        picodbg_logToStream(level, 0, "", msg);
405    }
406}
407
408
409void picodbg_assert(const char *file, int line, const char *func, const char *expr)
410{
411    if (strlen(func) > 0) {
412        fprintf(STDDBG, "assertion failed: %s, file %s, function %s, line %d",
413            expr, picodbg_fileTitle(file), func, line);
414    } else {
415        fprintf(STDDBG, "assertion failed: %s, file %s, line %d",
416            expr, picodbg_fileTitle(file), line);
417    }
418    picodbg_terminate();
419    abort();
420}
421
422
423#else
424
425/* To prevent warning about "translation unit is empty" when
426   diagnostic output is disabled. */
427static void picodbg_dummy(void) {
428    picodbg_dummy();
429}
430
431#endif /* defined(PICO_DEBUG) */
432
433#ifdef __cplusplus
434}
435#endif
436
437
438/* end */
439