/*(LGPL) --------------------------------------------------------------------------- logger.c - Simple logger with redirection --------------------------------------------------------------------------- * Copyright (C) 2003, David Olofson * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "config.h" #include "logger.h" #include "glSDL.h" #define LOG_BUFFER 1024 static int i__last; #define for_one_or_all(iterator, index, items) \ if(index < LOG_TARGETS) \ { \ if(index >= 0) \ iterator = i__last = index; \ else \ { \ iterator = 0; \ i__last = items - 1; \ } \ } \ else \ { \ iterator = 0; \ i__last = -10; \ } \ --iterator; \ while(++iterator <= i__last) typedef struct { /* Stream output */ int use_stream; FILE *stream; /* Callback output */ int handle; int (*callback)(int handle, const char *data); /* Formatting control */ unsigned flags; int written; } LOG_target; typedef struct { /* Output */ int target; /* Atributes */ unsigned attr; } LOG_level; static LOG_level *l_levels = NULL; static LOG_target *l_targets = NULL; static char *l_buffer = NULL; Uint32 start_time; /*-------------------------------------------------------------------- Low level internal stuff --------------------------------------------------------------------*/ /* Write stuff without any translation */ static inline int log_write_raw(int target, const char *text) { if(l_targets[target].use_stream) return fputs(text, l_targets[target].stream); else if(l_targets[target].callback) return l_targets[target].callback( l_targets[target].handle, text); else { /* We should never get here. */ assert(0); } } /* Write log file header if applicable. */ static inline void check_header(int target) { if(l_targets[target].written) return; if(l_targets[target].flags & LOG_HTML) { log_write_raw(target, "\n"); log_write_raw(target, "\n\t\n"); log_write_raw(target, "\t\tLog File\n"); log_write_raw(target, "\t\t\n"); log_write_raw(target, "\t\n"); log_write_raw(target, "\t\n"); } l_targets[target].written = 1; } /* Write log file footer if applicable. */ static inline void check_footer(int target) { if(!l_targets[target].written) return; if(l_targets[target].flags & LOG_HTML) log_write_raw(target, "\t\n\n"); } /* Write converting/escaping any reserved characters as needed */ static inline int log_write(int target, const char *text) { int i; if(l_targets[target].flags & LOG_HTML) { /* FIXME: Optimize this a little, maybe. :-) */ char buf[2] = "\0\0"; int last = -1; for(i = 0; text[i]; ++i) { buf[0] = text[i]; switch(text[i]) { case '\n': log_write_raw(target, "
\n"); break; case ' ': if(' ' == last) log_write_raw(target, " "); else log_write_raw(target, " "); break; case '<': log_write_raw(target, "<"); break; case '>': log_write_raw(target, ">"); break; case '&': log_write_raw(target, "&"); break; default: log_write_raw(target, buf); break; } last = text[i]; } return i; } else return log_write_raw(target, text); } static inline void put_timestamp(int level) { if(l_targets[l_levels[level].target].flags & LOG_TIMESTAMP) { char buf[16]; snprintf(buf, sizeof(buf)-1, "[%d] ", SDL_GetTicks() - start_time); log_write_raw(l_levels[level].target, buf); } } #define ANSI_SUPPORTED_FLAGS (LOG_COLORS | LOG_BRIGHT | LOG_STRONG | LOG_BLINK) #define HTML_SUPPORTED_FLAGS (LOG_COLORS | LOG_BRIGHT | LOG_STRONG | LOG_BLINK) static inline void set_attr(int level) { unsigned a = l_levels[level].attr; if(l_targets[l_levels[level].target].flags & LOG_ANSI) { #define SEP if('[' != buf[pos - 1]) buf[pos++] = ';' int pos = 0; char buf[24]; if(!(a & ANSI_SUPPORTED_FLAGS)) return; buf[pos++] = '\033'; buf[pos++] = '['; if(a & LOG_COLORS) { buf[pos++] = '3'; buf[pos++] = '0' + (a & LOG_COLORS) - 1; } if(a & LOG_BRIGHT) { SEP; buf[pos++] = '1'; } if(a & LOG_STRONG) { SEP; buf[pos++] = '4'; } if(a & LOG_BLINK) { SEP; buf[pos++] = '5'; } buf[pos++] = 'm'; buf[pos++] = '\0'; log_write_raw(l_levels[level].target, buf); #undef SEP } else if(l_targets[l_levels[level].target].flags & LOG_HTML) { if(a & LOG_COLORS) { const char *sel; if(a & LOG_BRIGHT) switch( (a & LOG_COLORS) - 1) { case 0: sel = ""; break; case 1: sel = ""; break; case 2: sel = ""; break; case 3: sel = ""; break; case 4: sel = ""; break; case 5: sel = ""; break; case 6: sel = ""; break; case 7: default: sel = ""; break; } else switch( (a & LOG_COLORS) - 1) { case 0: sel = ""; break; case 1: sel = ""; break; case 2: sel = ""; break; case 3: sel = ""; break; case 4: sel = ""; break; case 5: sel = ""; break; case 6: sel = ""; break; case 7: default: sel = ""; break; } log_write_raw(l_levels[level].target, sel); } if(a & LOG_STRONG) log_write_raw(l_levels[level].target, ""); if(a & LOG_BLINK) log_write_raw(l_levels[level].target, ""); } } static inline void reset_attr(int level) { unsigned a = l_levels[level].attr; if(l_targets[l_levels[level].target].flags & LOG_ANSI) { if(!(a & ANSI_SUPPORTED_FLAGS)) return; log_write_raw(l_levels[level].target, "\033[0m"); } else if(l_targets[l_levels[level].target].flags & LOG_HTML) { if(a & LOG_BLINK) log_write_raw(l_levels[level].target, ""); if(a & LOG_STRONG) log_write_raw(l_levels[level].target, ""); if(a & LOG_COLORS) log_write_raw(l_levels[level].target, ""); } } static inline int is_active(int level) { LOG_target *t = &l_targets[l_levels[level].target]; return (t->use_stream || t->callback); } /*-------------------------------------------------------------------- API entry points --------------------------------------------------------------------*/ int log_open(void) { if(l_levels) return 0; l_levels = calloc(LOG_LEVELS, sizeof(LOG_level)); if(!l_levels) return -1; l_targets = calloc(LOG_TARGETS, sizeof(LOG_target)); if(!l_targets) { log_close(); return -2; } l_buffer = calloc(1, LOG_BUFFER); if(!l_buffer) { log_close(); return -3; } log_set_target_stream(-1, stdout); log_set_target_flags(-1, 0); log_set_level_target(-1, 0); log_set_level_attr(-1, LOG_NOCOLOR); start_time = SDL_GetTicks(); return 0; } #define CHECK_INIT (l_levels ? 0 : -1) void log_close(void) { int i; if(CHECK_INIT < 0) return; for(i = 0; i < LOG_TARGETS; ++i) check_footer(i); free(l_targets); l_targets = NULL; free(l_levels); l_levels = NULL; free(l_buffer); l_buffer = NULL; } void log_set_target_stream(int target, FILE *stream) { int i; if(CHECK_INIT < 0) return; for_one_or_all(i, target, LOG_TARGETS) { l_targets[i].callback = NULL; l_targets[i].stream = stream; l_targets[i].use_stream = (stream != NULL); } } void log_set_target_callback(int target, int (*callback)(int handle, const char *data), int handle) { int i; if(CHECK_INIT < 0) return; for_one_or_all(i, target, LOG_TARGETS) { l_targets[i].use_stream = 0; l_targets[i].callback = callback; l_targets[i].handle = handle; } } void log_set_target_flags(int target, unsigned flags) { int i; if(CHECK_INIT < 0) return; for_one_or_all(i, target, LOG_TARGETS) l_targets[i].flags = flags; } void log_set_level_target(int level, int target) { int i; if(CHECK_INIT < 0) return; for_one_or_all(i, level, LOG_LEVELS) l_levels[i].target = target; } void log_set_level_attr(int level, unsigned attr) { int i; if(CHECK_INIT < 0) return; for_one_or_all(i, level, LOG_LEVELS) l_levels[i].attr = attr; } int log_puts(int level, const char *text) { int result, result2; if(CHECK_INIT < 0) return -1; if(level < 0 || level >= LOG_LEVELS) return -2; if(!is_active(level)) return 0; check_header(l_levels[level].target); set_attr(level); put_timestamp(level); result = log_write(l_levels[level].target, text); if(result >= 0) { result2 = log_write_raw(l_levels[level].target, "\n"); if(result2 >= 0) result += result2; } reset_attr(level); return result; } int log_printf(int level, const char *format, ...) { va_list args; int result; if(CHECK_INIT < 0) return -1; if(level < 0 || level >= LOG_LEVELS) return -2; if(!is_active(level)) return 0; check_header(l_levels[level].target); set_attr(level); put_timestamp(level); va_start(args, format); result = vsnprintf(l_buffer, LOG_BUFFER-1, format, args); va_end(args); if(result >= 0) result = log_write(l_levels[level].target, l_buffer); reset_attr(level); return result; }