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