]> sjero.net Git - linphone/blob - gtk/logging.c
Merge branch 'master' of git.savannah.nongnu.org:/srv/git/linphone
[linphone] / gtk / logging.c
1 /*
2 linphone, gtk-glade interface.
3 Copyright (C) 2008  Simon MORLAT (simon.morlat@linphone.org)
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19
20 #include "linphone.h"
21
22 #ifndef WIN32
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #endif
26
27
28 static GtkWidget *log_window=NULL;
29 static GStaticMutex log_mutex=G_STATIC_MUTEX_INIT;
30 static GList *log_queue=NULL;
31
32 typedef struct _LinphoneGtkLog{
33         OrtpLogLevel lev;
34         gchar *msg;
35 }LinphoneGtkLog;
36
37
38 /******
39  * Module to log to a file
40  ******/
41
42 /* Marker to insert as a line at the start of every log file */
43 #define LOGFILE_MARKER_START  "----<start>----"
44 /* Marker to insert as a line at the end of every log file */
45 #define LOGFILE_MARKER_STOP  "----<end>----"
46 /* Number of files to keep in history, log file rotation will be
47    performed. */
48 #define LOGFILE_ROTATION 4
49 /* Pointer to opened log file */
50 static FILE *_logfile = NULL;
51
52
53 /* Called on exit, print out the marker, close the file and avoid to
54    continue logging. */
55 static void linphone_gtk_log_uninit()
56 {
57         if (_logfile != NULL) {
58                 fprintf(_logfile, "%s\n", LOGFILE_MARKER_STOP);
59                 fclose(_logfile);
60                 _logfile = NULL;
61         }
62 }
63
64 /* Called when we start logging, find a good place for the log files,
65    perform rotation, insert the start marker and return the pointer to
66    the file that should be used for logging, or NULL on errors or if
67    disabled. */
68 static FILE *linphone_gtk_log_init()
69 {
70         static char _logdir[1024];
71         static char _logfname[1024];
72         static gboolean _log_init = FALSE;
73         const char *dst_fname;
74
75         if (!_log_init) {
76                 dst_fname = linphone_gtk_get_ui_config("logfile",NULL);
77                 /* For anything to happen, we need a logfile configuration variable,
78                  this is our trigger */
79                 if (dst_fname) {
80                         /* arrange for _logdir to contain a
81                          directory that has been created and _logfname to contain the
82                          path to a file to which we will log */
83 #ifdef WIN32
84                         const char *appdata=getenv("LOCALAPPDATA");
85                         if (appdata) {
86                                 snprintf(_logdir, sizeof(_logdir),"%s\\Linphone", appdata);
87                                 mkdir(_logdir);
88                         } else {
89                                 _logdir[0] = '\0';
90                         }
91 #define PATH_SEPARATOR '\\'
92 #else
93                         const char *home=getenv("HOME");
94                         if (home) {
95                                 snprintf(_logdir, sizeof(_logdir),"%s/.linphone", home);
96                                 mkdir(_logdir,S_IRUSR | S_IWUSR | S_IRGRP);
97                         } else {
98                                 _logdir[0] = '\0';
99                         }
100 #define PATH_SEPARATOR '/'
101 #endif
102                         /* We have a directory, fix the path to the log file in it and
103                          open the file so that we will be appending to it. */
104                         if (_logdir[0] != '\0') {
105                                 snprintf(_logfname, sizeof(_logfname), "%s%c%s",
106                                         _logdir, PATH_SEPARATOR, dst_fname);
107                                 /* If the constant LOGFILE_ROTATION is greater than zero, then
108                                  we kick away a simple rotation that will ensure that there
109                                  are never more than LOGFILE_ROTATION+1 old copies of the
110                                  log file on the disk.  The oldest file is always rotated
111                                  "away" as expected.  Rotated files have the same name as
112                                  the main log file, though with a number 0..LOGFILE_ROTATION
113                                  at the end, where the greater the number is, the older the
114                                  file is. */
115                                 if (ortp_file_exist(_logfname)==0 && LOGFILE_ROTATION > 0) {
116                                         int i;
117                                         char old_fname[1024];
118                                         char new_fname[1024];
119
120                                         /* Rotate away existing files.  We make sure to remove the
121                                          old files otherwise rename() would not work properly.  We
122                                          have to loop in reverse here. */
123                                         for (i=LOGFILE_ROTATION-1;i>=0;i--) {
124                                                 snprintf(old_fname, sizeof(old_fname), "%s%c%s.%d",
125                                                         _logdir, PATH_SEPARATOR, dst_fname, i);
126                                                 snprintf(new_fname, sizeof(new_fname), "%s%c%s.%d",
127                                                         _logdir, PATH_SEPARATOR, dst_fname, i+1);
128                                                 if (ortp_file_exist(old_fname)==0) {
129                                                         if (ortp_file_exist(new_fname)==0)
130                                                                 unlink(new_fname);
131                                                         rename(old_fname, new_fname);
132                                                 }
133                                         }
134                                         /* Move current log file as the first of the rotation.  Make
135                                          sure to remove the old .0 also, since otherwise rename()
136                                          would not work as expected. */
137                                         snprintf(new_fname, sizeof(new_fname), "%s%c%s.%d",
138                                                 _logdir, PATH_SEPARATOR, dst_fname, 0);
139                                         if (ortp_file_exist(new_fname)==0)
140                                                 unlink(new_fname);
141                                         rename(_logfname, new_fname);
142                                 }
143                                 /* Start a new log file and mark that we have now initialised */
144                                 _logfile = fopen(_logfname, "w");
145                                 fprintf(_logfile, "%s\n", LOGFILE_MARKER_START);
146                         }
147                 }
148                 _log_init = TRUE;
149         }
150         return _logfile;
151 }
152
153 static void linphone_gtk_log_file(OrtpLogLevel lev, const char *msg)
154 {
155         LinphoneCore *lc;
156         time_t now;
157         FILE *outlog;
158
159         lc = linphone_gtk_get_core();
160         /* Nothing to do until the core has initialised */
161         if (lc == NULL)
162                 return;
163
164         /* lc->config will turn NULL at exit, close the file to flush and
165          return to stop logging */
166         if (linphone_core_get_config(lc) == NULL) {
167                 linphone_gtk_log_uninit();
168                 return;
169         }
170
171         outlog = linphone_gtk_log_init();
172         if (outlog != NULL) {
173                 /* We have an opened file and we have initialised properly, it's
174                  time to write all these log messages. We convert the log level
175                  from oRTP into something readable and timestamp each log
176                  message.  The format of the timestamp can be controlled by
177                  logfile_date_format in the GtkUi section of the config file,
178                  but it defaults to something compact, but yet readable. */
179                 const char *lname="undef";
180                 const char *dateformat=linphone_gtk_get_ui_config("logfile_date_format",
181                     "%Y%m%d-%H:%M:%S");
182                 char date[256];
183
184                 /* Convert level constant to text */
185                 switch(lev){
186                         case ORTP_DEBUG:
187                                 lname="debug";
188                                 break;
189                         case ORTP_MESSAGE:
190                                 lname="message";
191                                 break;
192                         case ORTP_WARNING:
193                                 lname="warning";
194                                 break;
195                         case ORTP_ERROR:
196                                 lname="error";
197                                 break;
198                         case ORTP_FATAL:
199                                 lname="fatal";
200                                 break;
201                         default:
202                                 lname="undef";
203                                 break;
204                 }
205                 /* Get current time and format it properly */
206                 now = time(NULL);
207                 strftime(date, sizeof(date), dateformat, localtime(&now));
208                 /* Now print out the message to the logfile.  We don't flush,
209                  maybe we should do to ensure that we have all the messages in
210                  case of a crash (which is one of the main reasons we have a
211                      log facility in the first place). */
212                 fprintf(outlog, "[%s] [%s] %s\n", date, lname, msg);
213         }
214 }
215
216 void linphone_gtk_log_hide(){
217         if (log_window)
218                 gtk_widget_hide(log_window);
219 }
220
221 void linphone_gtk_create_log_window(void){
222         GtkTextBuffer *b;
223         log_window=linphone_gtk_create_window("log");
224         b=gtk_text_view_get_buffer(GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview")));
225         gtk_text_buffer_create_tag(b,"red","foreground","red",NULL);
226         gtk_text_buffer_create_tag(b,"orange","foreground","orange",NULL);
227         /*prevent the log window from being destroyed*/
228         g_signal_connect (G_OBJECT (log_window), "delete-event",
229                 G_CALLBACK (gtk_widget_hide_on_delete), log_window);
230
231 }
232
233 void linphone_gtk_destroy_log_window(void){
234         GtkWidget *w=log_window;
235         g_static_mutex_lock(&log_mutex);
236         log_window=NULL;
237         gtk_widget_destroy(w);
238         g_static_mutex_unlock(&log_mutex);
239 }
240
241 void linphone_gtk_log_show(void){
242         gtk_widget_show(log_window);
243         gtk_window_present(GTK_WINDOW(log_window));
244 }
245
246 static void linphone_gtk_display_log(OrtpLogLevel lev, const char *msg){
247         GtkTextIter iter,begin;
248         int off;
249         static GtkTextView *v=NULL;
250         GtkTextBuffer *b;
251         const char *lname="undef";
252
253         if (log_window==NULL) {
254                 return;
255         }
256
257         if (v==NULL) v=GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview"));
258         b=gtk_text_view_get_buffer(v);
259         switch(lev){
260                 case ORTP_DEBUG:
261                         lname="debug";
262                         break;
263                 case ORTP_MESSAGE:
264                         lname="message";
265                         break;
266                 case ORTP_WARNING:
267                         lname="warning";
268                         break;
269                 case ORTP_ERROR:
270                         lname="error";
271                         break;
272                 case ORTP_FATAL:
273                         lname="fatal";
274                         break;
275                 default:
276                         g_error("Bad level !");
277         }
278         gtk_text_buffer_get_end_iter(b,&iter);
279         off=gtk_text_iter_get_offset(&iter);
280         gtk_text_buffer_insert(b,&iter,lname,-1);
281         gtk_text_buffer_get_end_iter(b,&iter);
282         gtk_text_buffer_insert(b,&iter,": ",-1);
283         gtk_text_buffer_get_end_iter(b,&iter);
284         gtk_text_buffer_insert(b,&iter,msg,-1);
285         gtk_text_buffer_get_end_iter(b,&iter);
286         gtk_text_buffer_insert(b,&iter,"\n",-1);
287         gtk_text_buffer_get_end_iter(b,&iter);
288         gtk_text_buffer_get_iter_at_offset(b,&begin,off);
289         if (lev==ORTP_ERROR || lev==ORTP_FATAL) gtk_text_buffer_apply_tag_by_name(b,"red",&begin,&iter);
290         else if (lev==ORTP_WARNING) gtk_text_buffer_apply_tag_by_name(b,"orange",&begin,&iter);
291         gtk_text_buffer_get_end_iter(b,&iter);
292         //gtk_text_view_scroll_to_iter(v,&iter,0,FALSE,0,0);
293 }
294
295 gboolean linphone_gtk_check_logs(){
296         GList *elem;
297         g_static_mutex_lock(&log_mutex);
298         for(elem=log_queue;elem!=NULL;elem=elem->next){
299                 LinphoneGtkLog *lgl=(LinphoneGtkLog*)elem->data;
300                 linphone_gtk_display_log(lgl->lev,lgl->msg);
301                 g_free(lgl->msg);
302                 g_free(lgl);
303         }
304         if (log_queue) g_list_free(log_queue);
305         log_queue=NULL;
306         g_static_mutex_unlock(&log_mutex);
307         return TRUE;
308 }
309
310 void linphone_gtk_log_push(OrtpLogLevel lev, const char *fmt, va_list args){
311         gchar *msg=g_strdup_vprintf(fmt,args);
312         LinphoneGtkLog *lgl=g_new(LinphoneGtkLog,1);
313         lgl->lev=lev;
314         lgl->msg=msg;
315         g_static_mutex_lock(&log_mutex);
316         log_queue=g_list_append(log_queue,lgl);
317         linphone_gtk_log_file(lev, msg);
318         g_static_mutex_unlock(&log_mutex);
319 }
320