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