/* * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file picodbg.c * * Provides functions and macros to debug the Pico system and to trace * the execution of its code. * * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland * All rights reserved. * * History: * - 2009-04-20 -- initial version */ #ifdef __cplusplus extern "C" { #endif #if 0 } #endif #if defined(PICO_DEBUG) /* Two variants of colored console output are implemented: COLOR_MODE_WINDOWS uses the Windows API function SetConsoleTextAttribute COLOR_MODE_ANSI uses ANSI escape codes */ #if defined(_WIN32) #define COLOR_MODE_WINDOWS #else #define COLOR_MODE_ANSI #endif #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include "picodbg.h" /* Maximum length of a formatted tracing message */ #define MAX_MESSAGE_LEN 999 /* Maximum length of contextual information */ #define MAX_CONTEXT_LEN 499 /* Maximum length of filename filter */ #define MAX_FILTERFN_LEN 16 /* Delimiter used in debug messages */ #define MSG_DELIM "|" /* Standard output file for debug messages */ #define STDDBG stdout /* or stderr */ /* Default setup */ #define PICODBG_DEFAULT_LEVEL PICODBG_LOG_LEVEL_WARN #define PICODBG_DEFAULT_FILTERFN "" #define PICODBG_DEFAULT_FORMAT \ (PICODBG_SHOW_LEVEL | PICODBG_SHOW_SRCNAME | PICODBG_SHOW_FUNCTION) #define PICODBG_DEFAULT_COLOR 1 /* Current log level */ static int logLevel = PICODBG_DEFAULT_LEVEL; /* Current log filter (filename) */ static char logFilterFN[MAX_FILTERFN_LEN + 1]; /* Current log file or NULL if no log file is set */ static FILE *logFile = NULL; /* Current output format */ static int logFormat = PICODBG_DEFAULT_FORMAT; /* Color mode for console output (0 : disable colors, != 0 : enable colors */ static int optColor = 0; /* Buffer for context information */ static char ctxbuf[MAX_CONTEXT_LEN + 1]; /* Buffer to format tracing messages */ static char msgbuf[MAX_MESSAGE_LEN + 1]; /* *** Support for colored text output to console *****/ /* Console text colors */ enum color_t { /* order matches Windows color codes */ ColorBlack, ColorBlue, ColorGreen, ColorCyan, ColorRed, ColorPurple, ColorBrown, ColorLightGray, ColorDarkGray, ColorLightBlue, ColorLightGreen, ColorLightCyan, ColorLightRed, ColorLightPurple, ColorYellow, ColorWhite }; static enum color_t picodbg_getLevelColor(int level) { switch (level) { case PICODBG_LOG_LEVEL_ERROR: return ColorLightRed; case PICODBG_LOG_LEVEL_WARN : return ColorYellow; case PICODBG_LOG_LEVEL_INFO : return ColorGreen; case PICODBG_LOG_LEVEL_DEBUG: return ColorLightGray; case PICODBG_LOG_LEVEL_TRACE: return ColorDarkGray; } return ColorWhite; } #if defined(COLOR_MODE_WINDOWS) #define WIN32_LEAN_AND_MEAN #include <windows.h> static int picodbg_setTextAttr(FILE *stream, int attr) { HANDLE hConsole; if (stream == stdout) { hConsole = GetStdHandle(STD_OUTPUT_HANDLE); } else if (stream == stderr) { hConsole = GetStdHandle(STD_ERROR_HANDLE); } else { hConsole = INVALID_HANDLE_VALUE; } if (hConsole != INVALID_HANDLE_VALUE) { /* do nothing if console output is redirected to a file */ if (GetFileType(hConsole) == FILE_TYPE_CHAR) { CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(hConsole, &csbi); SetConsoleTextAttribute(hConsole, (WORD) attr); return (int) csbi.wAttributes; } } return 0; } #elif defined(COLOR_MODE_ANSI) static int picodbg_setTextAttr(FILE *stream, int attr) { const char *c = ""; if (attr == -1) { c = "0"; } else switch (attr) { case ColorBlack: c = "0;30"; break; case ColorRed: c = "0;31"; break; case ColorGreen: c = "0;32"; break; case ColorBrown: c = "0;33"; break; case ColorBlue: c = "0;34"; break; case ColorPurple: c = "0;35"; break; case ColorCyan: c = "0;36"; break; case ColorLightGray: c = "0;37"; break; case ColorDarkGray: c = "1;30"; break; case ColorLightRed: c = "1;31"; break; case ColorLightGreen: c = "1;32"; break; case ColorYellow: c = "1;33"; break; case ColorLightBlue: c = "1;34"; break; case ColorLightPurple: c = "1;35"; break; case ColorLightCyan: c = "1;36"; break; case ColorWhite: c = "1;37"; break; } fprintf(stream, "\x1b[%sm", c); return -1; } #else static int picodbg_setTextAttr(FILE *stream, int attr) { /* avoid 'unreferenced formal parameter' */ (void) stream; (void) attr; return 0; } #endif /* *** Auxiliary routines *****/ static const char *picodbg_fileTitle(const char *file) { const char *name = file, *str = file; /* try to extract file name without path in a platform independent way, i.e., skip all chars preceding path separator chars like '/' (Unix, MacOSX), '\' (Windows, DOS), and ':' (MacOS9) */ while (*str) { if ((*str == '\\') || (*str == '/') || (*str == ':')) { name = str + 1; } str++; } return name; } static void picodbg_logToStream(int level, int donewline, const char *context, const char *msg) { int oldAttr = 0; if (optColor) { oldAttr = picodbg_setTextAttr(STDDBG, picodbg_getLevelColor(level)); } fprintf(STDDBG, "%s%s", context, msg); if (donewline) fprintf(STDDBG, "\n"); if (logFile != NULL) { fprintf(logFile, "%s%s", context, msg); if (donewline) fprintf(logFile, "\n"); } if (optColor) { picodbg_setTextAttr(STDDBG, oldAttr); } } /* *** Exported routines *****/ void picodbg_initialize(int level) { logLevel = level; strcpy(logFilterFN, PICODBG_DEFAULT_FILTERFN); logFile = NULL; logFormat = PICODBG_DEFAULT_FORMAT; optColor = PICODBG_DEFAULT_COLOR; PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE); } void picodbg_terminate() { if (logFile != NULL) { fclose(logFile); } logLevel = 0; logFile = NULL; } void picodbg_setLogLevel(int level) { PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE); logLevel = level; } void picodbg_setLogFilterFN(const char *name) { strcpy(logFilterFN, name); } void picodbg_setLogFile(const char *name) { if (logFile != NULL) { fclose(logFile); } if ((name != NULL) && (strlen(name) > 0)) { logFile = fopen(name, "wt"); } else { logFile = NULL; } } void picodbg_enableColors(int flag) { optColor = (flag != 0); } void picodbg_setOutputFormat(unsigned int format) { logFormat = format; } const char *picodbg_varargs(const char *format, ...) { int len; va_list argptr; va_start(argptr, format); len = vsprintf(msgbuf, format, argptr); PICODBG_ASSERT_RANGE(len, 0, MAX_MESSAGE_LEN); return msgbuf; } void picodbg_log(int level, int donewline, const char *file, int line, const char *func, const char *msg) { char cb[MAX_CONTEXT_LEN + 1]; PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE); if ((level <= logLevel) && ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) { /* compose output format string */ strcpy(ctxbuf, "*** "); if (logFormat & PICODBG_SHOW_LEVEL) { switch (level) { case PICODBG_LOG_LEVEL_ERROR: strcat(ctxbuf, "error" MSG_DELIM); break; case PICODBG_LOG_LEVEL_WARN: strcat(ctxbuf, "warn " MSG_DELIM); break; case PICODBG_LOG_LEVEL_INFO: strcat(ctxbuf, "info " MSG_DELIM); break; case PICODBG_LOG_LEVEL_DEBUG: strcat(ctxbuf, "debug" MSG_DELIM); break; case PICODBG_LOG_LEVEL_TRACE: strcat(ctxbuf, "trace" MSG_DELIM); break; default: break; } } if (logFormat & PICODBG_SHOW_DATE) { /* nyi */ } if (logFormat & PICODBG_SHOW_TIME) { /* nyi */ } if (logFormat & PICODBG_SHOW_SRCNAME) { sprintf(cb, "%-10s", picodbg_fileTitle(file)); strcat(ctxbuf, cb); if (logFormat & PICODBG_SHOW_SRCLINE) { sprintf(cb, "(%d)", line); strcat(ctxbuf, cb); } strcat(ctxbuf, MSG_DELIM); } if (logFormat & PICODBG_SHOW_FUNCTION) { if (strlen(func) > 0) { sprintf(cb, "%-18s", func); strcat(ctxbuf, cb); strcat(ctxbuf, MSG_DELIM); } } picodbg_logToStream(level, donewline, ctxbuf, msg); } } void picodbg_log_msg(int level, const char *file, const char *msg) { PICODBG_ASSERT_RANGE(level, 0, PICODBG_LOG_LEVEL_TRACE); if ((level <= logLevel) && ((strlen(logFilterFN) == 0) || !strcmp(logFilterFN, picodbg_fileTitle(file)))) { picodbg_logToStream(level, 0, "", msg); } } void picodbg_assert(const char *file, int line, const char *func, const char *expr) { if (strlen(func) > 0) { fprintf(STDDBG, "assertion failed: %s, file %s, function %s, line %d", expr, picodbg_fileTitle(file), func, line); } else { fprintf(STDDBG, "assertion failed: %s, file %s, line %d", expr, picodbg_fileTitle(file), line); } picodbg_terminate(); abort(); } #else /* To prevent warning about "translation unit is empty" when diagnostic output is disabled. */ static void picodbg_dummy(void) { picodbg_dummy(); } #endif /* defined(PICO_DEBUG) */ #ifdef __cplusplus } #endif /* end */