#include "config.h" /* for 64-bit file support */
#include "gsklog.h"
#ifndef __REENTRANT
#define __REENTRANT 1
#endif
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#define DEFAULT_TIME_FORMAT "%Y-%m-%d %H:%M:%S"
#define DEFAULT_MODE "w" /* or "a" for append */
typedef struct _Piece Piece;
typedef struct _PrintInfo PrintInfo;
typedef struct _ParsedFormat ParsedFormat;
struct _PrintInfo
{
const char *domain;
GLogLevelFlags level;
const char *message;
};
struct _Piece
{
void (*print) (Piece *,
PrintInfo *info,
GString *out);
};
struct _ParsedFormat
{
guint ref_count;
char *output_format;
guint n_pieces;
Piece **pieces;
};
static void
literal_print (Piece *piece,
PrintInfo *info,
GString *out)
{
g_string_append (out, (char*)(piece + 1));
}
static Piece *
piece_literal (const char *str, gssize len)
{
Piece *p;
if (len < 0)
len = strlen (str);
p = g_malloc (sizeof (Piece) + len + 1);
p->print = literal_print;
memcpy ((char *)(p + 1), str, len);
((char *)(p + 1))[len] = '\0';
return p;
}
static void
message_print (Piece *piece,
PrintInfo *info,
GString *out)
{
g_string_append (out, info->message);
}
static Piece *
piece_message (void)
{
Piece *p = g_malloc (sizeof (Piece));
p->print = message_print;
return p;
}
static void
nmessage_print (Piece *piece,
PrintInfo *info,
GString *out)
{
guint max = * (guint *) (piece + 1);
guint mlen = strlen (info->message);
g_string_append_len (out, info->message, MIN (max, mlen));
}
static Piece *
piece_nmessage (guint n)
{
Piece *p = g_malloc (sizeof (Piece) + sizeof (guint));
p->print = nmessage_print;
((guint *) (p + 1))[0] = n;
return p;
}
static void
datetime_print (Piece *piece,
PrintInfo *info,
GString *out)
{
char buf[512];
time_t t;
struct tm tm;
gboolean *b = (gboolean *) (piece + 1);
const char *fmt = (char *) (b + 1);
time (&t);
if (*b)
localtime_r (&t, &tm);
else
gmtime_r (&t, &tm);
strftime (buf, sizeof (buf), fmt, &tm);
g_string_append (out, buf);
}
static Piece *
piece_datetime(gboolean use_localtime, const char *fmt)
{
Piece *piece = g_malloc (sizeof (Piece) + sizeof (gboolean) + strlen (fmt) + 1);
gboolean *b = (gboolean *) (piece + 1);
char *piece_fmt = (char *) (b + 1);
*b = use_localtime;
strcpy (piece_fmt, fmt);
piece->print = datetime_print;
return piece;
}
static void
domain_print (Piece *piece,
PrintInfo *info,
GString *out)
{
g_string_append (out, info->domain);
}
static Piece *
piece_domain (void)
{
Piece *piece = g_new (Piece, 1);
piece->print = domain_print;
return piece;
}
enum
{
LEVEL_MODE_LOWERCASE,
LEVEL_MODE_MIXEDCASE,
LEVEL_MODE_UPPERCASE,
LEVEL_MODE_G
};
static struct
{
GLogLevelFlags level;
const char *tags[4];
const char *suffix;
} level_infos[6] =
{
{ G_LOG_LEVEL_ERROR, { "error", "Error", "ERROR", "***ERROR***" }, "!!!" },
{ G_LOG_LEVEL_CRITICAL, { "critical", "Critical", "CRITICAL", "***CRITICAL***" }, "!!!" },
{ G_LOG_LEVEL_WARNING, { "warning", "Warning", "WARNING", "*WARNING*" }, "!" },
{ G_LOG_LEVEL_MESSAGE, { "message", "Message", "MESSAGE", "*Message*" }, "." },
{ G_LOG_LEVEL_INFO, { "info", "Info", "INFO", "Info" }, "." },
{ G_LOG_LEVEL_DEBUG, { "debug", "Debug", "DEBUG", "debug" }, "." }
};
static void
level_prefix_print (Piece *piece,
PrintInfo *info,
GString *out)
{
unsigned level;
for (level = 0; level < G_N_ELEMENTS (level_infos); level++)
if ((info->level & level_infos[level].level) != 0)
break;
if (level == G_N_ELEMENTS (level_infos))
g_string_append (out, "[unknown log level]");
else
g_string_append (out, level_infos[level].tags[* (unsigned *)(piece + 1)]);
}
static Piece *
piece_level_prefix (unsigned level_mode)
{
Piece *rv = g_malloc (sizeof (Piece) + sizeof (unsigned));
rv->print = level_prefix_print;
* (unsigned *) (rv + 1) = level_mode;
return rv;
}
static void
level_suffix_print (Piece *piece,
PrintInfo *info,
GString *out)
{
unsigned level;
for (level = 0; level < G_N_ELEMENTS (level_infos); level++)
if ((info->level & level_infos[level].level) != 0)
break;
if (level == G_N_ELEMENTS (level_infos))
g_string_append (out, ".");
else
g_string_append (out, level_infos[level].suffix);
}
static Piece *
piece_level_suffix (void)
{
Piece *rv = g_malloc (sizeof (Piece));
rv->print = level_suffix_print;
return rv;
}
static Piece *
handle_special_piece (const char *type)
{
gboolean got_digits = isdigit (type[0]);
guint count = 0;
if (got_digits)
{
char *end;
count = strtoul (type, &end, 10);
type = end;
}
if (strcmp (type, "message") == 0)
{
if (got_digits)
return piece_nmessage (count);
else
return piece_message ();
}
else if (strncmp (type, "localtime:", 10) == 0)
{
const char *fmt = type + 10;
return piece_datetime (TRUE, fmt);
}
else if (strncmp (type, "gmtime:", 7) == 0)
{
const char *fmt = type + 7;
return piece_datetime (FALSE, fmt);
}
else if (strcmp (type, "localtime") == 0)
{
return piece_datetime (TRUE, DEFAULT_TIME_FORMAT);
}
else if (strcmp (type, "gmtime") == 0)
{
return piece_datetime (FALSE, DEFAULT_TIME_FORMAT);
}
else if (strcmp (type, "domain") == 0)
return piece_domain ();
else if (strcmp (type, "level") == 0)
return piece_level_prefix (LEVEL_MODE_LOWERCASE);
else if (strcmp (type, "Level") == 0)
return piece_level_prefix (LEVEL_MODE_MIXEDCASE);
else if (strcmp (type, "LEVEL") == 0)
return piece_level_prefix (LEVEL_MODE_UPPERCASE);
else if (strcmp (type, "glevel") == 0)
return piece_level_prefix (LEVEL_MODE_G);
else if (strcmp (type, "levelsuffix") == 0)
return piece_level_suffix ();
else
return NULL;
}
static Piece *
handle_special_piece_n (const char *tmp, guint len)
{
char *msg = g_alloca (len + 1);
memcpy (msg, tmp, len);
msg[len] = 0;
return handle_special_piece (msg);
}
static ParsedFormat *
parse_format (const char *output_format)
{
GString *literal = g_string_new ("");
GPtrArray *pieces = g_ptr_array_new ();
const char *orig_output_format = output_format;
ParsedFormat *rv;
while (*output_format)
{
if (*output_format != '%')
{
g_string_append_c (literal, *output_format);
output_format++;
}
else
{
if (output_format[1] == '%')
{
g_string_append_c (literal, '%');
output_format += 2;
}
else if (output_format[1] == '{')
{
const char *start = output_format + 2;
const char *end = strchr (start, '}');
Piece *piece;
if (end == NULL)
{
g_warning ("missing '}'");
return NULL; /* XXX: leaks, needs proper error handling */
}
piece = handle_special_piece_n (start, end - start);
if (piece == NULL)
{
g_warning ("error parsing special log-format token '%.*s' (in context '%s')", (int)(end - start), start, output_format);
return NULL; /* XXX: leaks, needs proper error handling */
}
if (literal->len > 0)
{
g_ptr_array_add (pieces, piece_literal (literal->str, literal->len));
g_string_set_size (literal, 0);
}
g_ptr_array_add (pieces, piece);
output_format = end + 1;
}
else
{
g_warning ("error parsing format string, at '%s'", output_format);
return NULL; /* XXX: leaks, needs proper error handling */
}
}
}
#if 0 /* this commented code adds a newline, which we no longer need */
if (literal->len == 0 || literal->str[literal->len-1] != '\n')
g_string_append_c (literal, '\n');
#endif
if (literal->len > 0)
g_ptr_array_add (pieces, piece_literal (literal->str, literal->len));
g_string_free (literal, TRUE);
rv = g_new (ParsedFormat, 1);
rv->ref_count = 1;
rv->output_format = g_strdup (orig_output_format);
rv->n_pieces = pieces->len;
rv->pieces = (Piece **) g_ptr_array_free (pieces, FALSE);
return rv;
}
static ParsedFormat *
parsed_format_new (const char *output_format)
{
static GHashTable *output_format_to_parsed_format = NULL;
ParsedFormat *rv;
if (output_format == NULL)
output_format = GSK_LOG_DEFAULT_OUTPUT_FORMAT;
if (output_format_to_parsed_format == NULL)
output_format_to_parsed_format = g_hash_table_new (g_str_hash, g_str_equal);
rv = g_hash_table_lookup (output_format_to_parsed_format, output_format);
if (rv)
{
++(rv->ref_count);
return rv;
}
else
{
rv = parse_format (output_format);
if (rv == NULL)
return NULL;
g_hash_table_insert (output_format_to_parsed_format, rv->output_format, rv);
}
return rv;
}
static GHashTable *filename_to_FILE = NULL;
static FILE *
log_file_maybe_open (const char *filename, const char *mode)
{
FILE *fp;
if (filename_to_FILE == NULL)
filename_to_FILE = g_hash_table_new (g_str_hash, g_str_equal);
if (g_hash_table_lookup_extended (filename_to_FILE, filename, NULL, (gpointer *) &fp))
return fp;
fp = fopen (filename, mode);
if (fp != NULL)
setlinebuf (fp);
g_hash_table_insert (filename_to_FILE, g_strdup (filename), fp);
return fp;
}
/**
* gsk_log_append:
* @filename: log filename that should be opened in append-mode,
* rather than overwritten.
*
* Indicate that the given logfile should
* be appended to, rather than overwritten.
*
* This must be invoked before any other references to the logfile.
*/
void gsk_log_append (const char *filename)
{
log_file_maybe_open (filename, "a");
}
struct _GskLogTrap
{
const char *domain;
GLogLevelFlags level_mask;
ParsedFormat *format;
gpointer data;
GskLogTrapFunc func;
GDestroyNotify destroy;
};
static void
handle_fp (const char *domain,
GLogLevelFlags level,
const char *raw_message,
const char *formatted_message,
gpointer data)
{
FILE *fp = data;
fputs (formatted_message, fp);
fputc ('\n', fp);
}
static GskLogTrap *
trap_new_fp (FILE *fp, ParsedFormat *format)
{
GskLogTrap *trap = g_new (GskLogTrap, 1);
trap->data = fp;
trap->format = format;
trap->func = handle_fp;
trap->destroy = NULL;
return trap;
}
static void
handle_ring_buffer (const char *domain,
GLogLevelFlags level,
const char *raw_message,
const char *formatted_message,
gpointer data)
{
gsk_log_ring_buffer_add (data, formatted_message);
}
static GskLogTrap *
trap_new_ring_buffer (GskLogRingBuffer *buffer,
ParsedFormat *format)
{
GskLogTrap *trap = g_new (GskLogTrap, 1);
trap->data = buffer;
trap->format = format;
trap->func = handle_ring_buffer;
trap->destroy = NULL;
return trap;
}
static GskLogTrap *
trap_new_generic (GskLogTrapFunc func,
gpointer data,
GDestroyNotify destroy,
ParsedFormat *format)
{
GskLogTrap *trap = g_new (GskLogTrap, 1);
trap->data = data;
trap->format = format;
trap->func = func;
trap->destroy = destroy;
return trap;
}
static GHashTable *domain_to_slist_of_traps = NULL;
static gboolean log_system_initialized = FALSE;
static void
add_trap (const char *domain,
GLogLevelFlags level_mask,
GskLogTrap *trap)
{
GSList *trap_list;
gpointer key;
trap->level_mask = level_mask;
if (domain_to_slist_of_traps == NULL)
domain_to_slist_of_traps = g_hash_table_new (g_str_hash, g_str_equal);
if ( g_hash_table_lookup_extended (domain_to_slist_of_traps, domain,
&key, (gpointer *) &trap_list) )
{
if (trap_list)
g_slist_append (trap_list, trap);
else
g_hash_table_insert (domain_to_slist_of_traps,
(gpointer) domain,
g_slist_prepend (NULL, trap));
}
else
{
g_hash_table_insert (domain_to_slist_of_traps,
key = g_strdup (domain),
g_slist_prepend (NULL, trap));
}
trap->domain = key;
}
/**
* gsk_log_trap_domain_to_file:
*
* @domain: the log-domain to trap, as passed to g_log
* or the gsk_ family of log functions.
* @filename: the filename to write the log to.
* @output_format: a string giving the formatting
* to be used with the given trap.
* It may contain any of the following strings:
* %{message} the message itself.
* %{NNNmessage} the first NNN characters of message.
* %{localtime:FMT} the time/date in local timezone, formatted as per FMT.
* %{gmtime:FMT} the time/date in gm, formatted as per FMT.
* (If :FMT is omitted, a default format string is used)
* %{domain} the log domain.
* %{level} the log level, as 'error', 'message', etc.
* %{glevel} approximately how glib does the level:
* 'Debug', 'Info', '*Message*', '***Warning***',
* '***Critical***', '***ERROR***'.
* %{Level}, %{LEVEL} like %{level} with casing differences.
* %{levelsuffix} '.', '!', '!!!' depending on the severity.
* %% a percent symbol.
*/
GskLogTrap *
gsk_log_trap_domain_to_file(const char *domain,
GLogLevelFlags level_mask,
const char *filename,
const char *output_format)
{
FILE *fp = log_file_maybe_open (filename, DEFAULT_MODE);
ParsedFormat *format;
GskLogTrap *trap;
if (fp == NULL)
return NULL;
if (!log_system_initialized)
gsk_log_init ();
format = parsed_format_new (output_format);
if (format == NULL)
return NULL;
trap = trap_new_fp (fp, format);
add_trap (domain, level_mask, trap);
return trap;
}
GskLogTrap *gsk_log_trap_generic (const char *domain,
GLogLevelFlags trap_mask,
const char *output_format,
GskLogTrapFunc func,
gpointer data,
GDestroyNotify destroy)
{
ParsedFormat *format;
GskLogTrap *trap;
if (!log_system_initialized)
gsk_log_init ();
format = parsed_format_new (output_format);
if (format == NULL)
return NULL;
trap = trap_new_generic (func, data, destroy, format);
add_trap (domain, trap_mask, trap);
return trap;
}
static void
ignore_errors (const char *domain,
GLogLevelFlags level,
const char *raw_message,
const char *formatted_message,
gpointer data)
{
}
GskLogTrap *gsk_log_trap_ignore (const char *domain,
GLogLevelFlags trap_mask)
{
if (!log_system_initialized)
gsk_log_init ();
return gsk_log_trap_generic (domain, trap_mask, "",
ignore_errors, NULL, NULL);
}
GskLogTrap *gsk_log_trap_ring_buffer (const char *domain,
GLogLevelFlags trap_mask,
GskLogRingBuffer *buffer,
const char *output_format)
{
ParsedFormat *format;
GskLogTrap *trap;
if (!log_system_initialized)
gsk_log_init ();
format = parsed_format_new (output_format);
if (format == NULL)
return NULL;
trap = trap_new_ring_buffer (buffer, format);
add_trap (domain, trap_mask, trap);
return trap;
}
static void
trap_print_using_PrintInfo (GskLogTrap *trap,
PrintInfo *info)
{
GString *out = g_string_new ("");
guint i;
for (i = 0; i < trap->format->n_pieces; i++)
{
Piece *piece = trap->format->pieces[i];
piece->print (piece, info, out);
}
(*trap->func) (trap->domain, info->level,
info->message, out->str,
trap->data);
g_string_free (out, TRUE);
}
static void
log_default (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
GSList *traps = NULL;
if (log_domain != NULL)
traps = g_hash_table_lookup (domain_to_slist_of_traps, log_domain);
if (traps)
{
PrintInfo info = {log_domain, log_level, message};
g_slist_foreach (traps, (GFunc) trap_print_using_PrintInfo, &info);
}
else
g_log_default_handler (log_domain, log_level, message, NULL);
}
void gsk_log_init (void)
{
if (!log_system_initialized)
{
if (domain_to_slist_of_traps == NULL)
domain_to_slist_of_traps = g_hash_table_new (g_str_hash, g_str_equal);
g_log_set_default_handler (log_default, NULL);
log_system_initialized = TRUE;
}
}
syntax highlighted by Code2HTML, v. 0.9.1