Improvements to syslog logger - by Hans Lambermont
[vlc.git] / modules / misc / logger.c
1 /*****************************************************************************
2  * logger.c : file logging plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2002 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc/vlc.h>
33 #include <vlc_interface.h>
34 #include <vlc_playlist.h>
35 #include <vlc_charset.h>
36
37 #include <errno.h>                                                 /* ENOMEM */
38
39 #ifdef UNDER_CE
40 #   define _IONBF 0x0004
41 #endif
42
43 #define MODE_TEXT 0
44 #define MODE_HTML 1
45 #define MODE_SYSLOG 2
46
47 #ifdef __APPLE__
48 #define LOG_DIR "Library/Logs/"
49 #endif
50
51 #define LOG_FILE_TEXT "vlc-log.txt"
52 #define LOG_FILE_HTML "vlc-log.html"
53
54 #define LOG_STRING( msg, file ) fwrite( msg, strlen( msg ), 1, file );
55
56 #define TEXT_HEADER "-- logger module started --\n"
57 #define TEXT_FOOTER "-- logger module stopped --\n"
58
59 #define HTML_HEADER \
60     "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n" \
61     "  \"http://www.w3.org/TR/html4/strict.dtd\">\n" \
62     "<html>\n" \
63     "  <head>\n" \
64     "    <title>vlc log</title>\n" \
65     "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n" \
66     "  </head>\n" \
67     "  <body style=\"background-color: #000000; color: #aaaaaa;\">\n" \
68     "    <pre>\n" \
69     "      <b>-- logger module started --</b>\n"
70 #define HTML_FOOTER \
71     "      <b>-- logger module stopped --</b>\n" \
72     "    </pre>\n" \
73     "  </body>\n" \
74     "</html>\n"
75
76 #if HAVE_SYSLOG_H
77 #include <syslog.h>
78 #endif
79
80 /*****************************************************************************
81  * intf_sys_t: description and status of log interface
82  *****************************************************************************/
83 struct intf_sys_t
84 {
85     int i_mode;
86     FILE *p_rrd;
87     mtime_t last_update;
88
89     FILE *    p_file; /* The log file */
90     msg_subscription_t *p_sub;
91 };
92
93 /*****************************************************************************
94  * Local prototypes
95  *****************************************************************************/
96 static int  Open    ( vlc_object_t * );
97 static void Close   ( vlc_object_t * );
98 static void Run     ( intf_thread_t * );
99
100 static void FlushQueue        ( msg_subscription_t *, FILE *, int, int );
101 static void TextPrint         ( const msg_item_t *, FILE * );
102 static void HtmlPrint         ( const msg_item_t *, FILE * );
103 #ifdef HAVE_SYSLOG_H
104 static void SyslogPrint       ( const msg_item_t *);
105 #endif
106
107 static void DoRRD( intf_thread_t *p_intf );
108
109 /*****************************************************************************
110  * Module descriptor
111  *****************************************************************************/
112 static const char *mode_list[] = { "text", "html"
113 #ifdef HAVE_SYSLOG_H
114 ,"syslog"
115 #endif
116 };
117 static const char *mode_list_text[] = { N_("Text"), "HTML"
118 #ifdef HAVE_SYSLOG_H
119 , "syslog"
120 #endif
121 };
122
123 #define LOGMODE_TEXT N_("Log format")
124 #ifdef HAVE_SYSLOG_H
125 #define LOGMODE_LONGTEXT N_("Specify the log format. Available choices are " \
126   "\"text\" (default), \"html\", and \"syslog\" (special mode to send to " \
127   "syslog instead of file.")
128 #else
129 #define LOGMODE_LONGTEXT N_("Specify the log format. Available choices are " \
130   "\"text\" (default) and \"html\".")
131 #endif
132
133 vlc_module_begin();
134     set_shortname( _( "Logging" ) );
135     set_description( _("File logging") );
136
137     set_category( CAT_ADVANCED );
138     set_subcategory( SUBCAT_ADVANCED_MISC );
139
140     add_file( "logfile", NULL, NULL,
141              N_("Log filename"), N_("Specify the log filename."), VLC_FALSE );
142         change_unsafe();
143     add_string( "logmode", "text", NULL, LOGMODE_TEXT, LOGMODE_LONGTEXT,
144                 VLC_FALSE );
145         change_string_list( mode_list, mode_list_text, 0 );
146
147     add_file( "rrd-file", NULL, NULL, N_("RRD output file") ,
148                     N_("Output data for RRDTool in this file." ), VLC_TRUE );
149
150     set_capability( "interface", 0 );
151     set_callbacks( Open, Close );
152 vlc_module_end();
153
154 /*****************************************************************************
155  * Open: initialize and create stuff
156  *****************************************************************************/
157 static int Open( vlc_object_t *p_this )
158 {
159     intf_thread_t *p_intf = (intf_thread_t *)p_this;
160     char *psz_mode, *psz_file, *psz_rrd_file;
161
162     CONSOLE_INTRO_MSG;
163     msg_Info( p_intf, "using logger..." );
164
165     /* Allocate instance and initialize some members */
166     p_intf->p_sys = (intf_sys_t *)malloc( sizeof( intf_sys_t ) );
167     if( p_intf->p_sys == NULL )
168     {
169         msg_Err( p_intf, "out of memory" );
170         return -1;
171     }
172
173     psz_mode = var_CreateGetString( p_intf, "logmode" );
174     if( psz_mode )
175     {
176         if( !strcmp( psz_mode, "text" ) )
177         {
178             p_intf->p_sys->i_mode = MODE_TEXT;
179         }
180         else if( !strcmp( psz_mode, "html" ) )
181         {
182             p_intf->p_sys->i_mode = MODE_HTML;
183         }
184 #ifdef HAVE_SYSLOG_H
185         else if( !strcmp( psz_mode, "syslog" ) )
186         {
187             p_intf->p_sys->i_mode = MODE_SYSLOG;
188         }
189 #endif
190         else
191         {
192             msg_Warn( p_intf, "invalid log mode `%s', using `text'", psz_mode );
193             p_intf->p_sys->i_mode = MODE_TEXT;
194         }
195
196         free( psz_mode );
197     }
198     else
199     {
200         msg_Warn( p_intf, "no log mode specified, using `text'" );
201         p_intf->p_sys->i_mode = MODE_TEXT;
202     }
203
204     if( p_intf->p_sys->i_mode != MODE_SYSLOG )
205     {
206         psz_file = config_GetPsz( p_intf, "logfile" );
207         if( !psz_file )
208         {
209 #ifdef __APPLE__
210             char *psz_homedir = p_this->p_libvlc->psz_homedir;
211
212             if( !psz_homedir ) /* XXX: This should never happen */
213             {
214                 msg_Err( p_this, "unable to find home directory" );
215                 return -1;
216             }
217             psz_file = (char *)malloc( sizeof("/" LOG_DIR "/" LOG_FILE_HTML) +
218                                            strlen(psz_homedir) );
219             if( psz_file )
220             {
221                 switch( p_intf->p_sys->i_mode )
222                 {
223                 case MODE_HTML:
224                     sprintf( psz_file, "%s/" LOG_DIR "/" LOG_FILE_HTML,
225                          psz_homedir );
226                     break;
227                 case MODE_TEXT:
228                 default:
229                     sprintf( psz_file, "%s/" LOG_DIR "/" LOG_FILE_TEXT,
230                          psz_homedir );
231                     break;
232                 }
233             }
234 #else
235             switch( p_intf->p_sys->i_mode )
236             {
237             case MODE_HTML:
238                 psz_file = strdup( LOG_FILE_HTML );
239                 break;
240             case MODE_TEXT:
241             default:
242                 psz_file = strdup( LOG_FILE_TEXT );
243                 break;
244             }
245 #endif
246             msg_Warn( p_intf, "no log filename provided, using `%s'",
247                                psz_file );
248         }
249
250         /* Open the log file and remove any buffering for the stream */
251         msg_Dbg( p_intf, "opening logfile `%s'", psz_file );
252         p_intf->p_sys->p_file = utf8_fopen( psz_file, "at" );
253         if( p_intf->p_sys->p_file == NULL )
254         {
255             msg_Err( p_intf, "error opening logfile `%s'", psz_file );
256             free( p_intf->p_sys );
257             free( psz_file );
258             return -1;
259         }
260         setvbuf( p_intf->p_sys->p_file, NULL, _IONBF, 0 );
261
262         free( psz_file );
263
264         switch( p_intf->p_sys->i_mode )
265         {
266         case MODE_HTML:
267             LOG_STRING( HTML_HEADER, p_intf->p_sys->p_file );
268             break;
269         case MODE_TEXT:
270         default:
271             LOG_STRING( TEXT_HEADER, p_intf->p_sys->p_file );
272             break;
273         }
274
275     }
276     else
277     {
278         p_intf->p_sys->p_file = NULL;
279 #ifdef HAVE_SYSLOG_H
280         openlog( "VLC", LOG_PID|LOG_NDELAY, LOG_DAEMON );
281 #endif
282     }
283
284     p_intf->p_sys->last_update = 0;
285     p_intf->p_sys->p_rrd = NULL;
286
287     psz_rrd_file = config_GetPsz( p_intf, "rrd-file" );
288     if( psz_rrd_file && *psz_rrd_file )
289     {
290         p_intf->p_sys->p_rrd = utf8_fopen( psz_rrd_file, "w" );
291     }
292
293     p_intf->p_sys->p_sub = msg_Subscribe( p_intf , MSG_QUEUE_NORMAL );
294     p_intf->pf_run = Run;
295
296     return 0;
297 }
298
299 /*****************************************************************************
300  * Close: destroy interface stuff
301  *****************************************************************************/
302 static void Close( vlc_object_t *p_this )
303 {
304     intf_thread_t *p_intf = (intf_thread_t *)p_this;
305
306     /* Flush the queue and unsubscribe from the message queue */
307     FlushQueue( p_intf->p_sys->p_sub, p_intf->p_sys->p_file,
308                 p_intf->p_sys->i_mode, p_intf->p_libvlc->i_verbose );
309     msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
310
311     switch( p_intf->p_sys->i_mode )
312     {
313     case MODE_HTML:
314         LOG_STRING( HTML_FOOTER, p_intf->p_sys->p_file );
315         break;
316     case MODE_TEXT:
317 #ifdef HAVE_SYSLOG_H
318     case MODE_SYSLOG:
319         closelog();
320         break;
321 #endif
322     default:
323         LOG_STRING( TEXT_FOOTER, p_intf->p_sys->p_file );
324         break;
325     }
326
327     /* Close the log file */
328     if( p_intf->p_sys->i_mode != MODE_SYSLOG )
329         fclose( p_intf->p_sys->p_file );
330
331     /* Destroy structure */
332     free( p_intf->p_sys );
333 }
334
335 /*****************************************************************************
336  * Run: rc thread
337  *****************************************************************************
338  * This part of the interface is in a separate thread so that we can call
339  * exec() from within it without annoying the rest of the program.
340  *****************************************************************************/
341 static void Run( intf_thread_t *p_intf )
342 {
343     while( !p_intf->b_die )
344     {
345         FlushQueue( p_intf->p_sys->p_sub, p_intf->p_sys->p_file,
346                     p_intf->p_sys->i_mode, p_intf->p_libvlc->i_verbose );
347
348         if( p_intf->p_sys->p_rrd )
349             DoRRD( p_intf );
350
351         msleep( INTF_IDLE_SLEEP );
352     }
353 }
354
355 /*****************************************************************************
356  * FlushQueue: flush the message queue into the log
357  *****************************************************************************/
358 static void FlushQueue( msg_subscription_t *p_sub, FILE *p_file, int i_mode,
359                         int i_verbose )
360 {
361     int i_start, i_stop;
362
363     vlc_mutex_lock( p_sub->p_lock );
364     i_stop = *p_sub->pi_stop;
365     vlc_mutex_unlock( p_sub->p_lock );
366
367     if( p_sub->i_start != i_stop )
368     {
369         /* Append all messages to log file */
370         for( i_start = p_sub->i_start;
371              i_start != i_stop;
372              i_start = (i_start+1) % VLC_MSG_QSIZE )
373         {
374             switch( p_sub->p_msg[i_start].i_type )
375             {
376             case VLC_MSG_ERR:
377                 if( i_verbose < 0 ) continue;
378                 break;
379             case VLC_MSG_INFO:
380                 if( i_verbose < 0 ) continue;
381                 break;
382             case VLC_MSG_WARN:
383                 if( i_verbose < 1 ) continue;
384                 break;
385             case VLC_MSG_DBG:
386                 if( i_verbose < 2 ) continue;
387                 break;
388             }
389
390             switch( i_mode )
391             {
392             case MODE_HTML:
393                 HtmlPrint( &p_sub->p_msg[i_start], p_file );
394                 break;
395 #ifdef HAVE_SYSLOG_H
396             case MODE_SYSLOG:
397                 SyslogPrint( &p_sub->p_msg[i_start] );
398                 break;
399 #endif
400             case MODE_TEXT:
401             default:
402                 TextPrint( &p_sub->p_msg[i_start], p_file );
403                 break;
404             }
405         }
406
407         vlc_mutex_lock( p_sub->p_lock );
408         p_sub->i_start = i_start;
409         vlc_mutex_unlock( p_sub->p_lock );
410     }
411 }
412
413 static const char *ppsz_type[4] = { ": ", " error: ",
414                                     " warning: ", " debug: " };
415
416 static void TextPrint( const msg_item_t *p_msg, FILE *p_file )
417 {
418     LOG_STRING( p_msg->psz_module, p_file );
419     LOG_STRING( ppsz_type[p_msg->i_type], p_file );
420     LOG_STRING( p_msg->psz_msg, p_file );
421     LOG_STRING( "\n", p_file );
422 }
423
424 #ifdef HAVE_SYSLOG_H
425 static void SyslogPrint( const msg_item_t *p_msg )
426 {
427     static const int i_prio[4] = { LOG_INFO, LOG_ERR, LOG_WARNING, LOG_DEBUG };
428     int i_priority = i_prio[p_msg->i_type];
429
430     if( p_msg->psz_header )
431         syslog( i_priority, "%s%s %s: %s", p_msg->psz_header,
432                 ppsz_type[p_msg->i_type],
433                 p_msg->psz_module, p_msg->psz_msg );
434     else
435         syslog( i_priority, "%s%s: %s", p_msg->psz_module, 
436                 ppsz_type[p_msg->i_type], p_msg->psz_msg );
437  
438 }
439 #endif
440
441 static void HtmlPrint( const msg_item_t *p_msg, FILE *p_file )
442 {
443     static const char *ppsz_color[4] = { "<span style=\"color: #ffffff\">",
444                                          "<span style=\"color: #ff6666\">",
445                                          "<span style=\"color: #ffff66\">",
446                                          "<span style=\"color: #aaaaaa\">" };
447
448     LOG_STRING( p_msg->psz_module, p_file );
449     LOG_STRING( ppsz_type[p_msg->i_type], p_file );
450     LOG_STRING( ppsz_color[p_msg->i_type], p_file );
451     LOG_STRING( p_msg->psz_msg, p_file );
452     LOG_STRING( "</span>\n", p_file );
453 }
454
455 static void DoRRD( intf_thread_t *p_intf )
456 {
457     playlist_t *p_playlist;
458     if( mdate() - p_intf->p_sys->last_update < 1000000 )
459         return;
460     p_intf->p_sys->last_update = mdate();
461
462     p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
463                                                 FIND_ANYWHERE );
464     if( p_playlist && p_playlist->p_stats )
465     {
466         lldiv_t din = lldiv( p_playlist->p_stats->f_input_bitrate * 1000000,
467                              1000 );
468         lldiv_t ddm = lldiv( p_playlist->p_stats->f_demux_bitrate * 1000000,
469                              1000 );
470         lldiv_t dout = lldiv( p_playlist->p_stats->f_output_bitrate * 1000000,
471                              1000 );
472         fprintf( p_intf->p_sys->p_rrd,
473                    I64Fi":%lld.%03u:%lld.%03u:%lld.%03u\n",
474                    p_intf->p_sys->last_update/1000000,
475                    din.quot, (unsigned int)din.rem,
476                    ddm.quot, (unsigned int)ddm.rem,
477                    dout.quot, (unsigned int)dout.rem );
478         fflush( p_intf->p_sys->p_rrd );
479         vlc_object_release( p_playlist );
480     }
481 }