source: logging.c @ ff528e6

Last change on this file since ff528e6 was eea7bed4, checked in by Jason Gross <jasongross9@gmail.com>, 7 years ago
Moved log file name generation to perl I don't think that the class/personal distinction is the best for general protocols, but I don't know what should replace it. I've made class-logging default to only zephyr (a slight change from previous behavior, where jabber MUC's would be logged to $classlogpath, as well as all non-private non-login messages from other protocols), but made logging path generation overridable. TODO: Decide whether or not to filter out more 'bad' characters. Perhaps we should remove '!' because it indicates history in some shells and makes things obnoxious, or '~' becase it indicates homedirs in many shells. * '/' is for separating directories, and we don't want to accidentally make subdirectories We first NFKC for zephyrs, and then apply lc. The zephyr servers apply case-folded NFKC (so says http://zephyr.1ts.org/browser/zephyr/server/zstring.c). We should probably use Unicode::CaseFold instead of lc. I'm also not sure what the order case-adjustment and normalization should be. We first NFKC, then apply lc, to jabbers, as per http://xmpp.org/internet-drafts/attic/draft-ietf-xmpp-nodeprep-03.html (though I can't actually find anything that specifies the case-folding algorithm, nor the ordering). We now use lc instead of g_utf8_strdown to normalize AIM screennames.
  • Property mode set to 100644
File size: 12.5 KB
RevLine 
[7d4fbcd]1#include "owl.h"
[f271129]2#include <stdio.h>
[7d4fbcd]3
[cc305b5]4typedef struct _owl_log_entry { /* noproto */
5  char *filename;
6  char *message;
7} owl_log_entry;
8
[4511ac3]9typedef struct _owl_log_options { /* noproto */
10  bool drop_failed_logs;
11  bool display_initial_log_count;
12} owl_log_options;
[cc305b5]13
14static GMainContext *log_context;
15static GMainLoop *log_loop;
16static GThread *logging_thread;
[89e4294]17static bool defer_logs; /* to be accessed only on the disk-writing thread */
[ec36247]18static GQueue *deferred_entry_queue;
[cc305b5]19
[15b34fd]20/* This is now the one function that should be called to log a
21 * message.  It will do all the work necessary by calling the other
22 * functions in this file as necessary.
23 */
[c08c70a]24void owl_log_message(const owl_message *m) {
[15b34fd]25  owl_function_debugmsg("owl_log_message: entering");
26
[3c7d086a]27  if (m == NULL) {
28    owl_function_debugmsg("owl_log_message: passed null message");
29    return;
30  }
31
[15b34fd]32  /* should we be logging this message? */
33  if (!owl_log_shouldlog_message(m)) {
34    owl_function_debugmsg("owl_log_message: not logging message");
35    return;
36  }
37
[eea7bed4]38  owl_log_perl(m);
[42947f1]39
[15b34fd]40  owl_function_debugmsg("owl_log_message: leaving");
41}
[7d4fbcd]42
[15b34fd]43/* Return 1 if we should log the given message, otherwise return 0 */
[c08c70a]44int owl_log_shouldlog_message(const owl_message *m) {
[4542047]45  const owl_filter *f;
[12c35df]46
[15b34fd]47  /* If there's a logfilter and this message matches it, log */
48  f=owl_global_get_filter(&g, owl_global_get_logfilter(&g));
49  if (f && owl_filter_message_match(f, m)) return(1);
[7d4fbcd]50
[15b34fd]51  /* otherwise we do things based on the logging variables */
52
53  /* skip login/logout messages if appropriate */
54  if (!owl_global_is_loglogins(&g) && owl_message_is_loginout(m)) return(0);
55     
56  /* check direction */
57  if ((owl_global_get_loggingdirection(&g)==OWL_LOGGING_DIRECTION_IN) && owl_message_is_direction_out(m)) {
58    return(0);
59  }
60  if ((owl_global_get_loggingdirection(&g)==OWL_LOGGING_DIRECTION_OUT) && owl_message_is_direction_in(m)) {
61    return(0);
62  }
63
64  if (owl_message_is_type_zephyr(m)) {
65    if (owl_message_is_personal(m) && !owl_global_is_logging(&g)) return(0);
66    if (!owl_message_is_personal(m) && !owl_global_is_classlogging(&g)) return(0);
67  } else {
68    if (owl_message_is_private(m) || owl_message_is_loginout(m)) {
69      if (!owl_global_is_logging(&g)) return(0);
70    } else {
71      if (!owl_global_is_classlogging(&g)) return(0);
72    }
[7d4fbcd]73  }
[15b34fd]74  return(1);
75}
76
[cc305b5]77static void owl_log_error_main_thread(gpointer data)
78{
79  owl_function_error("%s", (const char*)data);
[42947f1]80}
81
[edb14cc]82static void owl_log_adminmsg_main_thread(gpointer data)
83{
84  owl_function_adminmsg("Logging", (const char*)data);
85}
86
[565a43c]87static void owl_log_makemsg_main_thread(gpointer data)
88{
89  owl_function_makemsg((const char*)data);
90}
91
[e47e1b0]92static void G_GNUC_PRINTF(1, 2) owl_log_error(const char *fmt, ...)
[cc305b5]93{
[e47e1b0]94  va_list ap;
95  char *data;
96
97  va_start(ap, fmt);
98  data = g_strdup_vprintf(fmt, ap);
99  va_end(ap);
100
[cc305b5]101  owl_select_post_task(owl_log_error_main_thread,
[e47e1b0]102                       data, g_free, g_main_context_default());
[42947f1]103}
104
[edb14cc]105static void G_GNUC_PRINTF(1, 2) owl_log_adminmsg(const char *fmt, ...)
106{
107  va_list ap;
108  char *data;
109
110  va_start(ap, fmt);
111  data = g_strdup_vprintf(fmt, ap);
112  va_end(ap);
113
114  owl_select_post_task(owl_log_adminmsg_main_thread,
115                       data, g_free, g_main_context_default());
116}
117
[565a43c]118static void G_GNUC_PRINTF(1, 2) owl_log_makemsg(const char *fmt, ...)
119{
120  va_list ap;
121  char *data;
122
123  va_start(ap, fmt);
124  data = g_strdup_vprintf(fmt, ap);
125  va_end(ap);
126
127  owl_select_post_task(owl_log_makemsg_main_thread,
128                       data, g_free, g_main_context_default());
129}
130
[ec36247]131static CALLER_OWN owl_log_entry *owl_log_new_entry(const char *buffer, const char *filename)
132{
133  owl_log_entry *log_msg = g_slice_new(owl_log_entry);
134  log_msg->message = g_strdup(buffer);
135  log_msg->filename = g_strdup(filename);
136  return log_msg;
137}
138
139static void owl_log_deferred_enqueue_message(const char *buffer, const char *filename)
140{
141  g_queue_push_tail(deferred_entry_queue, owl_log_new_entry(buffer, filename));
142}
143
[874fd19]144static void owl_log_deferred_enqueue_first_message(const char *buffer, const char *filename)
145{
146  g_queue_push_head(deferred_entry_queue, owl_log_new_entry(buffer, filename));
147}
148
[ec36247]149/* write out the entry if possible
150 * return 0 on success, errno on failure to open
151 */
152static int owl_log_try_write_entry(owl_log_entry *msg)
[cc305b5]153{
154  FILE *file = NULL;
155  file = fopen(msg->filename, "a");
156  if (!file) {
[ec36247]157    return errno;
[cc305b5]158  }
159  fprintf(file, "%s", msg->message);
160  fclose(file);
[ec36247]161  return 0;
[42947f1]162}
163
[cc305b5]164static void owl_log_entry_free(void *data)
165{
166  owl_log_entry *msg = (owl_log_entry*)data;
167  if (msg) {
168    g_free(msg->message);
169    g_free(msg->filename);
[7dcef03]170    g_slice_free(owl_log_entry, msg);
[cc305b5]171  }
172}
173
[ec36247]174#if GLIB_CHECK_VERSION(2, 32, 0)
175#else
176static void owl_log_entry_free_gfunc(gpointer data, gpointer user_data)
177{
178  owl_log_entry_free(data);
179}
180#endif
181
[253f37f]182static void owl_log_file_error(owl_log_entry *msg, int errnum)
[77beb3c]183{
184  owl_log_error("Unable to open file for logging: %s (file %s)",
[253f37f]185                g_strerror(errnum),
[77beb3c]186                msg->filename);
187}
188
[ec36247]189/* If we are deferring log messages, enqueue this entry for writing.
190 * Otherwise, try to write this log message, and, if it fails with
[e6f21ea]191 * EPERM, EACCES, or ETIMEDOUT, go into deferred logging mode and
192 * queue an admin message.  If it fails with anything else, display an
[8a06468]193 * error message, drop the log message, and do not go into deferred
194 * logging mode.
[89e4294]195 *
196 * N.B. This function is called only on the disk-writing thread. */
[ec36247]197static void owl_log_eventually_write_entry(gpointer data)
198{
199  int ret;
200  owl_log_entry *msg = (owl_log_entry*)data;
201  if (defer_logs) {
202    owl_log_deferred_enqueue_message(msg->message, msg->filename);
203  } else {
204    ret = owl_log_try_write_entry(msg);
[e6f21ea]205    if (ret == EPERM || ret == EACCES || ret == ETIMEDOUT) {
[ec36247]206      defer_logs = true;
207      owl_log_error("Unable to open file for logging (%s): \n"
208                    "%s.  \n"
209                    "Consider renewing your tickets.  Logging has been \n"
210                    "suspended, and your messages will be saved.  To \n"
211                    "resume logging, use the command :flush-logs.\n\n",
212                    msg->filename,
213                    g_strerror(ret));
[874fd19]214      /* If we were not in deferred logging mode, either the queue should be
215       * empty, or we are attempting to log a message that we just popped off
216       * the head of the queue.  Either way, we should enqueue this message as
217       * the first message in the queue, rather than the last, so that we
218       * preserve message ordering. */
219      owl_log_deferred_enqueue_first_message(msg->message, msg->filename);
[ec36247]220    } else if (ret != 0) {
[77beb3c]221      owl_log_file_error(msg, ret);
[ec36247]222    }
223  }
224}
225
[89e4294]226/* tries to write the deferred log entries
227 *
228 * N.B. This function is called only on the disk-writing thread. */
[ec36247]229static void owl_log_write_deferred_entries(gpointer data)
230{
231  owl_log_entry *entry;
[4511ac3]232  owl_log_options *opts = (owl_log_options *)data;
[77beb3c]233  int ret;
[604303c]234  int logged_message_count = 0;
[edb14cc]235  bool all_succeeded = true;
[ec36247]236
[4511ac3]237  if (opts->display_initial_log_count) {
238    if (g_queue_is_empty(deferred_entry_queue)) {
239      owl_log_makemsg("There are no logs to flush.");
240    } else {
241      owl_log_makemsg("Attempting to flush %u logs...",
242                      g_queue_get_length(deferred_entry_queue));
243    }
[565a43c]244  }
245
[ec36247]246  defer_logs = false;
247  while (!g_queue_is_empty(deferred_entry_queue) && !defer_logs) {
[604303c]248    logged_message_count++;
[ec36247]249    entry = (owl_log_entry*)g_queue_pop_head(deferred_entry_queue);
[4511ac3]250    if (!opts->drop_failed_logs) {
[874fd19]251      /* Attempt to write the log entry.  If it fails, re-queue the entry at
252       * the head of the queue. */
[77beb3c]253      owl_log_eventually_write_entry(entry);
254    } else {
[874fd19]255      /* Attempt to write the log entry. If it fails, print an error message,
256       * drop the log, and keep going through the queue. */
[77beb3c]257      ret = owl_log_try_write_entry(entry);
258      if (ret != 0) {
[edb14cc]259        all_succeeded = false;
[77beb3c]260        owl_log_file_error(entry, ret);
261      }
262    }
[ec36247]263    owl_log_entry_free(entry);
264  }
[565a43c]265  if (logged_message_count > 0) {
[4511ac3]266    if (opts->display_initial_log_count) {
267      /* first clear the "attempting to flush" message from the status bar */
268      owl_log_makemsg("");
269    }
[565a43c]270    if (!defer_logs) {
271      if (all_succeeded) {
272        owl_log_adminmsg("Flushed %d logs and resumed logging.",
273                         logged_message_count);
274      } else {
275        owl_log_adminmsg("Flushed or dropped %d logs and resumed logging.",
276                         logged_message_count);
277      }
278    } else {
279      owl_log_error("Attempted to flush %d logs; %u deferred logs remain.",
280                    logged_message_count, g_queue_get_length(deferred_entry_queue));
281    }
[edb14cc]282  }
[ec36247]283}
284
[4511ac3]285void owl_log_flush_logs(bool drop_failed_logs, bool quiet)
[ec36247]286{
[4511ac3]287  owl_log_options *data = g_new(owl_log_options, 1);
288  data->drop_failed_logs = drop_failed_logs;
289  data->display_initial_log_count = !quiet;
[77beb3c]290
291  owl_select_post_task(owl_log_write_deferred_entries,
292                       data,
293                       g_free,
294                       log_context);
[ec36247]295}
296
[cc305b5]297void owl_log_enqueue_message(const char *buffer, const char *filename)
298{
[ec36247]299  owl_log_entry *log_msg = owl_log_new_entry(buffer, filename);
300  owl_select_post_task(owl_log_eventually_write_entry, log_msg,
[cc305b5]301                       owl_log_entry_free, log_context);
[42947f1]302}
303
[c08c70a]304void owl_log_append(const owl_message *m, const char *filename) {
[7f463cf]305  char *buffer = owl_perlconfig_message_call_method(m, "log", 0, NULL);
[cc305b5]306  owl_log_enqueue_message(buffer, filename);
307  g_free(buffer);
[42947f1]308}
309
[24ccc01]310void owl_log_outgoing_zephyr_error(const owl_zwrite *zw, const char *text)
[2b86d14]311{
[e3a75ed]312  char *filename, *logpath;
[fe3b017]313  char *tobuff, *recip;
[180cd15]314  owl_message *m;
[cc305b5]315  GString *msgbuf;
[180cd15]316  /* create a present message so we can pass it to
[c79a047]317   * owl_log_shouldlog_message(void)
[180cd15]318   */
[7dcef03]319  m = g_slice_new(owl_message);
[e5da3fe]320  /* recip_index = 0 because there can only be one recipient anyway */
321  owl_message_create_from_zwrite(m, zw, text, 0);
[180cd15]322  if (!owl_log_shouldlog_message(m)) {
[91634ec]323    owl_message_delete(m);
[180cd15]324    return;
325  }
[91634ec]326  owl_message_delete(m);
[2b86d14]327
[180cd15]328  /* chop off a local realm */
[fe3b017]329  recip = owl_zwrite_get_recip_n_with_realm(zw, 0);
330  tobuff = short_zuser(recip);
331  g_free(recip);
[2b86d14]332
333  /* expand ~ in path names */
[60d7935]334  logpath = owl_util_makepath(owl_global_get_logpath(&g));
[dde1b4d]335  filename = g_build_filename(logpath, tobuff, NULL);
[cc305b5]336  msgbuf = g_string_new("");
337  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
338  if (text[strlen(text)-1] != '\n') {
339    g_string_append_printf(msgbuf, "\n");
[2b86d14]340  }
[cc305b5]341  owl_log_enqueue_message(msgbuf->str, filename);
342  g_string_free(msgbuf, TRUE);
[2b86d14]343
[dde1b4d]344  filename = g_build_filename(logpath, "all", NULL);
[ddbbcffa]345  g_free(logpath);
[cc305b5]346  msgbuf = g_string_new("");
347  g_string_printf(msgbuf, "ERROR (owl): %s\n%s\n", tobuff, text);
348  if (text[strlen(text)-1] != '\n') {
349    g_string_append_printf(msgbuf, "\n");
[2b86d14]350  }
[cc305b5]351  owl_log_enqueue_message(msgbuf->str, filename);
352  g_string_free(msgbuf, TRUE);
[2b86d14]353
[ddbbcffa]354  g_free(tobuff);
[2b86d14]355}
356
[eea7bed4]357void owl_log_perl(const owl_message *m)
[15283bb]358{
[eea7bed4]359  char *filenames_string = owl_perlconfig_call_with_message("BarnOwl::Logging::get_filenames_as_string", m);
360  char **filenames = g_strsplit(filenames_string, "\n", 0);
361  char **filename_ptr;
362  g_free(filenames_string);
[7d4fbcd]363
[eea7bed4]364  for (filename_ptr = filenames; *filename_ptr != NULL; filename_ptr++) {
365    owl_log_append(m, *filename_ptr);
[7d4fbcd]366  }
367
[eea7bed4]368  g_strfreev(filenames);
[7d4fbcd]369}
[cc305b5]370
371static gpointer owl_log_thread_func(gpointer data)
372{
[ec36247]373  log_context = g_main_context_new();
[cc305b5]374  log_loop = g_main_loop_new(log_context, FALSE);
375  g_main_loop_run(log_loop);
376  return NULL;
377}
378
[ec36247]379void owl_log_init(void)
[cc305b5]380{
[0a9ffc5]381  log_context = g_main_context_new();
[0792d99]382#if GLIB_CHECK_VERSION(2, 31, 0)
383  logging_thread = g_thread_new("logging",
384                                owl_log_thread_func,
385                                NULL);
386#else
387  GError *error = NULL;
[cc305b5]388  logging_thread = g_thread_create(owl_log_thread_func,
389                                   NULL,
390                                   TRUE,
391                                   &error);
392  if (error) {
393    endwin();
394    fprintf(stderr, "Error spawning logging thread: %s\n", error->message);
395    fflush(stderr);
396    exit(1);
397  }
[0792d99]398#endif
[ec36247]399
400  deferred_entry_queue = g_queue_new();
[cc305b5]401}
402
403static void owl_log_quit_func(gpointer data)
404{
[ec36247]405  /* flush the deferred logs queue, trying to write the
[77beb3c]406   * entries to the disk one last time.  Drop any failed
[4511ac3]407   * entries, and be quiet about it. */
408  owl_log_options opts;
409  opts.drop_failed_logs = true;
410  opts.display_initial_log_count = false;
411  owl_log_write_deferred_entries(&opts);
[ec36247]412#if GLIB_CHECK_VERSION(2, 32, 0)
413  g_queue_free_full(deferred_entry_queue, owl_log_entry_free);
414#else
415  g_queue_foreach(deferred_entry_queue, owl_log_entry_free_gfunc, NULL);
416  g_queue_free(deferred_entry_queue);
417#endif
418
[cc305b5]419  g_main_loop_quit(log_loop);
420}
421
422void owl_log_shutdown(void)
423{
424  owl_select_post_task(owl_log_quit_func, NULL,
425                       NULL, log_context);
426  g_thread_join(logging_thread);
427}
Note: See TracBrowser for help on using the repository browser.