2008-02-29 Cosimo Cecchi <cosimoc@gnome.org>
[nautilus.git] / libnautilus-private / nautilus-debug-log.c
blobf4ce3204755e6e9e76f021ca575cd353797d8551
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
3 nautilus-debug-log.c: Ring buffer for logging debug messages
5 Copyright (C) 2006 Novell, Inc.
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public
18 License along with this program; if not, write to the
19 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
22 Author: Federico Mena-Quintero <federico@novell.com>
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 <eel/eel-glib-extensions.h>
31 #include "nautilus-debug-log.h"
32 #include "nautilus-file.h"
34 #define DEFAULT_RING_BUFFER_NUM_LINES 1000
36 #define KEY_FILE_GROUP "debug log"
37 #define KEY_FILE_DOMAINS_KEY "enable domains"
38 #define KEY_FILE_MAX_LINES_KEY "max lines"
40 static GStaticMutex log_mutex = G_STATIC_MUTEX_INIT;
42 static GHashTable *domains_hash;
43 static char **ring_buffer;
44 static int ring_buffer_next_index;
45 static int ring_buffer_num_lines;
46 static int ring_buffer_max_lines = DEFAULT_RING_BUFFER_NUM_LINES;
48 static GSList *milestones_head;
49 static GSList *milestones_tail;
51 static void
52 lock (void)
54 g_static_mutex_lock (&log_mutex);
57 static void
58 unlock (void)
60 g_static_mutex_unlock (&log_mutex);
63 void
64 nautilus_debug_log (gboolean is_milestone, const char *domain, const char *format, ...)
66 va_list args;
68 va_start (args, format);
69 nautilus_debug_logv (is_milestone, domain, NULL, format, args);
70 va_end (args);
73 static gboolean
74 is_domain_enabled (const char *domain)
76 /* User actions are always logged */
77 if (strcmp (domain, NAUTILUS_DEBUG_LOG_DOMAIN_USER) == 0)
78 return TRUE;
80 if (!domains_hash)
81 return FALSE;
83 return (g_hash_table_lookup (domains_hash, domain) != NULL);
86 static void
87 ensure_ring (void)
89 if (ring_buffer)
90 return;
92 ring_buffer = g_new0 (char *, ring_buffer_max_lines);
93 ring_buffer_next_index = 0;
94 ring_buffer_num_lines = 0;
97 static void
98 add_to_ring (char *str)
100 ensure_ring ();
102 g_assert (str != NULL);
104 if (ring_buffer_num_lines == ring_buffer_max_lines) {
105 /* We have an overlap, and the ring_buffer_next_index points to
106 * the "first" item. Free it to make room for the new item.
109 g_assert (ring_buffer[ring_buffer_next_index] != NULL);
110 g_free (ring_buffer[ring_buffer_next_index]);
111 } else
112 ring_buffer_num_lines++;
114 g_assert (ring_buffer_num_lines <= ring_buffer_max_lines);
116 ring_buffer[ring_buffer_next_index] = str;
118 ring_buffer_next_index++;
119 if (ring_buffer_next_index == ring_buffer_max_lines) {
120 ring_buffer_next_index = 0;
121 g_assert (ring_buffer_num_lines == ring_buffer_max_lines);
125 static void
126 add_to_milestones (const char *str)
128 char *str_copy;
130 str_copy = g_strdup (str);
132 if (milestones_tail) {
133 milestones_tail = g_slist_append (milestones_tail, str_copy);
134 milestones_tail = milestones_tail->next;
135 } else {
136 milestones_head = milestones_tail = g_slist_append (NULL, str_copy);
139 g_assert (milestones_head != NULL && milestones_tail != NULL);
142 void
143 nautilus_debug_logv (gboolean is_milestone, const char *domain, const GList *uris, const char *format, va_list args)
145 char *str;
146 char *debug_str;
147 struct timeval tv;
148 struct tm tm;
150 lock ();
152 if (!(is_milestone || is_domain_enabled (domain)))
153 goto out;
155 str = g_strdup_vprintf (format, args);
156 gettimeofday (&tv, NULL);
158 tm = *localtime (&tv.tv_sec);
160 debug_str = g_strdup_printf ("%p %04d/%02d/%02d %02d:%02d:%02d.%04d (%s): %s",
161 g_thread_self (),
162 tm.tm_year + 1900,
163 tm.tm_mon + 1,
164 tm.tm_mday,
165 tm.tm_hour,
166 tm.tm_min,
167 tm.tm_sec,
168 (int) (tv.tv_usec / 100),
169 domain,
170 str);
171 g_free (str);
173 if (uris) {
174 int debug_str_len;
175 int uris_len;
176 const GList *l;
177 char *new_str;
178 char *p;
180 uris_len = 0;
182 for (l = uris; l; l = l->next) {
183 const char *uri;
185 uri = l->data;
186 uris_len += strlen (uri) + 2; /* plus 2 for a tab and the newline */
189 debug_str_len = strlen (debug_str);
190 new_str = g_new (char, debug_str_len + 1 + uris_len); /* plus 1 for newline */
192 p = g_stpcpy (new_str, debug_str);
193 *p++ = '\n';
195 for (l = uris; l; l = l->next) {
196 const char *uri;
198 uri = l->data;
200 *p++ = '\t';
202 p = g_stpcpy (p, uri);
204 if (l->next)
205 *p++ = '\n';
208 g_free (debug_str);
209 debug_str = new_str;
212 add_to_ring (debug_str);
213 if (is_milestone)
214 add_to_milestones (debug_str);
216 out:
217 unlock ();
220 void
221 nautilus_debug_log_with_uri_list (gboolean is_milestone, const char *domain, const GList *uris,
222 const char *format, ...)
224 va_list args;
226 va_start (args, format);
227 nautilus_debug_logv (is_milestone, domain, uris, format, args);
228 va_end (args);
231 void
232 nautilus_debug_log_with_file_list (gboolean is_milestone, const char *domain, GList *files,
233 const char *format, ...)
235 va_list args;
236 GList *uris;
237 GList *l;
239 uris = NULL;
241 for (l = files; l; l = l->next) {
242 NautilusFile *file;
243 char *uri;
245 file = NAUTILUS_FILE (l->data);
246 uri = nautilus_file_get_uri (file);
248 if (nautilus_file_is_gone (file)) {
249 char *new_uri;
251 /* Hack: this will create an invalid URI, but it's for
252 * display purposes only.
254 new_uri = g_strconcat (uri ? uri : "", " (gone)", NULL);
255 g_free (uri);
256 uri = new_uri;
258 uris = g_list_prepend (uris, uri);
261 uris = g_list_reverse (uris);
263 va_start (args, format);
264 nautilus_debug_logv (is_milestone, domain, uris, format, args);
265 va_end (args);
267 eel_g_list_free_deep (uris);
270 gboolean
271 nautilus_debug_log_load_configuration (const char *filename, GError **error)
273 GKeyFile *key_file;
274 char **strings;
275 gsize num_strings;
276 int num;
277 GError *my_error;
279 g_assert (filename != NULL);
280 g_assert (error == NULL || *error == NULL);
282 key_file = g_key_file_new ();
284 if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error)) {
285 g_key_file_free (key_file);
286 return FALSE;
289 /* Domains */
291 my_error = NULL;
292 strings = g_key_file_get_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY, &num_strings, &my_error);
293 if (my_error)
294 g_error_free (my_error);
295 else {
296 int i;
298 for (i = 0; i < num_strings; i++)
299 strings[i] = g_strstrip (strings[i]);
301 nautilus_debug_log_enable_domains ((const char **) strings, num_strings);
302 g_strfreev (strings);
305 /* Number of lines */
307 my_error = NULL;
308 num = g_key_file_get_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, &my_error);
309 if (my_error)
310 g_error_free (my_error);
311 else
312 nautilus_debug_log_set_max_lines (num);
314 g_key_file_free (key_file);
315 return TRUE;
318 void
319 nautilus_debug_log_enable_domains (const char **domains, int n_domains)
321 int i;
323 g_assert (domains != NULL);
324 g_assert (n_domains >= 0);
326 lock ();
328 if (!domains_hash)
329 domains_hash = g_hash_table_new (g_str_hash, g_str_equal);
331 for (i = 0; i < n_domains; i++) {
332 g_assert (domains[i] != NULL);
334 if (strcmp (domains[i], NAUTILUS_DEBUG_LOG_DOMAIN_USER) == 0)
335 continue; /* user actions are always enabled */
337 if (g_hash_table_lookup (domains_hash, domains[i]) == NULL) {
338 char *domain;
340 domain = g_strdup (domains[i]);
341 g_hash_table_insert (domains_hash, domain, domain);
345 unlock ();
348 void
349 nautilus_debug_log_disable_domains (const char **domains, int n_domains)
351 int i;
353 g_assert (domains != NULL);
354 g_assert (n_domains >= 0);
356 lock ();
358 if (domains_hash) {
359 for (i = 0; i < n_domains; i++) {
360 char *domain;
362 g_assert (domains[i] != NULL);
364 if (strcmp (domains[i], NAUTILUS_DEBUG_LOG_DOMAIN_USER) == 0)
365 continue; /* user actions are always enabled */
367 domain = g_hash_table_lookup (domains_hash, domains[i]);
368 if (domain) {
369 g_hash_table_remove (domains_hash, domain);
370 g_free (domain);
373 } /* else, there is nothing to disable */
375 unlock ();
378 gboolean
379 nautilus_debug_log_is_domain_enabled (const char *domain)
381 gboolean retval;
383 g_assert (domain != NULL);
385 lock ();
386 retval = is_domain_enabled (domain);
387 unlock ();
389 return retval;
392 struct domains_dump_closure {
393 char **domains;
394 int num_domains;
397 static void
398 domains_foreach_dump_cb (gpointer key, gpointer value, gpointer data)
400 struct domains_dump_closure *closure;
401 char *domain;
403 closure = data;
404 domain = key;
406 closure->domains[closure->num_domains] = domain;
407 closure->num_domains++;
410 static GKeyFile *
411 make_key_file_from_configuration (void)
413 GKeyFile *key_file;
414 struct domains_dump_closure closure;
415 int num_domains;
417 key_file = g_key_file_new ();
419 /* domains */
421 if (domains_hash) {
422 num_domains = g_hash_table_size (domains_hash);
423 if (num_domains != 0) {
424 closure.domains = g_new (char *, num_domains);
425 closure.num_domains = 0;
427 g_hash_table_foreach (domains_hash, domains_foreach_dump_cb, &closure);
428 g_assert (num_domains == closure.num_domains);
430 g_key_file_set_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY,
431 (const gchar * const *) closure.domains, closure.num_domains);
432 g_free (closure.domains);
436 /* max lines */
438 g_key_file_set_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, ring_buffer_max_lines);
440 return key_file;
443 static gboolean
444 write_string (const char *filename, FILE *file, const char *str, GError **error)
446 if (fputs (str, file) == EOF) {
447 int saved_errno;
449 saved_errno = errno;
450 g_set_error (error,
451 G_FILE_ERROR,
452 g_file_error_from_errno (saved_errno),
453 "error when writing to log file %s", filename);
455 return FALSE;
458 return TRUE;
461 static gboolean
462 dump_configuration (const char *filename, FILE *file, GError **error)
464 GKeyFile *key_file;
465 char *data;
466 gsize length;
467 gboolean success;
469 if (!write_string (filename, file,
470 "\n\n"
471 "This configuration for the debug log can be re-created\n"
472 "by putting the following in ~/nautilus-debug-log.conf\n"
473 "(use ';' to separate domain names):\n\n",
474 error)) {
475 return FALSE;
478 success = FALSE;
480 key_file = make_key_file_from_configuration ();
482 data = g_key_file_to_data (key_file, &length, error);
483 if (!data)
484 goto out;
486 if (!write_string (filename, file, data, error)) {
487 goto out;
490 success = TRUE;
491 out:
492 g_key_file_free (key_file);
493 return success;
496 static gboolean
497 dump_milestones (const char *filename, FILE *file, GError **error)
499 GSList *l;
501 if (!write_string (filename, file, "===== BEGIN MILESTONES =====\n", error))
502 return FALSE;
504 for (l = milestones_head; l; l = l->next) {
505 const char *str;
507 str = l->data;
508 if (!(write_string (filename, file, str, error)
509 && write_string (filename, file, "\n", error)))
510 return FALSE;
513 if (!write_string (filename, file, "===== END MILESTONES =====\n", error))
514 return FALSE;
516 return TRUE;
519 static gboolean
520 dump_ring_buffer (const char *filename, FILE *file, GError **error)
522 int start_index;
523 int i;
525 if (!write_string (filename, file, "===== BEGIN RING BUFFER =====\n", error))
526 return FALSE;
528 if (ring_buffer_num_lines == ring_buffer_max_lines)
529 start_index = ring_buffer_next_index;
530 else
531 start_index = 0;
533 for (i = 0; i < ring_buffer_num_lines; i++) {
534 int idx;
536 idx = (start_index + i) % ring_buffer_max_lines;
538 if (!(write_string (filename, file, ring_buffer[idx], error)
539 && write_string (filename, file, "\n", error))) {
540 return FALSE;
544 if (!write_string (filename, file, "===== END RING BUFFER =====\n", error))
545 return FALSE;
547 return TRUE;
550 gboolean
551 nautilus_debug_log_dump (const char *filename, GError **error)
553 FILE *file;
554 gboolean success;
556 g_assert (error == NULL || *error == NULL);
558 lock ();
560 success = FALSE;
562 file = fopen (filename, "w");
563 if (!file) {
564 int saved_errno;
566 saved_errno = errno;
567 g_set_error (error,
568 G_FILE_ERROR,
569 g_file_error_from_errno (saved_errno),
570 "could not open log file %s", filename);
571 goto out;
574 if (!(dump_milestones (filename, file, error)
575 && dump_ring_buffer (filename, file, error)
576 && dump_configuration (filename, file, error))) {
577 goto do_close;
580 success = TRUE;
582 do_close:
584 if (fclose (file) != 0) {
585 int saved_errno;
587 saved_errno = errno;
589 if (error && *error) {
590 g_error_free (*error);
591 *error = NULL;
594 g_set_error (error,
595 G_FILE_ERROR,
596 g_file_error_from_errno (saved_errno),
597 "error when closing log file %s", filename);
598 success = FALSE;
601 out:
603 unlock ();
604 return success;
607 void
608 nautilus_debug_log_set_max_lines (int num_lines)
610 char **new_buffer;
611 int lines_to_copy;
613 g_assert (num_lines > 0);
615 lock ();
617 if (num_lines == ring_buffer_max_lines)
618 goto out;
620 new_buffer = g_new0 (char *, num_lines);
622 lines_to_copy = MIN (num_lines, ring_buffer_num_lines);
624 if (ring_buffer) {
625 int start_index;
626 int i;
628 if (ring_buffer_num_lines == ring_buffer_max_lines)
629 start_index = (ring_buffer_next_index + ring_buffer_max_lines - lines_to_copy) % ring_buffer_max_lines;
630 else
631 start_index = ring_buffer_num_lines - lines_to_copy;
633 g_assert (start_index >= 0 && start_index < ring_buffer_max_lines);
635 for (i = 0; i < lines_to_copy; i++) {
636 int idx;
638 idx = (start_index + i) % ring_buffer_max_lines;
640 new_buffer[i] = ring_buffer[idx];
641 ring_buffer[idx] = NULL;
644 for (i = 0; i < ring_buffer_max_lines; i++)
645 g_free (ring_buffer[i]);
647 g_free (ring_buffer);
650 ring_buffer = new_buffer;
651 ring_buffer_next_index = lines_to_copy;
652 ring_buffer_num_lines = lines_to_copy;
653 ring_buffer_max_lines = num_lines;
655 out:
657 unlock ();
661 nautilus_debug_log_get_max_lines (void)
663 int retval;
665 lock ();
666 retval = ring_buffer_max_lines;
667 unlock ();
669 return retval;
672 void
673 nautilus_debug_log_clear (void)
675 int i;
677 lock ();
679 if (!ring_buffer)
680 goto out;
682 for (i = 0; i < ring_buffer_max_lines; i++) {
683 g_free (ring_buffer[i]);
684 ring_buffer[i] = NULL;
687 ring_buffer_next_index = 0;
688 ring_buffer_num_lines = 0;
690 out:
691 unlock ();