logger.c 13.7 KB
Newer Older
1 2 3
/*****************************************************************************
 * logger.c : file logging plugin for vlc
 *****************************************************************************
4
 * Copyright (C) 2002 the VideoLAN team
5
 * $Id$
6 7 8 9 10 11 12
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
13
 *
14 15 16 17 18 19 20
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25 26 27 28 29 30 31 32
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */
#include <string.h>

#include <errno.h>                                                 /* ENOMEM */
#include <stdio.h>

33 34 35 36
#ifdef UNDER_CE
#   define _IONBF 0x0004
#endif

37 38 39 40 41
#include <vlc/vlc.h>
#include <vlc/intf.h>

#define MODE_TEXT 0
#define MODE_HTML 1
42
#define MODE_SYSLOG 2
43

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
44 45 46 47 48 49 50
#ifdef SYS_DARWIN
#define LOG_DIR "Library/Logs/"
#endif

#define LOG_FILE_TEXT "vlc-log.txt"
#define LOG_FILE_HTML "vlc-log.html"

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
#define LOG_STRING( msg, file ) fwrite( msg, strlen( msg ), 1, file );

#define TEXT_HEADER "-- logger module started --\n"
#define TEXT_FOOTER "-- logger module stopped --\n"

#define HTML_HEADER \
    "<html>\n" \
    "  <head>\n" \
    "    <title>vlc log</title>\n" \
    "  </head>\n" \
    "  <body bgcolor=\"#000000\" text=\"#aaaaaa\">\n" \
    "    <pre>\n" \
    "      <b>-- logger module started --</b>\n"
#define HTML_FOOTER \
    "      <b>-- logger module stopped --</b>\n" \
    "    </pre>\n" \
    "  </body>\n" \
    "</html>\n"

70 71 72 73
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif

74 75 76 77 78 79
/*****************************************************************************
 * intf_sys_t: description and status of log interface
 *****************************************************************************/
struct intf_sys_t
{
    int i_mode;
Clément Stenac's avatar
Clément Stenac committed
80 81
    FILE *p_rrd;
    mtime_t last_update;
82 83 84 85 86 87 88 89

    FILE *    p_file; /* The log file */
    msg_subscription_t *p_sub;
};

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
90
static int  Open    ( vlc_object_t * );
91 92 93 94 95 96
static void Close   ( vlc_object_t * );
static void Run     ( intf_thread_t * );

static void FlushQueue        ( msg_subscription_t *, FILE *, int );
static void TextPrint         ( const msg_item_t *, FILE * );
static void HtmlPrint         ( const msg_item_t *, FILE * );
97 98 99
#ifdef HAVE_SYSLOG_H
static void SyslogPrint       ( const msg_item_t *);
#endif
100

Clément Stenac's avatar
Clément Stenac committed
101 102
static void DoRRD( intf_thread_t *p_intf );

103 104 105
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
106 107 108 109 110 111 112 113 114 115
static char *mode_list[] = { "text", "html"
#ifdef HAVE_SYSLOG_H
,"syslog"
#endif
};
static char *mode_list_text[] = { N_("Text"), "HTML"
#ifdef HAVE_SYSLOG_H
, "syslog"
#endif
};
116

Christophe Massiot's avatar
Christophe Massiot committed
117
#define LOGMODE_TEXT N_("Log format")
118 119 120
#ifdef HAVE_SYSLOG_H
#define LOGMODE_LONGTEXT N_("Specify the log format. Available choices are \"text\" (default), \"html\", and \"syslog\".")
#else
121
#define LOGMODE_LONGTEXT N_("Specify the log format. Available choices are \"text\" (default) and \"html\".")
122
#endif
Christophe Massiot's avatar
Christophe Massiot committed
123

124
vlc_module_begin();
125 126
    set_shortname( N_( "Logging" ) );
    set_description( _("File logging") );
Gildas Bazin's avatar
 
Gildas Bazin committed
127

128 129
    add_file( "logfile", NULL, NULL,
             N_("Log filename"), N_("Specify the log filename."), VLC_FALSE );
130 131 132
    add_string( "logmode", "text", NULL, LOGMODE_TEXT, LOGMODE_LONGTEXT,
                VLC_FALSE );
        change_string_list( mode_list, mode_list_text, 0 );
Gildas Bazin's avatar
 
Gildas Bazin committed
133

Clément Stenac's avatar
Clément Stenac committed
134 135 136
    add_string( "rrd-file", NULL, NULL, N_("RRD output file") ,
                    N_("Output data for RRDTool in this file" ), VLC_TRUE );

137 138 139 140 141 142 143 144
    set_capability( "interface", 0 );
    set_callbacks( Open, Close );
vlc_module_end();

/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
145
{
146
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
Clément Stenac's avatar
Clément Stenac committed
147
    char *psz_mode, *psz_file, *psz_rrd_file;
148

Gildas Bazin's avatar
 
Gildas Bazin committed
149
    CONSOLE_INTRO_MSG;
150
    msg_Info( p_intf, "Using logger..." );
151 152 153 154 155 156 157 158 159

    /* Allocate instance and initialize some members */
    p_intf->p_sys = (intf_sys_t *)malloc( sizeof( intf_sys_t ) );
    if( p_intf->p_sys == NULL )
    {
        msg_Err( p_intf, "out of memory" );
        return -1;
    }

160
    psz_mode = var_CreateGetString( p_intf, "logmode" );
161 162 163 164 165 166 167 168 169 170
    if( psz_mode )
    {
        if( !strcmp( psz_mode, "text" ) )
        {
            p_intf->p_sys->i_mode = MODE_TEXT;
        }
        else if( !strcmp( psz_mode, "html" ) )
        {
            p_intf->p_sys->i_mode = MODE_HTML;
        }
171 172 173 174 175 176
#ifdef HAVE_SYSLOG_H
        else if( !strcmp( psz_mode, "syslog" ) )
        {
            p_intf->p_sys->i_mode = MODE_SYSLOG;
        }
#endif
177 178 179 180 181 182 183 184 185 186 187 188 189 190
        else
        {
            msg_Err( p_intf, "invalid log mode `%s', using `text'", psz_mode );
            p_intf->p_sys->i_mode = MODE_TEXT;
        }

        free( psz_mode );
    }
    else
    {
        msg_Warn( p_intf, "no log mode specified, using `text'" );
        p_intf->p_sys->i_mode = MODE_TEXT;
    }

191
    if( p_intf->p_sys->i_mode != MODE_SYSLOG )
192
    {
193 194 195
        psz_file = config_GetPsz( p_intf, "logfile" );
        if( !psz_file )
        {
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
196
#ifdef SYS_DARWIN
197
            char *psz_homedir = p_this->p_vlc->psz_homedir;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
198

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
            if( !psz_homedir )
            {
                msg_Err( p_this, "psz_homedir is null" );
                return -1;
            }
            psz_file = (char *)malloc( sizeof("/" LOG_DIR "/" LOG_FILE_HTML) +
                                           strlen(psz_homedir) );
            if( psz_file )
            {
                switch( p_intf->p_sys->i_mode )
                {
                case MODE_HTML:
                    sprintf( psz_file, "%s/" LOG_DIR "/" LOG_FILE_HTML,
                         psz_homedir );
                    break;
                case MODE_TEXT:
                default:
                    sprintf( psz_file, "%s/" LOG_DIR "/" LOG_FILE_TEXT,
                         psz_homedir );
                    break;
                }
            }
#else
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
222 223 224
            switch( p_intf->p_sys->i_mode )
            {
            case MODE_HTML:
225
                psz_file = strdup( LOG_FILE_HTML );
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
226 227 228
                break;
            case MODE_TEXT:
            default:
229
                psz_file = strdup( LOG_FILE_TEXT );
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
230 231
                break;
            }
232 233 234
#endif
            msg_Warn( p_intf, "no log filename provided, using `%s'",
                               psz_file );
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
235
        }
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250

        /* Open the log file and remove any buffering for the stream */
        msg_Dbg( p_intf, "opening logfile `%s'", psz_file );
        p_intf->p_sys->p_file = fopen( psz_file, "wt" );
        if( p_intf->p_sys->p_file == NULL )
        {
            msg_Err( p_intf, "error opening logfile `%s'", psz_file );
            free( p_intf->p_sys );
            free( psz_file );
            return -1;
        }
        setvbuf( p_intf->p_sys->p_file, NULL, _IONBF, 0 );

        free( psz_file );

251
        switch( p_intf->p_sys->i_mode )
252 253
        {
        case MODE_HTML:
254
            LOG_STRING( HTML_HEADER, p_intf->p_sys->p_file );
255 256 257
            break;
        case MODE_TEXT:
        default:
258
            LOG_STRING( TEXT_HEADER, p_intf->p_sys->p_file );
259 260 261 262
            break;
        }

    }
263
    else
264
    {
265 266 267 268
        p_intf->p_sys->p_file = NULL;
#ifdef HAVE_SYSLOG_H
        openlog( "VLC", 0, LOG_DAEMON );
#endif
269 270
    }

Clément Stenac's avatar
Clément Stenac committed
271 272 273 274 275 276 277 278 279
    p_intf->p_sys->last_update = 0;
    p_intf->p_sys->p_rrd = NULL;

    psz_rrd_file = config_GetPsz( p_intf, "rrd-file" );
    if( psz_rrd_file && *psz_rrd_file )
    {
        p_intf->p_sys->p_rrd = fopen( psz_rrd_file, "w" );
    }

280
    p_intf->p_sys->p_sub = msg_Subscribe( p_intf , MSG_QUEUE_NORMAL );
Gildas Bazin's avatar
 
Gildas Bazin committed
281 282
    p_intf->pf_run = Run;

283 284 285 286 287 288 289
    return 0;
}

/*****************************************************************************
 * Close: destroy interface stuff
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
290
{
291
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
292

293 294 295 296 297 298 299 300 301 302 303
    /* Flush the queue and unsubscribe from the message queue */
    FlushQueue( p_intf->p_sys->p_sub, p_intf->p_sys->p_file,
                p_intf->p_sys->i_mode );
    msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );

    switch( p_intf->p_sys->i_mode )
    {
    case MODE_HTML:
        LOG_STRING( HTML_FOOTER, p_intf->p_sys->p_file );
        break;
    case MODE_TEXT:
304 305 306 307 308
#ifdef HAVE_SYSLOG_H
    case MODE_SYSLOG:
        closelog();
        break;
#endif
309 310 311 312 313 314
    default:
        LOG_STRING( TEXT_FOOTER, p_intf->p_sys->p_file );
        break;
    }

    /* Close the log file */
315 316
    if( p_intf->p_sys->i_mode != MODE_SYSLOG )
        fclose( p_intf->p_sys->p_file );
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334

    /* Destroy structure */
    free( p_intf->p_sys );
}

/*****************************************************************************
 * Run: rc thread
 *****************************************************************************
 * This part of the interface is in a separate thread so that we can call
 * exec() from within it without annoying the rest of the program.
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
    while( !p_intf->b_die )
    {
        FlushQueue( p_intf->p_sys->p_sub, p_intf->p_sys->p_file,
                    p_intf->p_sys->i_mode );

Clément Stenac's avatar
Clément Stenac committed
335 336 337
        if( p_intf->p_sys->p_rrd )
            DoRRD( p_intf );

338 339 340 341 342
        msleep( INTF_IDLE_SLEEP );
    }
}

/*****************************************************************************
343
 * FlushQueue: flush the message queue into the log
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
 *****************************************************************************/
static void FlushQueue( msg_subscription_t *p_sub, FILE *p_file, int i_mode )
{
    int i_start, i_stop;

    vlc_mutex_lock( p_sub->p_lock );
    i_stop = *p_sub->pi_stop;
    vlc_mutex_unlock( p_sub->p_lock );

    if( p_sub->i_start != i_stop )
    {
        /* Append all messages to log file */
        for( i_start = p_sub->i_start;
             i_start != i_stop;
             i_start = (i_start+1) % VLC_MSG_QSIZE )
        {
            switch( i_mode )
            {
            case MODE_HTML:
                HtmlPrint( &p_sub->p_msg[i_start], p_file );
                break;
365 366 367 368 369
#ifdef HAVE_SYSLOG_H
            case MODE_SYSLOG:
                SyslogPrint( &p_sub->p_msg[i_start] );
                break;
#endif
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
            case MODE_TEXT:
            default:
                TextPrint( &p_sub->p_msg[i_start], p_file );
                break;
            }
        }

        vlc_mutex_lock( p_sub->p_lock );
        p_sub->i_start = i_start;
        vlc_mutex_unlock( p_sub->p_lock );
    }
}

static const char *ppsz_type[4] = { ": ", " error: ",
                                    " warning: ", " debug: " };

static void TextPrint( const msg_item_t *p_msg, FILE *p_file )
{
    LOG_STRING( p_msg->psz_module, p_file );
    LOG_STRING( ppsz_type[p_msg->i_type], p_file );
    LOG_STRING( p_msg->psz_msg, p_file );
    LOG_STRING( "\n", p_file );
}

394 395 396
#ifdef HAVE_SYSLOG_H
static void SyslogPrint( const msg_item_t *p_msg )
{
397 398
    int i_priority = LOG_INFO;

399 400 401 402 403
    if( p_msg->i_type  == 0 ) i_priority = LOG_INFO;
    if( p_msg->i_type  == 1 ) i_priority = LOG_ERR;
    if( p_msg->i_type  == 2 ) i_priority = LOG_WARNING;
    if( p_msg->i_type  == 3 ) i_priority = LOG_DEBUG;

404 405 406 407 408 409
    if( p_msg->psz_header )
        syslog( i_priority, "%s %s: %s", p_msg->psz_header,
                p_msg->psz_module, p_msg->psz_msg );
    else
        syslog( i_priority, "%s: %s", p_msg->psz_module, p_msg->psz_msg );
        
410 411 412
}
#endif

413 414 415 416 417 418 419 420 421 422 423 424 425 426
static void HtmlPrint( const msg_item_t *p_msg, FILE *p_file )
{
    static const char *ppsz_color[4] = { "<font color=\"#ffffff\">",
                                         "<font color=\"#ff6666\">",
                                         "<font color=\"#ffff66\">",
                                         "<font color=\"#aaaaaa\">" };

    LOG_STRING( p_msg->psz_module, p_file );
    LOG_STRING( ppsz_type[p_msg->i_type], p_file );
    LOG_STRING( ppsz_color[p_msg->i_type], p_file );
    LOG_STRING( p_msg->psz_msg, p_file );
    LOG_STRING( "</font>\n", p_file );
}

Clément Stenac's avatar
Clément Stenac committed
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
static void DoRRD( intf_thread_t *p_intf )
{
    playlist_t *p_playlist;
    float f_input_bitrate;
    if( mdate() - p_intf->p_sys->last_update < 1000000 )
        return;
    p_intf->p_sys->last_update = mdate();

    p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
                                                FIND_ANYWHERE );
    if( p_playlist && p_playlist->p_stats )
    {
        fprintf( p_intf->p_sys->p_rrd, I64Fi":%f:%f:%f\n",
                   p_intf->p_sys->last_update/1000000,
                   (float)(p_playlist->p_stats->f_input_bitrate)*1000,
                   (float)(p_playlist->p_stats->f_demux_bitrate)*1000,
                   (float)(p_playlist->p_stats->f_output_bitrate)*1000 );
        fflush( p_intf->p_sys->p_rrd );
        vlc_object_release( p_playlist );
    }
}