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