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