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>
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
;
54 g_static_mutex_lock (&log_mutex
);
60 g_static_mutex_unlock (&log_mutex
);
64 nautilus_debug_log (gboolean is_milestone
, const char *domain
, const char *format
, ...)
68 va_start (args
, format
);
69 nautilus_debug_logv (is_milestone
, domain
, NULL
, format
, args
);
74 is_domain_enabled (const char *domain
)
76 /* User actions are always logged */
77 if (strcmp (domain
, NAUTILUS_DEBUG_LOG_DOMAIN_USER
) == 0)
83 return (g_hash_table_lookup (domains_hash
, domain
) != NULL
);
92 ring_buffer
= g_new0 (char *, ring_buffer_max_lines
);
93 ring_buffer_next_index
= 0;
94 ring_buffer_num_lines
= 0;
98 add_to_ring (char *str
)
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
]);
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
);
126 add_to_milestones (const char *str
)
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
;
136 milestones_head
= milestones_tail
= g_slist_append (NULL
, str_copy
);
139 g_assert (milestones_head
!= NULL
&& milestones_tail
!= NULL
);
143 nautilus_debug_logv (gboolean is_milestone
, const char *domain
, const GList
*uris
, const char *format
, va_list args
)
152 if (!(is_milestone
|| is_domain_enabled (domain
)))
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",
168 (int) (tv
.tv_usec
/ 100),
182 for (l
= uris
; l
; l
= l
->next
) {
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
);
195 for (l
= uris
; l
; l
= l
->next
) {
202 p
= g_stpcpy (p
, uri
);
212 add_to_ring (debug_str
);
214 add_to_milestones (debug_str
);
221 nautilus_debug_log_with_uri_list (gboolean is_milestone
, const char *domain
, const GList
*uris
,
222 const char *format
, ...)
226 va_start (args
, format
);
227 nautilus_debug_logv (is_milestone
, domain
, uris
, format
, args
);
232 nautilus_debug_log_with_file_list (gboolean is_milestone
, const char *domain
, GList
*files
,
233 const char *format
, ...)
241 for (l
= files
; l
; l
= l
->next
) {
245 file
= NAUTILUS_FILE (l
->data
);
246 uri
= nautilus_file_get_uri (file
);
248 if (nautilus_file_is_gone (file
)) {
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
);
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
);
267 eel_g_list_free_deep (uris
);
271 nautilus_debug_log_load_configuration (const char *filename
, GError
**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
);
292 strings
= g_key_file_get_string_list (key_file
, KEY_FILE_GROUP
, KEY_FILE_DOMAINS_KEY
, &num_strings
, &my_error
);
294 g_error_free (my_error
);
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 */
308 num
= g_key_file_get_integer (key_file
, KEY_FILE_GROUP
, KEY_FILE_MAX_LINES_KEY
, &my_error
);
310 g_error_free (my_error
);
312 nautilus_debug_log_set_max_lines (num
);
314 g_key_file_free (key_file
);
319 nautilus_debug_log_enable_domains (const char **domains
, int n_domains
)
323 g_assert (domains
!= NULL
);
324 g_assert (n_domains
>= 0);
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
) {
340 domain
= g_strdup (domains
[i
]);
341 g_hash_table_insert (domains_hash
, domain
, domain
);
349 nautilus_debug_log_disable_domains (const char **domains
, int n_domains
)
353 g_assert (domains
!= NULL
);
354 g_assert (n_domains
>= 0);
359 for (i
= 0; i
< n_domains
; i
++) {
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
]);
369 g_hash_table_remove (domains_hash
, domain
);
373 } /* else, there is nothing to disable */
379 nautilus_debug_log_is_domain_enabled (const char *domain
)
383 g_assert (domain
!= NULL
);
386 retval
= is_domain_enabled (domain
);
392 struct domains_dump_closure
{
398 domains_foreach_dump_cb (gpointer key
, gpointer value
, gpointer data
)
400 struct domains_dump_closure
*closure
;
406 closure
->domains
[closure
->num_domains
] = domain
;
407 closure
->num_domains
++;
411 make_key_file_from_configuration (void)
414 struct domains_dump_closure closure
;
417 key_file
= g_key_file_new ();
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
);
438 g_key_file_set_integer (key_file
, KEY_FILE_GROUP
, KEY_FILE_MAX_LINES_KEY
, ring_buffer_max_lines
);
444 write_string (const char *filename
, FILE *file
, const char *str
, GError
**error
)
446 if (fputs (str
, file
) == EOF
) {
452 g_file_error_from_errno (saved_errno
),
453 "error when writing to log file %s", filename
);
462 dump_configuration (const char *filename
, FILE *file
, GError
**error
)
469 if (!write_string (filename
, file
,
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",
480 key_file
= make_key_file_from_configuration ();
482 data
= g_key_file_to_data (key_file
, &length
, error
);
486 if (!write_string (filename
, file
, data
, error
)) {
492 g_key_file_free (key_file
);
497 dump_milestones (const char *filename
, FILE *file
, GError
**error
)
501 if (!write_string (filename
, file
, "===== BEGIN MILESTONES =====\n", error
))
504 for (l
= milestones_head
; l
; l
= l
->next
) {
508 if (!(write_string (filename
, file
, str
, error
)
509 && write_string (filename
, file
, "\n", error
)))
513 if (!write_string (filename
, file
, "===== END MILESTONES =====\n", error
))
520 dump_ring_buffer (const char *filename
, FILE *file
, GError
**error
)
525 if (!write_string (filename
, file
, "===== BEGIN RING BUFFER =====\n", error
))
528 if (ring_buffer_num_lines
== ring_buffer_max_lines
)
529 start_index
= ring_buffer_next_index
;
533 for (i
= 0; i
< ring_buffer_num_lines
; i
++) {
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
))) {
544 if (!write_string (filename
, file
, "===== END RING BUFFER =====\n", error
))
551 nautilus_debug_log_dump (const char *filename
, GError
**error
)
556 g_assert (error
== NULL
|| *error
== NULL
);
562 file
= fopen (filename
, "w");
569 g_file_error_from_errno (saved_errno
),
570 "could not open log file %s", filename
);
574 if (!(dump_milestones (filename
, file
, error
)
575 && dump_ring_buffer (filename
, file
, error
)
576 && dump_configuration (filename
, file
, error
))) {
584 if (fclose (file
) != 0) {
589 if (error
&& *error
) {
590 g_error_free (*error
);
596 g_file_error_from_errno (saved_errno
),
597 "error when closing log file %s", filename
);
608 nautilus_debug_log_set_max_lines (int num_lines
)
613 g_assert (num_lines
> 0);
617 if (num_lines
== ring_buffer_max_lines
)
620 new_buffer
= g_new0 (char *, num_lines
);
622 lines_to_copy
= MIN (num_lines
, ring_buffer_num_lines
);
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
;
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
++) {
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
;
661 nautilus_debug_log_get_max_lines (void)
666 retval
= ring_buffer_max_lines
;
673 nautilus_debug_log_clear (void)
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;