summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-debug-log.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private/caja-debug-log.c')
-rw-r--r--libcaja-private/caja-debug-log.c760
1 files changed, 760 insertions, 0 deletions
diff --git a/libcaja-private/caja-debug-log.c b/libcaja-private/caja-debug-log.c
new file mode 100644
index 00000000..66192ced
--- /dev/null
+++ b/libcaja-private/caja-debug-log.c
@@ -0,0 +1,760 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-debug-log.c: Ring buffer for logging debug messages
+
+ Copyright (C) 2006 Novell, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Federico Mena-Quintero <[email protected]>
+*/
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <eel/eel-glib-extensions.h>
+#include "caja-debug-log.h"
+#include "caja-file.h"
+
+#define DEFAULT_RING_BUFFER_NUM_LINES 1000
+
+#define KEY_FILE_GROUP "debug log"
+#define KEY_FILE_DOMAINS_KEY "enable domains"
+#define KEY_FILE_MAX_LINES_KEY "max lines"
+
+#define MAX_URI_COUNT 20
+
+static GStaticMutex log_mutex = G_STATIC_MUTEX_INIT;
+
+static GHashTable *domains_hash;
+static char **ring_buffer;
+static int ring_buffer_next_index;
+static int ring_buffer_num_lines;
+static int ring_buffer_max_lines = DEFAULT_RING_BUFFER_NUM_LINES;
+
+static GSList *milestones_head;
+static GSList *milestones_tail;
+
+static void
+lock (void)
+{
+ g_static_mutex_lock (&log_mutex);
+}
+
+static void
+unlock (void)
+{
+ g_static_mutex_unlock (&log_mutex);
+}
+
+void
+caja_debug_log (gboolean is_milestone, const char *domain, const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, NULL, format, args);
+ va_end (args);
+}
+
+static gboolean
+is_domain_enabled (const char *domain)
+{
+ /* User actions are always logged */
+ if (strcmp (domain, CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ return TRUE;
+
+ if (!domains_hash)
+ return FALSE;
+
+ return (g_hash_table_lookup (domains_hash, domain) != NULL);
+}
+
+static void
+ensure_ring (void)
+{
+ if (ring_buffer)
+ return;
+
+ ring_buffer = g_new0 (char *, ring_buffer_max_lines);
+ ring_buffer_next_index = 0;
+ ring_buffer_num_lines = 0;
+}
+
+static void
+add_to_ring (char *str)
+{
+ ensure_ring ();
+
+ g_assert (str != NULL);
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ {
+ /* We have an overlap, and the ring_buffer_next_index points to
+ * the "first" item. Free it to make room for the new item.
+ */
+
+ g_assert (ring_buffer[ring_buffer_next_index] != NULL);
+ g_free (ring_buffer[ring_buffer_next_index]);
+ }
+ else
+ ring_buffer_num_lines++;
+
+ g_assert (ring_buffer_num_lines <= ring_buffer_max_lines);
+
+ ring_buffer[ring_buffer_next_index] = str;
+
+ ring_buffer_next_index++;
+ if (ring_buffer_next_index == ring_buffer_max_lines)
+ {
+ ring_buffer_next_index = 0;
+ g_assert (ring_buffer_num_lines == ring_buffer_max_lines);
+ }
+}
+
+static void
+add_to_milestones (const char *str)
+{
+ char *str_copy;
+
+ str_copy = g_strdup (str);
+
+ if (milestones_tail)
+ {
+ milestones_tail = g_slist_append (milestones_tail, str_copy);
+ milestones_tail = milestones_tail->next;
+ }
+ else
+ {
+ milestones_head = milestones_tail = g_slist_append (NULL, str_copy);
+ }
+
+ g_assert (milestones_head != NULL && milestones_tail != NULL);
+}
+
+void
+caja_debug_logv (gboolean is_milestone, const char *domain, const GList *uris, const char *format, va_list args)
+{
+ char *str;
+ char *debug_str;
+ struct timeval tv;
+ struct tm tm;
+
+ lock ();
+
+ if (!(is_milestone || is_domain_enabled (domain)))
+ goto out;
+
+ str = g_strdup_vprintf (format, args);
+ gettimeofday (&tv, NULL);
+
+ tm = *localtime (&tv.tv_sec);
+
+ debug_str = g_strdup_printf ("%p %04d/%02d/%02d %02d:%02d:%02d.%04d (%s): %s",
+ g_thread_self (),
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ (int) (tv.tv_usec / 100),
+ domain,
+ str);
+ g_free (str);
+
+ if (uris)
+ {
+ int debug_str_len;
+ int uris_len;
+ const GList *l;
+ char *new_str;
+ char *p;
+ int count;
+
+ uris_len = 0;
+
+ count = 0;
+ for (l = uris; l; l = l->next)
+ {
+ const char *uri;
+
+ uri = l->data;
+ uris_len += strlen (uri) + 2; /* plus 2 for a tab and the newline */
+
+ if (count++ > MAX_URI_COUNT)
+ {
+ uris_len += 4; /* "...\n" */
+ break;
+ }
+
+ }
+
+ debug_str_len = strlen (debug_str);
+ new_str = g_new (char, debug_str_len + 1 + uris_len + 1); /* plus 1 for newline & zero */
+
+ p = g_stpcpy (new_str, debug_str);
+ *p++ = '\n';
+
+ count = 0;
+ for (l = uris; l; l = l->next)
+ {
+ const char *uri;
+
+ uri = l->data;
+
+ *p++ = '\t';
+
+ p = g_stpcpy (p, uri);
+
+ if (l->next)
+ *p++ = '\n';
+
+ if (count++ > MAX_URI_COUNT)
+ {
+ p = g_stpcpy (p, "...\n");
+ break;
+ }
+ }
+
+ g_free (debug_str);
+ debug_str = new_str;
+ }
+
+ add_to_ring (debug_str);
+ if (is_milestone)
+ add_to_milestones (debug_str);
+
+out:
+ unlock ();
+}
+
+void
+caja_debug_log_with_uri_list (gboolean is_milestone, const char *domain, const GList *uris,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, uris, format, args);
+ va_end (args);
+}
+
+void
+caja_debug_log_with_file_list (gboolean is_milestone, const char *domain, GList *files,
+ const char *format, ...)
+{
+ va_list args;
+ GList *uris;
+ GList *l;
+ int count;
+
+ /* Avoid conversion if debugging disabled */
+ if (!(is_milestone ||
+ caja_debug_log_is_domain_enabled (domain)))
+ {
+ return;
+ }
+
+ uris = NULL;
+
+ count = 0;
+ for (l = files; l; l = l->next)
+ {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+ uri = caja_file_get_uri (file);
+
+ if (caja_file_is_gone (file))
+ {
+ char *new_uri;
+
+ /* Hack: this will create an invalid URI, but it's for
+ * display purposes only.
+ */
+ new_uri = g_strconcat (uri ? uri : "", " (gone)", NULL);
+ g_free (uri);
+ uri = new_uri;
+ }
+ uris = g_list_prepend (uris, uri);
+
+ /* Avoid doing to much work, more than MAX_URI_COUNT uris
+ won't be shown anyway */
+ if (count++ > MAX_URI_COUNT + 1)
+ {
+ break;
+ }
+ }
+
+ uris = g_list_reverse (uris);
+
+ va_start (args, format);
+ caja_debug_logv (is_milestone, domain, uris, format, args);
+ va_end (args);
+
+ eel_g_list_free_deep (uris);
+}
+
+gboolean
+caja_debug_log_load_configuration (const char *filename, GError **error)
+{
+ GKeyFile *key_file;
+ char **strings;
+ gsize num_strings;
+ int num;
+ GError *my_error;
+
+ g_assert (filename != NULL);
+ g_assert (error == NULL || *error == NULL);
+
+ key_file = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
+ {
+ g_key_file_free (key_file);
+ return FALSE;
+ }
+
+ /* Domains */
+
+ my_error = NULL;
+ strings = g_key_file_get_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY, &num_strings, &my_error);
+ if (my_error)
+ g_error_free (my_error);
+ else
+ {
+ int i;
+
+ for (i = 0; i < num_strings; i++)
+ strings[i] = g_strstrip (strings[i]);
+
+ caja_debug_log_enable_domains ((const char **) strings, num_strings);
+ g_strfreev (strings);
+ }
+
+ /* Number of lines */
+
+ my_error = NULL;
+ num = g_key_file_get_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, &my_error);
+ if (my_error)
+ g_error_free (my_error);
+ else
+ caja_debug_log_set_max_lines (num);
+
+ g_key_file_free (key_file);
+ return TRUE;
+}
+
+void
+caja_debug_log_enable_domains (const char **domains, int n_domains)
+{
+ int i;
+
+ g_assert (domains != NULL);
+ g_assert (n_domains >= 0);
+
+ lock ();
+
+ if (!domains_hash)
+ domains_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < n_domains; i++)
+ {
+ g_assert (domains[i] != NULL);
+
+ if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ continue; /* user actions are always enabled */
+
+ if (g_hash_table_lookup (domains_hash, domains[i]) == NULL)
+ {
+ char *domain;
+
+ domain = g_strdup (domains[i]);
+ g_hash_table_insert (domains_hash, domain, domain);
+ }
+ }
+
+ unlock ();
+}
+
+void
+caja_debug_log_disable_domains (const char **domains, int n_domains)
+{
+ int i;
+
+ g_assert (domains != NULL);
+ g_assert (n_domains >= 0);
+
+ lock ();
+
+ if (domains_hash)
+ {
+ for (i = 0; i < n_domains; i++)
+ {
+ char *domain;
+
+ g_assert (domains[i] != NULL);
+
+ if (strcmp (domains[i], CAJA_DEBUG_LOG_DOMAIN_USER) == 0)
+ continue; /* user actions are always enabled */
+
+ domain = g_hash_table_lookup (domains_hash, domains[i]);
+ if (domain)
+ {
+ g_hash_table_remove (domains_hash, domain);
+ g_free (domain);
+ }
+ }
+ } /* else, there is nothing to disable */
+
+ unlock ();
+}
+
+gboolean
+caja_debug_log_is_domain_enabled (const char *domain)
+{
+ gboolean retval;
+
+ g_assert (domain != NULL);
+
+ lock ();
+ retval = is_domain_enabled (domain);
+ unlock ();
+
+ return retval;
+}
+
+struct domains_dump_closure
+{
+ char **domains;
+ int num_domains;
+};
+
+static void
+domains_foreach_dump_cb (gpointer key, gpointer value, gpointer data)
+{
+ struct domains_dump_closure *closure;
+ char *domain;
+
+ closure = data;
+ domain = key;
+
+ closure->domains[closure->num_domains] = domain;
+ closure->num_domains++;
+}
+
+static GKeyFile *
+make_key_file_from_configuration (void)
+{
+ GKeyFile *key_file;
+ struct domains_dump_closure closure;
+ int num_domains;
+
+ key_file = g_key_file_new ();
+
+ /* domains */
+
+ if (domains_hash)
+ {
+ num_domains = g_hash_table_size (domains_hash);
+ if (num_domains != 0)
+ {
+ closure.domains = g_new (char *, num_domains);
+ closure.num_domains = 0;
+
+ g_hash_table_foreach (domains_hash, domains_foreach_dump_cb, &closure);
+ g_assert (num_domains == closure.num_domains);
+
+ g_key_file_set_string_list (key_file, KEY_FILE_GROUP, KEY_FILE_DOMAINS_KEY,
+ (const gchar * const *) closure.domains, closure.num_domains);
+ g_free (closure.domains);
+ }
+ }
+
+ /* max lines */
+
+ g_key_file_set_integer (key_file, KEY_FILE_GROUP, KEY_FILE_MAX_LINES_KEY, ring_buffer_max_lines);
+
+ return key_file;
+}
+
+static gboolean
+write_string (const char *filename, FILE *file, const char *str, GError **error)
+{
+ if (fputs (str, file) == EOF)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "error when writing to log file %s", filename);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+dump_configuration (const char *filename, FILE *file, GError **error)
+{
+ GKeyFile *key_file;
+ char *data;
+ gsize length;
+ gboolean success;
+
+ if (!write_string (filename, file,
+ "\n\n"
+ "This configuration for the debug log can be re-created\n"
+ "by putting the following in ~/caja-debug-log.conf\n"
+ "(use ';' to separate domain names):\n\n",
+ error))
+ {
+ return FALSE;
+ }
+
+ success = FALSE;
+
+ key_file = make_key_file_from_configuration ();
+
+ data = g_key_file_to_data (key_file, &length, error);
+ if (!data)
+ goto out;
+
+ if (!write_string (filename, file, data, error))
+ {
+ goto out;
+ }
+
+ success = TRUE;
+out:
+ g_key_file_free (key_file);
+ return success;
+}
+
+static gboolean
+dump_milestones (const char *filename, FILE *file, GError **error)
+{
+ GSList *l;
+
+ if (!write_string (filename, file, "===== BEGIN MILESTONES =====\n", error))
+ return FALSE;
+
+ for (l = milestones_head; l; l = l->next)
+ {
+ const char *str;
+
+ str = l->data;
+ if (!(write_string (filename, file, str, error)
+ && write_string (filename, file, "\n", error)))
+ return FALSE;
+ }
+
+ if (!write_string (filename, file, "===== END MILESTONES =====\n", error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+dump_ring_buffer (const char *filename, FILE *file, GError **error)
+{
+ int start_index;
+ int i;
+
+ if (!write_string (filename, file, "===== BEGIN RING BUFFER =====\n", error))
+ return FALSE;
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ start_index = ring_buffer_next_index;
+ else
+ start_index = 0;
+
+ for (i = 0; i < ring_buffer_num_lines; i++)
+ {
+ int idx;
+
+ idx = (start_index + i) % ring_buffer_max_lines;
+
+ if (!(write_string (filename, file, ring_buffer[idx], error)
+ && write_string (filename, file, "\n", error)))
+ {
+ return FALSE;
+ }
+ }
+
+ if (!write_string (filename, file, "===== END RING BUFFER =====\n", error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+caja_debug_log_dump (const char *filename, GError **error)
+{
+ FILE *file;
+ gboolean success;
+
+ g_assert (error == NULL || *error == NULL);
+
+ lock ();
+
+ success = FALSE;
+
+ file = fopen (filename, "w");
+ if (!file)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "could not open log file %s", filename);
+ goto out;
+ }
+
+ if (!(dump_milestones (filename, file, error)
+ && dump_ring_buffer (filename, file, error)
+ && dump_configuration (filename, file, error)))
+ {
+ goto do_close;
+ }
+
+ success = TRUE;
+
+do_close:
+
+ if (fclose (file) != 0)
+ {
+ int saved_errno;
+
+ saved_errno = errno;
+
+ if (error && *error)
+ {
+ g_error_free (*error);
+ *error = NULL;
+ }
+
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (saved_errno),
+ "error when closing log file %s", filename);
+ success = FALSE;
+ }
+
+out:
+
+ unlock ();
+ return success;
+}
+
+void
+caja_debug_log_set_max_lines (int num_lines)
+{
+ char **new_buffer;
+ int lines_to_copy;
+
+ g_assert (num_lines > 0);
+
+ lock ();
+
+ if (num_lines == ring_buffer_max_lines)
+ goto out;
+
+ new_buffer = g_new0 (char *, num_lines);
+
+ lines_to_copy = MIN (num_lines, ring_buffer_num_lines);
+
+ if (ring_buffer)
+ {
+ int start_index;
+ int i;
+
+ if (ring_buffer_num_lines == ring_buffer_max_lines)
+ start_index = (ring_buffer_next_index + ring_buffer_max_lines - lines_to_copy) % ring_buffer_max_lines;
+ else
+ start_index = ring_buffer_num_lines - lines_to_copy;
+
+ g_assert (start_index >= 0 && start_index < ring_buffer_max_lines);
+
+ for (i = 0; i < lines_to_copy; i++)
+ {
+ int idx;
+
+ idx = (start_index + i) % ring_buffer_max_lines;
+
+ new_buffer[i] = ring_buffer[idx];
+ ring_buffer[idx] = NULL;
+ }
+
+ for (i = 0; i < ring_buffer_max_lines; i++)
+ g_free (ring_buffer[i]);
+
+ g_free (ring_buffer);
+ }
+
+ ring_buffer = new_buffer;
+ ring_buffer_next_index = lines_to_copy;
+ ring_buffer_num_lines = lines_to_copy;
+ ring_buffer_max_lines = num_lines;
+
+out:
+
+ unlock ();
+}
+
+int
+caja_debug_log_get_max_lines (void)
+{
+ int retval;
+
+ lock ();
+ retval = ring_buffer_max_lines;
+ unlock ();
+
+ return retval;
+}
+
+void
+caja_debug_log_clear (void)
+{
+ int i;
+
+ lock ();
+
+ if (!ring_buffer)
+ goto out;
+
+ for (i = 0; i < ring_buffer_max_lines; i++)
+ {
+ g_free (ring_buffer[i]);
+ ring_buffer[i] = NULL;
+ }
+
+ ring_buffer_next_index = 0;
+ ring_buffer_num_lines = 0;
+
+out:
+ unlock ();
+}