1
0
mirror of https://gitlab.xiph.org/xiph/icecast-common.git synced 2024-09-01 03:54:15 -04:00
icecast-common/log/log.c
Thomas B. Ruecker 8685e6ed5a Explicitly license httpp log net thread and timing
This change was ACKd by all authors and explicitly states the license
to be LGPLv2 in all files to match COPYING. This removes ambiguity
when using these files e.g. as part of libshout.

For a list of authors, pease refer to the copyright headers of each
file.
2014-12-05 09:31:08 +00:00

684 lines
18 KiB
C

/*
** Logging framework.
**
** Copyright (C) 2014 Michael Smith <msmith@icecast.org>,
** Ralph Giles <giles@xiph.org>,
** Ed "oddsock" Zaleski <oddsock@xiph.org>,
** Karl Heyes <karl@xiph.org>,
** Jack Moffitt <jack@icecast.org>,
** Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
** Thomas Ruecker <thomas@ruecker.fi>
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Library General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This library 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
** Library General Public License for more details.
**
** You should have received a copy of the GNU Library General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
** Boston, MA 02110-1301, USA.
**
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef _WIN32
#include <pthread.h>
#else
#include <windows.h>
#endif
#include "log.h"
#define LOG_MAXLOGS 25
#define LOG_MAXLINELEN 1024
#ifdef _WIN32
#define mutex_t CRITICAL_SECTION
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#else
#define mutex_t pthread_mutex_t
#endif
static mutex_t _logger_mutex;
static int _initialized = 0;
typedef struct _log_entry_t
{
char *line;
unsigned int len;
struct _log_entry_t *next;
} log_entry_t;
typedef struct log_tag
{
int in_use;
unsigned level;
char *filename;
FILE *logfile;
off_t size;
off_t trigger_level;
int archive_timestamp;
unsigned long total;
unsigned int entries;
unsigned int keep_entries;
log_entry_t *log_head;
log_entry_t **log_tail;
char *buffer;
} log_t;
static log_t loglist[LOG_MAXLOGS];
static int _get_log_id(void);
static void _release_log_id(int log_id);
static void _lock_logger(void);
static void _unlock_logger(void);
static int _log_open (int id)
{
if (loglist [id] . in_use == 0)
return 0;
/* check for cases where an open of the logfile is wanted */
if (loglist [id] . logfile == NULL ||
(loglist [id] . trigger_level && loglist [id] . size > loglist [id] . trigger_level))
{
if (loglist [id] . filename) /* only re-open files where we have a name */
{
struct stat st;
if (loglist [id] . logfile)
{
char new_name [4096];
fclose (loglist [id] . logfile);
loglist [id] . logfile = NULL;
/* simple rename, but could use time providing locking were used */
if (loglist[id].archive_timestamp)
{
char timestamp [128];
time_t now = time(NULL);
strftime (timestamp, sizeof (timestamp), "%Y%m%d_%H%M%S", localtime (&now));
snprintf (new_name, sizeof(new_name), "%s.%s", loglist[id].filename, timestamp);
}
else {
snprintf (new_name, sizeof(new_name), "%s.old", loglist [id] . filename);
}
#ifdef _WIN32
if (stat (new_name, &st) == 0)
remove (new_name);
#endif
rename (loglist [id] . filename, new_name);
}
loglist [id] . logfile = fopen (loglist [id] . filename, "a");
if (loglist [id] . logfile == NULL)
return 0;
setvbuf (loglist [id] . logfile, NULL, IO_BUFFER_TYPE, 0);
if (stat (loglist [id] . filename, &st) < 0)
loglist [id] . size = 0;
else
loglist [id] . size = st.st_size;
}
else
loglist [id] . size = 0;
}
return 1;
}
void log_initialize(void)
{
int i;
if (_initialized) return;
for (i = 0; i < LOG_MAXLOGS; i++) {
loglist[i].in_use = 0;
loglist[i].level = 2;
loglist[i].size = 0;
loglist[i].trigger_level = 1000000000;
loglist[i].filename = NULL;
loglist[i].logfile = NULL;
loglist[i].buffer = NULL;
loglist[i].total = 0;
loglist[i].entries = 0;
loglist[i].keep_entries = 0;
loglist[i].log_head = NULL;
loglist[i].log_tail = &loglist[i].log_head;
}
/* initialize mutexes */
#ifndef _WIN32
pthread_mutex_init(&_logger_mutex, NULL);
#else
InitializeCriticalSection(&_logger_mutex);
#endif
_initialized = 1;
}
int log_open_file(FILE *file)
{
int log_id;
if(file == NULL) return LOG_EINSANE;
log_id = _get_log_id();
if (log_id < 0) return LOG_ENOMORELOGS;
loglist[log_id].logfile = file;
loglist[log_id].filename = NULL;
loglist[log_id].size = 0;
return log_id;
}
int log_open(const char *filename)
{
int id;
FILE *file;
if (filename == NULL) return LOG_EINSANE;
if (strcmp(filename, "") == 0) return LOG_EINSANE;
file = fopen(filename, "a");
id = log_open_file(file);
if (id >= 0)
{
struct stat st;
setvbuf (loglist [id] . logfile, NULL, IO_BUFFER_TYPE, 0);
loglist [id] . filename = strdup (filename);
if (stat (loglist [id] . filename, &st) == 0)
loglist [id] . size = st.st_size;
loglist [id] . entries = 0;
loglist [id] . log_head = NULL;
loglist [id] . log_tail = &loglist [id] . log_head;
}
return id;
}
/* set the trigger level to trigger, represented in kilobytes */
void log_set_trigger(int id, unsigned trigger)
{
if (id >= 0 && id < LOG_MAXLOGS && loglist [id] . in_use)
{
loglist [id] . trigger_level = trigger*1024;
}
}
int log_set_filename(int id, const char *filename)
{
if (id < 0 || id >= LOG_MAXLOGS)
return LOG_EINSANE;
/* NULL filename is ok, empty filename is not. */
if ((filename && !strcmp(filename, "")) || loglist [id] . in_use == 0)
return LOG_EINSANE;
_lock_logger();
if (loglist [id] . filename)
free (loglist [id] . filename);
if (filename)
loglist [id] . filename = strdup (filename);
else
loglist [id] . filename = NULL;
_unlock_logger();
return id;
}
int log_set_archive_timestamp(int id, int value)
{
if (id < 0 || id >= LOG_MAXLOGS)
return LOG_EINSANE;
_lock_logger();
loglist[id].archive_timestamp = value;
_unlock_logger();
return id;
}
int log_open_with_buffer(const char *filename, int size)
{
/* not implemented */
return LOG_ENOTIMPL;
}
void log_set_lines_kept (int log_id, unsigned int count)
{
if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
if (loglist[log_id].in_use == 0) return;
_lock_logger ();
loglist[log_id].keep_entries = count;
while (loglist[log_id].entries > count)
{
log_entry_t *to_go = loglist [log_id].log_head;
loglist [log_id].log_head = to_go->next;
loglist [log_id].total -= to_go->len;
free (to_go->line);
free (to_go);
loglist [log_id].entries--;
}
_unlock_logger ();
}
void log_set_level(int log_id, unsigned level)
{
if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
if (loglist[log_id].in_use == 0) return;
loglist[log_id].level = level;
}
void log_flush(int log_id)
{
if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
if (loglist[log_id].in_use == 0) return;
_lock_logger();
if (loglist[log_id].logfile)
fflush(loglist[log_id].logfile);
_unlock_logger();
}
void log_reopen(int log_id)
{
if (log_id < 0 || log_id >= LOG_MAXLOGS)
return;
if (loglist [log_id] . filename && loglist [log_id] . logfile)
{
_lock_logger();
fclose (loglist [log_id] . logfile);
loglist [log_id] . logfile = NULL;
_unlock_logger();
}
}
void log_close(int log_id)
{
if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
_lock_logger();
if (loglist[log_id].in_use == 0)
{
_unlock_logger();
return;
}
loglist[log_id].in_use = 0;
loglist[log_id].level = 2;
if (loglist[log_id].filename) free(loglist[log_id].filename);
if (loglist[log_id].buffer) free(loglist[log_id].buffer);
if (loglist [log_id] . logfile)
{
fclose (loglist [log_id] . logfile);
loglist [log_id] . logfile = NULL;
}
while (loglist[log_id].entries)
{
log_entry_t *to_go = loglist [log_id].log_head;
loglist [log_id].log_head = to_go->next;
loglist [log_id].total -= to_go->len;
free (to_go->line);
free (to_go);
loglist [log_id].entries--;
}
_unlock_logger();
}
void log_shutdown(void)
{
/* destroy mutexes */
#ifndef _WIN32
pthread_mutex_destroy(&_logger_mutex);
#else
DeleteCriticalSection(&_logger_mutex);
#endif
_initialized = 0;
}
static int create_log_entry (int log_id, const char *pre, const char *line)
{
log_entry_t *entry;
if (loglist[log_id].keep_entries == 0)
return fprintf (loglist[log_id].logfile, "%s%s\n", pre, line);
entry = calloc (1, sizeof (log_entry_t));
entry->len = strlen (pre) + strlen (line) + 2;
entry->line = malloc (entry->len);
snprintf (entry->line, entry->len, "%s%s\n", pre, line);
loglist [log_id].total += entry->len;
fprintf (loglist[log_id].logfile, "%s", entry->line);
*loglist [log_id].log_tail = entry;
loglist [log_id].log_tail = &entry->next;
if (loglist [log_id].entries >= loglist [log_id].keep_entries)
{
log_entry_t *to_go = loglist [log_id].log_head;
loglist [log_id].log_head = to_go->next;
loglist [log_id].total -= to_go->len;
free (to_go->line);
free (to_go);
}
else
loglist [log_id].entries++;
return entry->len;
}
void log_contents (int log_id, char **_contents, unsigned int *_len)
{
int remain;
log_entry_t *entry;
char *ptr;
if (log_id < 0) return;
if (log_id >= LOG_MAXLOGS) return; /* Bad log number */
_lock_logger ();
remain = loglist [log_id].total + 1;
*_contents = malloc (remain);
**_contents= '\0';
*_len = loglist [log_id].total;
entry = loglist [log_id].log_head;
ptr = *_contents;
while (entry)
{
int len = snprintf (ptr, remain, "%s", entry->line);
if (len > 0)
{
ptr += len;
remain -= len;
}
entry = entry->next;
}
_unlock_logger ();
}
static void __vsnprintf(char *str, size_t size, const char *format, va_list ap) {
int in_block = 0;
int block_size = 0;
int block_len;
int block_space = 0;
const char * arg;
char buf[80];
for (; *format && size; format++)
{
if ( !in_block )
{
if ( *format == '%' ) {
in_block = 1;
block_size = 0;
block_len = 0;
block_space = 0;
}
else
{
*(str++) = *format;
size--;
}
}
else
{
// TODO: %l*[sdupi] as well as %.4080s and "%.*s
arg = NULL;
switch (*format)
{
case 'l':
block_size++;
break;
case '.':
// just ignore '.'. If somebody cares: fix it.
break;
case '*':
block_len = va_arg(ap, int);
break;
case ' ':
block_space = 1;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
block_len = atoi(format);
for (; *format >= '0' && *format <= '9'; format++);
format--;
break;
case 'p':
snprintf(buf, sizeof(buf), "%p", (void*)va_arg(ap, void *));
arg = buf;
case 'd':
case 'i':
case 'u':
if (!arg)
{
switch (block_size)
{
case 0:
if (*format == 'u')
snprintf(buf, sizeof(buf), "%u", (unsigned int)va_arg(ap, unsigned int));
else
snprintf(buf, sizeof(buf), "%i", (int)va_arg(ap, int));
break;
case 1:
if (*format == 'u')
snprintf(buf, sizeof(buf), "%lu", (unsigned long int)va_arg(ap, unsigned long int));
else
snprintf(buf, sizeof(buf), "%li", (long int)va_arg(ap, long int));
break;
case 2:
if (*format == 'u')
snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, unsigned long long int));
else
snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, long long int));
break;
default:
snprintf(buf, sizeof(buf), "<<<invalid>>>");
break;
}
arg = buf;
}
case 's':
case 'H':
// TODO.
if (!arg)
arg = va_arg(ap, const char *);
if (!arg)
arg = "(null)";
if (!block_len)
block_len = strlen(arg);
// the if() is the outer structure so the inner for()
// is branch optimized.
if (*format == 'H' )
{
for (; *arg && block_len && size; arg++, size--, block_len--)
{
if ((*arg <= '"' || *arg == '`' || *arg == '\\') && !(block_space && *arg == ' '))
*(str++) = '.';
else
*(str++) = *arg;
}
}
else
{
for (; *arg && block_len && size; arg++, size--, block_len--)
*(str++) = *arg;
}
in_block = 0;
break;
}
}
}
if ( !size )
str--;
*str = 0;
}
void log_write(int log_id, unsigned priority, const char *cat, const char *func,
const char *fmt, ...)
{
static const char *prior[] = { "EROR", "WARN", "INFO", "DBUG" };
int datelen;
time_t now;
char pre[256];
char line[LOG_MAXLINELEN];
va_list ap;
if (log_id < 0 || log_id >= LOG_MAXLOGS) return; /* Bad log number */
if (loglist[log_id].level < priority) return;
if (!priority || priority > sizeof(prior)/sizeof(prior[0])) return; /* Bad priority */
va_start(ap, fmt);
__vsnprintf(line, sizeof(line), fmt, ap);
va_end(ap);
now = time(NULL);
datelen = strftime (pre, sizeof (pre), "[%Y-%m-%d %H:%M:%S]", localtime(&now));
snprintf (pre+datelen, sizeof (pre)-datelen, " %s %s%s ", prior [priority-1], cat, func);
_lock_logger();
if (_log_open (log_id))
{
int len = create_log_entry (log_id, pre, line);
if (len > 0)
loglist[log_id].size += len;
}
_unlock_logger();
}
void log_write_direct(int log_id, const char *fmt, ...)
{
va_list ap;
time_t now;
char line[LOG_MAXLINELEN];
if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
va_start(ap, fmt);
now = time(NULL);
_lock_logger();
__vsnprintf(line, LOG_MAXLINELEN, fmt, ap);
if (_log_open (log_id))
{
int len = create_log_entry (log_id, "", line);
if (len > 0)
loglist[log_id].size += len;
}
_unlock_logger();
va_end(ap);
fflush(loglist[log_id].logfile);
}
static int _get_log_id(void)
{
int i;
int id = -1;
/* lock mutex */
_lock_logger();
for (i = 0; i < LOG_MAXLOGS; i++)
if (loglist[i].in_use == 0) {
loglist[i].in_use = 1;
id = i;
break;
}
/* unlock mutex */
_unlock_logger();
return id;
}
static void _release_log_id(int log_id)
{
/* lock mutex */
_lock_logger();
loglist[log_id].in_use = 0;
/* unlock mutex */
_unlock_logger();
}
static void _lock_logger(void)
{
#ifndef _WIN32
pthread_mutex_lock(&_logger_mutex);
#else
EnterCriticalSection(&_logger_mutex);
#endif
}
static void _unlock_logger(void)
{
#ifndef _WIN32
pthread_mutex_unlock(&_logger_mutex);
#else
LeaveCriticalSection(&_logger_mutex);
#endif
}