log.c 17.8 KB
Newer Older
1 2 3
/* 
** Logging framework.
**
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
** 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.
**
27 28
*/

29 30 31
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
Jack Moffitt's avatar
Jack Moffitt committed
32 33 34 35 36
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
37 38 39 40 41 42 43 44 45 46
#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

Jack Moffitt's avatar
Jack Moffitt committed
47 48 49 50 51 52 53 54 55 56 57 58 59 60

#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
61 62
#define snprintf _snprintf
#define vsnprintf _vsnprintf
Jack Moffitt's avatar
Jack Moffitt committed
63 64 65 66 67 68 69
#else
#define mutex_t pthread_mutex_t
#endif

static mutex_t _logger_mutex;
static int _initialized = 0;

70 71 72 73 74 75 76 77
typedef struct _log_entry_t
{
   char *line;
   unsigned int len;
   struct _log_entry_t *next;
} log_entry_t;


Jack Moffitt's avatar
Jack Moffitt committed
78 79
typedef struct log_tag
{
80
    int in_use;
Jack Moffitt's avatar
Jack Moffitt committed
81

82
    unsigned level;
Jack Moffitt's avatar
Jack Moffitt committed
83

84 85
    char *filename;
    FILE *logfile;
86 87
    off_t size;
    off_t trigger_level;
88
    int archive_timestamp;
89 90 91 92 93 94

    unsigned long total;
    unsigned int entries;
    unsigned int keep_entries;
    log_entry_t *log_head;
    log_entry_t **log_tail;
95
    
Michael Smith's avatar
Michael Smith committed
96
    char *buffer;
Jack Moffitt's avatar
Jack Moffitt committed
97 98
} log_t;

99
static log_t loglist[LOG_MAXLOGS];
Jack Moffitt's avatar
Jack Moffitt committed
100

Michael Smith's avatar
Michael Smith committed
101 102 103
static int _get_log_id(void);
static void _lock_logger(void);
static void _unlock_logger(void);
Jack Moffitt's avatar
Jack Moffitt committed
104

105

106
static int _log_open (int id)
107 108 109 110 111 112 113 114 115 116 117 118 119 120
{
    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)
            {
121
                char new_name [4096];
122 123 124
                fclose (loglist [id] . logfile);
                loglist [id] . logfile = NULL;
                /* simple rename, but could use time providing locking were used */
125 126 127 128 129 130 131
                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);
132 133 134 135
                }
                else {
                    snprintf (new_name,  sizeof(new_name), "%s.old", loglist [id] . filename);
                }
136 137 138 139
#ifdef _WIN32
                if (stat (new_name, &st) == 0)
                    remove (new_name);
#endif
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
                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;
}

Michael Smith's avatar
Michael Smith committed
157
void log_initialize(void)
Jack Moffitt's avatar
Jack Moffitt committed
158
{
159
    int i;
Jack Moffitt's avatar
Jack Moffitt committed
160

161
    if (_initialized) return;
Jack Moffitt's avatar
Jack Moffitt committed
162

163 164 165
    for (i = 0; i < LOG_MAXLOGS; i++) {
        loglist[i].in_use = 0;
        loglist[i].level = 2;
166
        loglist[i].size = 0;
167
        loglist[i].trigger_level = 1000000000;
168 169 170
        loglist[i].filename = NULL;
        loglist[i].logfile = NULL;
        loglist[i].buffer = NULL;
171 172 173 174 175
        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;
176
    }
Jack Moffitt's avatar
Jack Moffitt committed
177

178
    /* initialize mutexes */
Jack Moffitt's avatar
Jack Moffitt committed
179
#ifndef _WIN32
180
    pthread_mutex_init(&_logger_mutex, NULL);
Jack Moffitt's avatar
Jack Moffitt committed
181
#else
182
    InitializeCriticalSection(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
183 184
#endif

185
    _initialized = 1;
Jack Moffitt's avatar
Jack Moffitt committed
186 187
}

188
int log_open_file(FILE *file)
Jack Moffitt's avatar
Jack Moffitt committed
189
{
190
    int log_id;
Jack Moffitt's avatar
Jack Moffitt committed
191

192
    if(file == NULL) return LOG_EINSANE;
Jack Moffitt's avatar
Jack Moffitt committed
193

194 195
    log_id = _get_log_id();
    if (log_id < 0) return LOG_ENOMORELOGS;
Jack Moffitt's avatar
Jack Moffitt committed
196

197
    loglist[log_id].logfile = file;
198 199
    loglist[log_id].filename = NULL;
    loglist[log_id].size = 0;
200

201
    return log_id;
Jack Moffitt's avatar
Jack Moffitt committed
202 203
}

204 205 206

int log_open(const char *filename)
{
207
    int id;
208 209
    FILE *file;

210 211
    if (filename == NULL) return LOG_EINSANE;
    if (strcmp(filename, "") == 0) return LOG_EINSANE;
212 213 214
    
    file = fopen(filename, "a");

215 216 217 218 219 220 221 222 223 224
    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;
225 226 227
        loglist [id] . entries = 0;
        loglist [id] . log_head = NULL;
        loglist [id] . log_tail = &loglist [id] . log_head;
228 229 230 231 232 233 234 235 236 237 238 239 240 241
    }

    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;
    }
}
242 243


244 245
int log_set_filename(int id, const char *filename)
{
246
    if (id < 0 || id >= LOG_MAXLOGS)
247
        return LOG_EINSANE;
248 249
    /* NULL filename is ok, empty filename is not. */
    if ((filename && !strcmp(filename, "")) || loglist [id] . in_use == 0)
250 251 252 253
        return LOG_EINSANE;
     _lock_logger();
    if (loglist [id] . filename)
        free (loglist [id] . filename);
254 255 256 257
    if (filename)
        loglist [id] . filename = strdup (filename);
    else
        loglist [id] . filename = NULL;
258 259
     _unlock_logger();
    return id;
260 261
}

262 263 264 265 266 267 268 269 270 271
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;
}

272

Jack Moffitt's avatar
Jack Moffitt committed
273 274
int log_open_with_buffer(const char *filename, int size)
{
275 276
    /* not implemented */
    return LOG_ENOTIMPL;
Jack Moffitt's avatar
Jack Moffitt committed
277 278
}

279

280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
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 ();
}


300
void log_set_level(int log_id, unsigned level)
Jack Moffitt's avatar
Jack Moffitt committed
301
{
302 303
    if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
    if (loglist[log_id].in_use == 0) return;
Jack Moffitt's avatar
Jack Moffitt committed
304

305
    loglist[log_id].level = level;
Jack Moffitt's avatar
Jack Moffitt committed
306 307 308 309
}

void log_flush(int log_id)
{
310 311
    if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
    if (loglist[log_id].in_use == 0) return;
Jack Moffitt's avatar
Jack Moffitt committed
312

313 314 315 316
    _lock_logger();
    if (loglist[log_id].logfile)
        fflush(loglist[log_id].logfile);
    _unlock_logger();
Jack Moffitt's avatar
Jack Moffitt committed
317 318 319 320
}

void log_reopen(int log_id)
{
321
    if (log_id < 0 || log_id >= LOG_MAXLOGS)
322
        return;
323
    if (loglist [log_id] . filename && loglist [log_id] . logfile)
324 325 326 327 328
    {
        _lock_logger();

        fclose (loglist [log_id] . logfile);
        loglist [log_id] . logfile = NULL;
329

330 331
        _unlock_logger();
    }
Jack Moffitt's avatar
Jack Moffitt committed
332 333 334 335
}

void log_close(int log_id)
{
336
    if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
337 338 339

    _lock_logger();

340 341 342 343 344
    if (loglist[log_id].in_use == 0)
    {
        _unlock_logger();
        return;
    }
345 346 347 348 349

    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);
350 351 352 353 354 355

    if (loglist [log_id] . logfile)
    {
        fclose (loglist [log_id] . logfile);
        loglist [log_id] . logfile = NULL;
    }
356 357 358 359 360 361 362 363 364
    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--;
    }
365
    _unlock_logger();
Jack Moffitt's avatar
Jack Moffitt committed
366 367
}

Michael Smith's avatar
Michael Smith committed
368
void log_shutdown(void)
Jack Moffitt's avatar
Jack Moffitt committed
369
{
370
    /* destroy mutexes */
Jack Moffitt's avatar
Jack Moffitt committed
371
#ifndef _WIN32
372
    pthread_mutex_destroy(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
373
#else
374
    DeleteCriticalSection(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
375 376
#endif 

377
    _initialized = 0;
Jack Moffitt's avatar
Jack Moffitt committed
378 379
}

380 381 382 383 384 385 386 387 388

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));
389 390 391 392
    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;
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
    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++;
408
    return entry->len;
409 410 411 412 413 414 415 416 417 418
}


void log_contents (int log_id, char **_contents, unsigned int *_len)
{
    int remain;
    log_entry_t *entry;
    char *ptr;

    if (log_id < 0) return;
419
    if (log_id >= LOG_MAXLOGS) return; /* Bad log number */
420 421 422 423

    _lock_logger ();
    remain = loglist [log_id].total + 1;
    *_contents = malloc (remain);
424
    **_contents= '\0';
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
    *_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 ();
}

442 443 444 445
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;
446
    int block_space = 0;
447 448 449 450 451 452 453 454 455 456 457
    const char * arg;
    char buf[80];

    for (; *format && size; format++)
    {
        if ( !in_block )
        {
            if ( *format == '%' ) {
                in_block = 1;
                block_size = 0;
                block_len  = 0;
458
                block_space = 0;
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
            }
            else
            {
                *(str++) = *format;
                size--;
            }
        }
        else
        {
            // TODO: %l*[sdupi] as well as %.4080s and "%.*s
            arg = NULL;
            switch (*format)
            {
                case 'l':
                    block_size++;
                    break;
475 476 477
                case 'z':
                    block_size = 'z';
                    break;
478 479 480 481 482 483
                case '.':
                    // just ignore '.'. If somebody cares: fix it.
                    break;
                case '*':
                    block_len = va_arg(ap, int);
                    break;
484 485 486
                case ' ':
                    block_space = 1;
                    break;
487 488 489 490 491 492 493 494 495 496 497
                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++);
498
                    format--;
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
                    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;
528 529 530 531 532 533 534
                            case 'z':
				/* We do not use 'z' type of snprintf() here as it is not safe to use on a few outdated platforms. */
                                if (*format == 'u')
                                    snprintf(buf, sizeof(buf), "%llu", (unsigned long long int)va_arg(ap, size_t));
                                else
                                    snprintf(buf, sizeof(buf), "%lli", (long long int)va_arg(ap, ssize_t));
                                break;
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
                            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' )
                    {
555
                        for (; *arg && block_len && size; arg++, size--, block_len--)
556
                        {
557
                            if ((*arg <= '"' || *arg == '`'  || *arg == '\\') && !(block_space && *arg == ' '))
558 559 560 561 562 563 564
                                *(str++) = '.';
                            else
                                *(str++) = *arg;
                        }
                    }
                    else
                    {
565
                        for (; *arg && block_len && size; arg++, size--, block_len--)
566 567 568 569 570 571 572 573 574 575 576 577 578
                            *(str++) = *arg;
                    }
                    in_block = 0;
                    break;
            }
        }
    }

    if ( !size )
        str--;

    *str = 0;
}
579

580
void log_write(int log_id, unsigned priority, const char *cat, const char *func, 
581
        const char *fmt, ...)
Jack Moffitt's avatar
Jack Moffitt committed
582
{
583
    static const char *prior[] = { "EROR", "WARN", "INFO", "DBUG" };
584
    int datelen;
585
    time_t now;
586 587 588
    char pre[256];
    char line[LOG_MAXLINELEN];
    va_list ap;
Jack Moffitt's avatar
Jack Moffitt committed
589

590
    if (log_id < 0 || log_id >= LOG_MAXLOGS) return; /* Bad log number */
591
    if (loglist[log_id].level < priority) return;
592
    if (!priority || priority > sizeof(prior)/sizeof(prior[0])) return; /* Bad priority */
Jack Moffitt's avatar
Jack Moffitt committed
593

594

595
    va_start(ap, fmt);
596 597
    __vsnprintf(line, sizeof(line), fmt, ap);
    va_end(ap);
Jack Moffitt's avatar
Jack Moffitt committed
598

599
    now = time(NULL);
600 601
    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);
Jack Moffitt's avatar
Jack Moffitt committed
602

603
    _lock_logger();
604
    if (_log_open (log_id))
605
    {
606
        int len = create_log_entry (log_id, pre, line);
607 608 609 610
        if (len > 0)
            loglist[log_id].size += len;
    }
    _unlock_logger();
Jack Moffitt's avatar
Jack Moffitt committed
611 612 613 614
}

void log_write_direct(int log_id, const char *fmt, ...)
{
615
    va_list ap;
616
    char line[LOG_MAXLINELEN];
Jack Moffitt's avatar
Jack Moffitt committed
617

618
    if (log_id < 0 || log_id >= LOG_MAXLOGS) return;
619 620
    
    va_start(ap, fmt);
621 622

    _lock_logger();
623
    __vsnprintf(line, LOG_MAXLINELEN, fmt, ap);
624
    if (_log_open (log_id))
625
    {
626
        int len = create_log_entry (log_id, "", line);
627 628 629 630 631
        if (len > 0)
            loglist[log_id].size += len;
    }
    _unlock_logger();

632
    va_end(ap);
Jack Moffitt's avatar
Jack Moffitt committed
633

634
    fflush(loglist[log_id].logfile);
Jack Moffitt's avatar
Jack Moffitt committed
635 636
}

Michael Smith's avatar
Michael Smith committed
637
static int _get_log_id(void)
Jack Moffitt's avatar
Jack Moffitt committed
638
{
639 640
    int i;
    int id = -1;
Jack Moffitt's avatar
Jack Moffitt committed
641

642 643
    /* lock mutex */
    _lock_logger();
Jack Moffitt's avatar
Jack Moffitt committed
644

645 646 647 648 649 650
    for (i = 0; i < LOG_MAXLOGS; i++)
        if (loglist[i].in_use == 0) {
            loglist[i].in_use = 1;
            id = i;
            break;
        }
Jack Moffitt's avatar
Jack Moffitt committed
651

652 653
    /* unlock mutex */
    _unlock_logger();
Jack Moffitt's avatar
Jack Moffitt committed
654

655
    return id;
Jack Moffitt's avatar
Jack Moffitt committed
656 657
}

Michael Smith's avatar
Michael Smith committed
658
static void _lock_logger(void)
Jack Moffitt's avatar
Jack Moffitt committed
659 660
{
#ifndef _WIN32
661
    pthread_mutex_lock(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
662
#else
663
    EnterCriticalSection(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
664 665 666
#endif
}

Michael Smith's avatar
Michael Smith committed
667
static void _unlock_logger(void)
Jack Moffitt's avatar
Jack Moffitt committed
668 669
{
#ifndef _WIN32
670
    pthread_mutex_unlock(&_logger_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
671
#else
672 673
    LeaveCriticalSection(&_logger_mutex);
#endif    
Jack Moffitt's avatar
Jack Moffitt committed
674 675 676 677 678
}