goa: Add missing linker flag (for real).
[platform/upstream/evolution-data-server.git] / libedataserver / e-debug-log.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * e-debug-log.c: Ring buffer for logging debug messages
4  *
5  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * Author: Federico Mena-Quintero <federico@novell.com>
22  */
23
24 #include <config.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <time.h>
29 #include <sys/time.h>
30 #include "e-debug-log.h"
31
32 #define DEFAULT_RING_BUFFER_NUM_LINES 1000
33
34 #define KEY_FILE_GROUP          "debug log"
35 #define KEY_FILE_DOMAINS_KEY    "enable domains"
36 #define KEY_FILE_MAX_LINES_KEY  "max lines"
37
38 static GMutex log_mutex;
39
40 static GHashTable *domains_hash;
41 static gchar **ring_buffer;
42 static gint ring_buffer_next_index;
43 static gint ring_buffer_num_lines;
44 static gint ring_buffer_max_lines = DEFAULT_RING_BUFFER_NUM_LINES;
45
46 static GSList *milestones_head;
47 static GSList *milestones_tail;
48
49 static void
50 lock (void)
51 {
52         g_mutex_lock (&log_mutex);
53 }
54
55 static void
56 unlock (void)
57 {
58         g_mutex_unlock (&log_mutex);
59 }
60
61 /**
62  * e_debug_log:
63  *
64  * Since: 2.32
65  **/
66 void
67 e_debug_log (gboolean is_milestone,
68              const gchar *domain,
69              const gchar *format,
70              ...)
71 {
72         va_list args;
73
74         va_start (args, format);
75         e_debug_logv (is_milestone, domain, format, args);
76         va_end (args);
77 }
78
79 static gboolean
80 is_domain_enabled (const gchar *domain)
81 {
82         /* User actions are always logged */
83         if (strcmp (domain, E_DEBUG_LOG_DOMAIN_USER) == 0)
84                 return TRUE;
85
86         if (!domains_hash)
87                 return FALSE;
88
89         return (g_hash_table_lookup (domains_hash, domain) != NULL);
90 }
91
92 static void
93 ensure_ring (void)
94 {
95         if (ring_buffer)
96                 return;
97
98         ring_buffer = g_new0 (gchar *, ring_buffer_max_lines);
99         ring_buffer_next_index = 0;
100         ring_buffer_num_lines = 0;
101 }
102
103 static void
104 add_to_ring (gchar *str)
105 {
106         ensure_ring ();
107
108         g_assert (str != NULL);
109
110         if (ring_buffer_num_lines == ring_buffer_max_lines) {
111                 /* We have an overlap, and the ring_buffer_next_index pogints to
112                  * the "first" item.  Free it to make room for the new item.
113                  */
114
115                 g_assert (ring_buffer[ring_buffer_next_index] != NULL);
116                 g_free (ring_buffer[ring_buffer_next_index]);
117         } else
118                 ring_buffer_num_lines++;
119
120         g_assert (ring_buffer_num_lines <= ring_buffer_max_lines);
121
122         ring_buffer[ring_buffer_next_index] = str;
123
124         ring_buffer_next_index++;
125         if (ring_buffer_next_index == ring_buffer_max_lines) {
126                 ring_buffer_next_index = 0;
127                 g_assert (ring_buffer_num_lines == ring_buffer_max_lines);
128         }
129 }
130
131 static void
132 add_to_milestones (const gchar *str)
133 {
134         gchar *str_copy;
135
136         str_copy = g_strdup (str);
137
138         if (milestones_tail) {
139                 milestones_tail = g_slist_append (milestones_tail, str_copy);
140                 milestones_tail = milestones_tail->next;
141         } else {
142                 milestones_head = milestones_tail = g_slist_append (NULL, str_copy);
143         }
144
145         g_assert (milestones_head != NULL && milestones_tail != NULL);
146 }
147
148 /**
149  * e_debug_logv:
150  *
151  * Since: 2.32
152  **/
153 void
154 e_debug_logv (gboolean is_milestone,
155               const gchar *domain,
156               const gchar *format,
157               va_list args)
158 {
159         gchar *str;
160         gchar *debug_str;
161         struct timeval tv;
162         struct tm tm;
163
164         lock ();
165
166         if (!(is_milestone || is_domain_enabled (domain)))
167                 goto out;
168
169         str = g_strdup_vprintf (format, args);
170         gettimeofday (&tv, NULL);
171
172         tm = *localtime (&tv.tv_sec);
173
174         debug_str = g_strdup_printf (
175                 "%p;%04d/%02d/%02d;%02d:%02d:%02d.%04d;(%s);%s",
176                 g_thread_self (),
177                 tm.tm_year + 1900,
178                 tm.tm_mon + 1,
179                 tm.tm_mday,
180                 tm.tm_hour,
181                 tm.tm_min,
182                 tm.tm_sec,
183                 (gint) (tv.tv_usec / 100),
184                 domain, str);
185         g_free (str);
186
187         add_to_ring (debug_str);
188         if (is_milestone)
189                 add_to_milestones (debug_str);
190
191  out:
192         unlock ();
193 }
194
195 /**
196  * e_debug_log_load_configuration:
197  *
198  * Since: 2.32
199  **/
200 gboolean
201 e_debug_log_load_configuration (const gchar *filename,
202                                 GError **error)
203 {
204         GKeyFile *key_file;
205         gchar **strings;
206         gsize num_strings;
207         gint num;
208         GError *my_error;
209
210         g_assert (filename != NULL);
211         g_assert (error == NULL || *error == NULL);
212
213         key_file = g_key_file_new ();
214
215         if (!g_key_file_load_from_file (
216                 key_file, filename, G_KEY_FILE_NONE, error)) {
217                 g_key_file_free (key_file);
218                 return FALSE;
219         }
220
221         /* Domains */
222
223         my_error = NULL;
224         strings = g_key_file_get_string_list (
225                 key_file, KEY_FILE_GROUP,
226                 KEY_FILE_DOMAINS_KEY, &num_strings, &my_error);
227         if (my_error)
228                 g_error_free (my_error);
229         else {
230                 gint i;
231
232                 for (i = 0; i < num_strings; i++)
233                         strings[i] = g_strstrip (strings[i]);
234
235                 e_debug_log_enable_domains (
236                         (const gchar **) strings, num_strings);
237                 g_strfreev (strings);
238         }
239
240         /* Number of lines */
241
242         my_error = NULL;
243         num = g_key_file_get_integer (
244                 key_file, KEY_FILE_GROUP,
245                 KEY_FILE_MAX_LINES_KEY, &my_error);
246         if (my_error)
247                 g_error_free (my_error);
248         else
249                 e_debug_log_set_max_lines (num);
250
251         g_key_file_free (key_file);
252         return TRUE;
253 }
254
255 /**
256  * e_debug_log_enable_domains:
257  *
258  * Since: 2.32
259  **/
260 void
261 e_debug_log_enable_domains (const gchar **domains,
262                             gint n_domains)
263 {
264         gint i;
265
266         g_assert (domains != NULL);
267         g_assert (n_domains >= 0);
268
269         lock ();
270
271         if (!domains_hash)
272                 domains_hash = g_hash_table_new (g_str_hash, g_str_equal);
273
274         for (i = 0; i < n_domains; i++) {
275                 g_assert (domains[i] != NULL);
276
277                 if (strcmp (domains[i], E_DEBUG_LOG_DOMAIN_USER) == 0)
278                         continue; /* user actions are always enabled */
279
280                 if (g_hash_table_lookup (domains_hash, domains[i]) == NULL) {
281                         gchar *domain;
282
283                         domain = g_strdup (domains[i]);
284                         g_hash_table_insert (domains_hash, domain, domain);
285                 }
286         }
287
288         unlock ();
289 }
290
291 /**
292  * e_debug_log_disable_domains:
293  *
294  * Since: 2.32
295  **/
296 void
297 e_debug_log_disable_domains (const gchar **domains,
298                              gint n_domains)
299 {
300         gint i;
301
302         g_assert (domains != NULL);
303         g_assert (n_domains >= 0);
304
305         lock ();
306
307         if (domains_hash) {
308                 for (i = 0; i < n_domains; i++) {
309                         gchar *domain;
310
311                         g_assert (domains[i] != NULL);
312
313                         if (strcmp (domains[i], E_DEBUG_LOG_DOMAIN_USER) == 0)
314                                 continue; /* user actions are always enabled */
315
316                         domain = g_hash_table_lookup (domains_hash, domains[i]);
317                         if (domain) {
318                                 g_hash_table_remove (domains_hash, domain);
319                                 g_free (domain);
320                         }
321                 }
322         } /* else, there is nothing to disable */
323
324         unlock ();
325 }
326
327 /**
328  * e_debug_log_is_domain_enabled:
329  *
330  * Since: 2.32
331  **/
332 gboolean
333 e_debug_log_is_domain_enabled (const gchar *domain)
334 {
335         gboolean retval;
336
337         g_assert (domain != NULL);
338
339         lock ();
340         retval = is_domain_enabled (domain);
341         unlock ();
342
343         return retval;
344 }
345
346 struct domains_dump_closure {
347         gchar **domains;
348         gint num_domains;
349 };
350
351 static void
352 domains_foreach_dump_cb (gpointer key,
353                          gpointer value,
354                          gpointer data)
355 {
356         struct domains_dump_closure *closure;
357         gchar *domain;
358
359         closure = data;
360         domain = key;
361
362         closure->domains[closure->num_domains] = domain;
363         closure->num_domains++;
364 }
365
366 static GKeyFile *
367 make_key_file_from_configuration (void)
368 {
369         GKeyFile *key_file;
370         struct domains_dump_closure closure;
371         gint num_domains;
372
373         key_file = g_key_file_new ();
374
375         /* domains */
376
377         if (domains_hash) {
378                 num_domains = g_hash_table_size (domains_hash);
379                 if (num_domains != 0) {
380                         closure.domains = g_new (gchar *, num_domains);
381                         closure.num_domains = 0;
382
383                         g_hash_table_foreach (
384                                 domains_hash,
385                                 domains_foreach_dump_cb,
386                                 &closure);
387                         g_assert (num_domains == closure.num_domains);
388
389                         g_key_file_set_string_list (
390                                 key_file,
391                                 KEY_FILE_GROUP,
392                                 KEY_FILE_DOMAINS_KEY,
393                                 (const gchar * const *) closure.domains,
394                                 closure.num_domains);
395                         g_free (closure.domains);
396                 }
397         }
398
399         /* max lines */
400
401         g_key_file_set_integer (
402                 key_file, KEY_FILE_GROUP,
403                 KEY_FILE_MAX_LINES_KEY, ring_buffer_max_lines);
404
405         return key_file;
406 }
407
408 static gboolean
409 write_string (const gchar *filename,
410               FILE *file,
411               const gchar *str,
412               GError **error)
413 {
414         if (fputs (str, file) == EOF) {
415                 gint saved_errno;
416
417                 saved_errno = errno;
418                 g_set_error (
419                         error,
420                         G_FILE_ERROR,
421                         g_file_error_from_errno (saved_errno),
422                         "error when writing to log file %s", filename);
423
424                 return FALSE;
425         }
426
427         return TRUE;
428 }
429
430 static gboolean
431 dump_configuration (const gchar *filename,
432                     FILE *file,
433                     GError **error)
434 {
435         GKeyFile *key_file;
436         gchar *data;
437         gsize length;
438         gboolean success;
439
440         if (!write_string (
441                 filename, file,
442                 "\n\n"
443                 "This configuration for the debug log can be re-created\n"
444                 "by putting the following in ~/evolution-data-server-debug-log.conf\n"
445                 "(use ';' to separate domain names):\n\n", error)) {
446                 return FALSE;
447         }
448
449         success = FALSE;
450
451         key_file = make_key_file_from_configuration ();
452
453         data = g_key_file_to_data (key_file, &length, error);
454         if (!data)
455                 goto out;
456
457         if (!write_string (filename, file, data, error)) {
458                 goto out;
459         }
460
461         success = TRUE;
462  out:
463         g_key_file_free (key_file);
464         return success;
465 }
466
467 static gboolean
468 dump_milestones (const gchar *filename,
469                  FILE *file,
470                  GError **error)
471 {
472         GSList *l;
473
474         if (!write_string (filename, file, "===== BEGIN MILESTONES =====\n", error))
475                 return FALSE;
476
477         for (l = milestones_head; l; l = l->next) {
478                 const gchar *str;
479
480                 str = l->data;
481                 if (!(write_string (filename, file, str, error)
482                       && write_string (filename, file, "\n", error)))
483                         return FALSE;
484         }
485
486         if (!write_string (filename, file, "===== END MILESTONES =====\n", error))
487                 return FALSE;
488
489         return TRUE;
490 }
491
492 static gboolean
493 dump_ring_buffer (const gchar *filename,
494                   FILE *file,
495                   GError **error)
496 {
497         gint start_index;
498         gint i;
499
500         if (!write_string (filename, file, "===== BEGIN RING BUFFER =====\n", error))
501                 return FALSE;
502
503         if (ring_buffer_num_lines == ring_buffer_max_lines)
504                 start_index = ring_buffer_next_index;
505         else
506                 start_index = 0;
507
508         for (i = 0; i < ring_buffer_num_lines; i++) {
509                 gint idx;
510
511                 idx = (start_index + i) % ring_buffer_max_lines;
512
513                 if (!(write_string (filename, file, ring_buffer[idx], error)
514                       && write_string (filename, file, "\n", error))) {
515                         return FALSE;
516                 }
517         }
518
519         if (!write_string (filename, file, "===== END RING BUFFER =====\n", error))
520                 return FALSE;
521
522         return TRUE;
523 }
524
525 /**
526  * e_debug_log_dump:
527  *
528  * Since: 2.32
529  **/
530 gboolean
531 e_debug_log_dump (const gchar *filename,
532                   GError **error)
533 {
534         FILE *file;
535         gboolean success;
536
537         g_assert (error == NULL || *error == NULL);
538
539         lock ();
540
541         success = FALSE;
542
543         file = fopen (filename, "w");
544         if (!file) {
545                 gint saved_errno;
546
547                 saved_errno = errno;
548                 g_set_error (
549                         error,
550                         G_FILE_ERROR,
551                         g_file_error_from_errno (saved_errno),
552                         "could not open log file %s", filename);
553                 goto out;
554         }
555
556         if (!(dump_milestones (filename, file, error)
557               && dump_ring_buffer (filename, file, error)
558               && dump_configuration (filename, file, error))) {
559                 goto do_close;
560         }
561
562         success = TRUE;
563
564  do_close:
565
566         if (fclose (file) != 0) {
567                 gint saved_errno;
568
569                 saved_errno = errno;
570
571                 if (error && *error) {
572                         g_error_free (*error);
573                         *error = NULL;
574                 }
575
576                 g_set_error (
577                         error,
578                         G_FILE_ERROR,
579                         g_file_error_from_errno (saved_errno),
580                         "error when closing log file %s", filename);
581                 success = FALSE;
582         }
583
584  out:
585
586         unlock ();
587         return success;
588 }
589
590 /**
591  * e_debug_log_dump_to_dated_file:
592  *
593  * Since: 2.32
594  **/
595 gboolean
596 e_debug_log_dump_to_dated_file (GError **error)
597 {
598         time_t t;
599         struct tm tm;
600         gchar *basename;
601         gchar *filename;
602         gboolean retval;
603
604         g_assert (error == NULL || *error == NULL);
605
606         t = time (NULL);
607         tm = *localtime (&t);
608
609         basename = g_strdup_printf (
610                 "e-debug-log-%04d-%02d-%02d-%02d-%02d-%02d.txt",
611                 tm.tm_year + 1900,
612                 tm.tm_mon + 1,
613                 tm.tm_mday,
614                 tm.tm_hour,
615                 tm.tm_min,
616                 tm.tm_sec);
617         filename = g_build_filename (g_get_home_dir (), basename, NULL);
618
619         retval = e_debug_log_dump (filename, error);
620
621         g_free (basename);
622         g_free (filename);
623
624         return retval;
625 }
626
627 /**
628  * e_debug_log_set_max_lines:
629  *
630  * Since: 2.32
631  **/
632 void
633 e_debug_log_set_max_lines (gint num_lines)
634 {
635         gchar **new_buffer;
636         gint lines_to_copy;
637
638         g_assert (num_lines > 0);
639
640         lock ();
641
642         if (num_lines == ring_buffer_max_lines)
643                 goto out;
644
645         new_buffer = g_new0 (gchar *, num_lines);
646
647         lines_to_copy = MIN (num_lines, ring_buffer_num_lines);
648
649         if (ring_buffer) {
650                 gint start_index;
651                 gint i;
652
653                 if (ring_buffer_num_lines == ring_buffer_max_lines)
654                         start_index =
655                                 (ring_buffer_next_index +
656                                 ring_buffer_max_lines - lines_to_copy) %
657                                 ring_buffer_max_lines;
658                 else
659                         start_index = ring_buffer_num_lines - lines_to_copy;
660
661                 g_assert (start_index >= 0 && start_index < ring_buffer_max_lines);
662
663                 for (i = 0; i < lines_to_copy; i++) {
664                         gint idx;
665
666                         idx = (start_index + i) % ring_buffer_max_lines;
667
668                         new_buffer[i] = ring_buffer[idx];
669                         ring_buffer[idx] = NULL;
670                 }
671
672                 for (i = 0; i < ring_buffer_max_lines; i++)
673                         g_free (ring_buffer[i]);
674
675                 g_free (ring_buffer);
676         }
677
678         ring_buffer = new_buffer;
679         ring_buffer_next_index = lines_to_copy;
680         ring_buffer_num_lines = lines_to_copy;
681         ring_buffer_max_lines = num_lines;
682
683  out:
684
685         unlock ();
686 }
687
688 /**
689  * e_debug_log_get_max_lines:
690  *
691  * Since: 2.32
692  **/
693 gint
694 e_debug_log_get_max_lines (void)
695 {
696         gint retval;
697
698         lock ();
699         retval = ring_buffer_max_lines;
700         unlock ();
701
702         return retval;
703 }
704
705 /**
706  * e_debug_log_clear:
707  *
708  * Since: 2.32
709  **/
710 void
711 e_debug_log_clear (void)
712 {
713         gint i;
714
715         lock ();
716
717         if (!ring_buffer)
718                 goto out;
719
720         for (i = 0; i < ring_buffer_max_lines; i++) {
721                 g_free (ring_buffer[i]);
722                 ring_buffer[i] = NULL;
723         }
724
725         ring_buffer_next_index = 0;
726         ring_buffer_num_lines = 0;
727
728  out:
729         unlock ();
730 }
731