- logger.c:103 log_write_raw() missing return in unreachable assert(0) branch; clang flagged the missing return statement. - logger.c:191 SDL_GetTicks() now returns Uint64 in SDL 3; widen the printf format from %d to %llu and bump the local buffer size. Build is clean. Remaining warnings (strncat-size in a_wave.c, invalid-source-encoding in a_midifile.c) are pre-2007 code-style issues unrelated to SDL. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
487 lines
10 KiB
C
487 lines
10 KiB
C
/*(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 <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
#include "config.h"
|
|
#include "logger.h"
|
|
#include "sdl_compat.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);
|
|
}
|
|
return 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, "<!DOCTYPE HTML PUBLIC "
|
|
"\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
|
|
"\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n");
|
|
log_write_raw(target, "<HTML>\n\t<HEAD>\n");
|
|
log_write_raw(target, "\t\t<TITLE>Log File</TITLE>\n");
|
|
log_write_raw(target, "\t\t<meta http-equiv=\"Content-Type\" "
|
|
"content=\"text/html; charset=ISO-8859-1\">\n");
|
|
log_write_raw(target, "\t</HEAD>\n");
|
|
log_write_raw(target, "\t<BODY BGCOLOR=#000000 TEXT=#999999>\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</BODY>\n</HTML>\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, "<br>\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[24];
|
|
snprintf(buf, sizeof(buf)-1, "[%llu] ",
|
|
(unsigned long long)(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 = "<FONT COLOR=#333333>"; break;
|
|
case 1: sel = "<FONT COLOR=#990000>"; break;
|
|
case 2: sel = "<FONT COLOR=#009900>"; break;
|
|
case 3: sel = "<FONT COLOR=#999900>"; break;
|
|
case 4: sel = "<FONT COLOR=#000099>"; break;
|
|
case 5: sel = "<FONT COLOR=#990099>"; break;
|
|
case 6: sel = "<FONT COLOR=#009999>"; break;
|
|
case 7:
|
|
default:
|
|
sel = "<FONT COLOR=#999999>"; break;
|
|
}
|
|
else
|
|
switch( (a & LOG_COLORS) - 1)
|
|
{
|
|
case 0: sel = "<FONT COLOR=#666666>"; break;
|
|
case 1: sel = "<FONT COLOR=#ff0000>"; break;
|
|
case 2: sel = "<FONT COLOR=#00ff00>"; break;
|
|
case 3: sel = "<FONT COLOR=#ffff00>"; break;
|
|
case 4: sel = "<FONT COLOR=#0000ff>"; break;
|
|
case 5: sel = "<FONT COLOR=#ff00ff>"; break;
|
|
case 6: sel = "<FONT COLOR=#00ffff>"; break;
|
|
case 7:
|
|
default:
|
|
sel = "<FONT COLOR=#ffffff>"; break;
|
|
}
|
|
log_write_raw(l_levels[level].target, sel);
|
|
}
|
|
if(a & LOG_STRONG)
|
|
log_write_raw(l_levels[level].target, "<STRONG>");
|
|
if(a & LOG_BLINK)
|
|
log_write_raw(l_levels[level].target, "<BLINK>");
|
|
}
|
|
}
|
|
|
|
|
|
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, "</BLINK>");
|
|
if(a & LOG_STRONG)
|
|
log_write_raw(l_levels[level].target, "</STRONG>");
|
|
if(a & LOG_COLORS)
|
|
log_write_raw(l_levels[level].target, "</FONT>");
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|