source: logging.c @ 18c1925

Last change on this file since 18c1925 was 18c1925, checked in by Jason Gross <jgross@mit.edu>, 7 years ago
Moved the checking of whether or not to log messages to perl
  • Property mode set to 100644
File size: 9.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
[18c1925]32  g_free(owl_perlconfig_call_with_message("BarnOwl::Logging::log", m));
[42947f1]33
[15b34fd]34  owl_function_debugmsg("owl_log_message: leaving");
35}
[7d4fbcd]36
[cc305b5]37static void owl_log_error_main_thread(gpointer data)
38{
39  owl_function_error("%s", (const char*)data);
[42947f1]40}
41
[edb14cc]42static void owl_log_adminmsg_main_thread(gpointer data)
43{
44  owl_function_adminmsg("Logging", (const char*)data);
45}
46
[565a43c]47static void owl_log_makemsg_main_thread(gpointer data)
48{
49  owl_function_makemsg((const char*)data);
50}
51
[e47e1b0]52static void G_GNUC_PRINTF(1, 2) owl_log_error(const char *fmt, ...)
[cc305b5]53{
[e47e1b0]54  va_list ap;
55  char *data;
56
57  va_start(ap, fmt);
58  data = g_strdup_vprintf(fmt, ap);
59  va_end(ap);
60
[cc305b5]61  owl_select_post_task(owl_log_error_main_thread,
[e47e1b0]62                       data, g_free, g_main_context_default());
[42947f1]63}
64
[edb14cc]65static void G_GNUC_PRINTF(1, 2) owl_log_adminmsg(const char *fmt, ...)
66{
67  va_list ap;
68  char *data;
69
70  va_start(ap, fmt);
71  data = g_strdup_vprintf(fmt, ap);
72  va_end(ap);
73
74  owl_select_post_task(owl_log_adminmsg_main_thread,
75                       data, g_free, g_main_context_default());
76}
77
[565a43c]78static void G_GNUC_PRINTF(1, 2) owl_log_makemsg(const char *fmt, ...)
79{
80  va_list ap;
81  char *data;
82
83  va_start(ap, fmt);
84  data = g_strdup_vprintf(fmt, ap);
85  va_end(ap);
86
87  owl_select_post_task(owl_log_makemsg_main_thread,
88                       data, g_free, g_main_context_default());
89}
90
[ec36247]91static CALLER_OWN owl_log_entry *owl_log_new_entry(const char *buffer, const char *filename)
92{
93  owl_log_entry *log_msg = g_slice_new(owl_log_entry);
94  log_msg->message = g_strdup(buffer);
95  log_msg->filename = g_strdup(filename);
96  return log_msg;
97}
98
99static void owl_log_deferred_enqueue_message(const char *buffer, const char *filename)
100{
101  g_queue_push_tail(deferred_entry_queue, owl_log_new_entry(buffer, filename));
102}
103
[874fd19]104static void owl_log_deferred_enqueue_first_message(const char *buffer, const char *filename)
105{
106  g_queue_push_head(deferred_entry_queue, owl_log_new_entry(buffer, filename));
107}
108
[ec36247]109/* write out the entry if possible
110 * return 0 on success, errno on failure to open
111 */
112static int owl_log_try_write_entry(owl_log_entry *msg)
[cc305b5]113{
114  FILE *file = NULL;
115  file = fopen(msg->filename, "a");
116  if (!file) {
[ec36247]117    return errno;
[cc305b5]118  }
119  fprintf(file, "%s", msg->message);
120  fclose(file);
[ec36247]121  return 0;
[42947f1]122}
123
[cc305b5]124static void owl_log_entry_free(void *data)
125{
126  owl_log_entry *msg = (owl_log_entry*)data;
127  if (msg) {
128    g_free(msg->message);
129    g_free(msg->filename);
[7dcef03]130    g_slice_free(owl_log_entry, msg);
[cc305b5]131  }
132}
133
[ec36247]134#if GLIB_CHECK_VERSION(2, 32, 0)
135#else
136static void owl_log_entry_free_gfunc(gpointer data, gpointer user_data)
137{
138  owl_log_entry_free(data);
139}
140#endif
141
[253f37f]142static void owl_log_file_error(owl_log_entry *msg, int errnum)
[77beb3c]143{
144  owl_log_error("Unable to open file for logging: %s (file %s)",
[253f37f]145                g_strerror(errnum),
[77beb3c]146                msg->filename);
147}
148
[ec36247]149/* If we are deferring log messages, enqueue this entry for writing.
150 * Otherwise, try to write this log message, and, if it fails with
[e6f21ea]151 * EPERM, EACCES, or ETIMEDOUT, go into deferred logging mode and
152 * queue an admin message.  If it fails with anything else, display an
[8a06468]153 * error message, drop the log message, and do not go into deferred
154 * logging mode.
[89e4294]155 *
156 * N.B. This function is called only on the disk-writing thread. */
[ec36247]157static void owl_log_eventually_write_entry(gpointer data)
158{
159  int ret;
160  owl_log_entry *msg = (owl_log_entry*)data;
161  if (defer_logs) {
162    owl_log_deferred_enqueue_message(msg->message, msg->filename);
163  } else {
164    ret = owl_log_try_write_entry(msg);
[e6f21ea]165    if (ret == EPERM || ret == EACCES || ret == ETIMEDOUT) {
[ec36247]166      defer_logs = true;
167      owl_log_error("Unable to open file for logging (%s): \n"
168                    "%s.  \n"
169                    "Consider renewing your tickets.  Logging has been \n"
170                    "suspended, and your messages will be saved.  To \n"
171                    "resume logging, use the command :flush-logs.\n\n",
172                    msg->filename,
173                    g_strerror(ret));
[874fd19]174      /* If we were not in deferred logging mode, either the queue should be
175       * empty, or we are attempting to log a message that we just popped off
176       * the head of the queue.  Either way, we should enqueue this message as
177       * the first message in the queue, rather than the last, so that we
178       * preserve message ordering. */
179      owl_log_deferred_enqueue_first_message(msg->message, msg->filename);
[ec36247]180    } else if (ret != 0) {
[77beb3c]181      owl_log_file_error(msg, ret);
[ec36247]182    }
183  }
184}
185
[89e4294]186/* tries to write the deferred log entries
187 *
188 * N.B. This function is called only on the disk-writing thread. */
[ec36247]189static void owl_log_write_deferred_entries(gpointer data)
190{
191  owl_log_entry *entry;
[4511ac3]192  owl_log_options *opts = (owl_log_options *)data;
[77beb3c]193  int ret;
[604303c]194  int logged_message_count = 0;
[edb14cc]195  bool all_succeeded = true;
[ec36247]196
[4511ac3]197  if (opts->display_initial_log_count) {
198    if (g_queue_is_empty(deferred_entry_queue)) {
199      owl_log_makemsg("There are no logs to flush.");
200    } else {
201      owl_log_makemsg("Attempting to flush %u logs...",
202                      g_queue_get_length(deferred_entry_queue));
203    }
[565a43c]204  }
205
[ec36247]206  defer_logs = false;
207  while (!g_queue_is_empty(deferred_entry_queue) && !defer_logs) {
[604303c]208    logged_message_count++;
[ec36247]209    entry = (owl_log_entry*)g_queue_pop_head(deferred_entry_queue);
[4511ac3]210    if (!opts->drop_failed_logs) {
[874fd19]211      /* Attempt to write the log entry.  If it fails, re-queue the entry at
212       * the head of the queue. */
[77beb3c]213      owl_log_eventually_write_entry(entry);
214    } else {
[874fd19]215      /* Attempt to write the log entry. If it fails, print an error message,
216       * drop the log, and keep going through the queue. */
[77beb3c]217      ret = owl_log_try_write_entry(entry);
218      if (ret != 0) {
[edb14cc]219        all_succeeded = false;
[77beb3c]220        owl_log_file_error(entry, ret);
221      }
222    }
[ec36247]223    owl_log_entry_free(entry);
224  }
[565a43c]225  if (logged_message_count > 0) {
[4511ac3]226    if (opts->display_initial_log_count) {
227      /* first clear the "attempting to flush" message from the status bar */
228      owl_log_makemsg("");
229    }
[565a43c]230    if (!defer_logs) {
231      if (all_succeeded) {
232        owl_log_adminmsg("Flushed %d logs and resumed logging.",
233                         logged_message_count);
234      } else {
235        owl_log_adminmsg("Flushed or dropped %d logs and resumed logging.",
236                         logged_message_count);
237      }
238    } else {
239      owl_log_error("Attempted to flush %d logs; %u deferred logs remain.",
240                    logged_message_count, g_queue_get_length(deferred_entry_queue));
241    }
[edb14cc]242  }
[ec36247]243}
244
[4511ac3]245void owl_log_flush_logs(bool drop_failed_logs, bool quiet)
[ec36247]246{
[4511ac3]247  owl_log_options *data = g_new(owl_log_options, 1);
248  data->drop_failed_logs = drop_failed_logs;
249  data->display_initial_log_count = !quiet;
[77beb3c]250
251  owl_select_post_task(owl_log_write_deferred_entries,
252                       data,
253                       g_free,
254                       log_context);
[ec36247]255}
256
[cc305b5]257void owl_log_enqueue_message(const char *buffer, const char *filename)
258{
[ec36247]259  owl_log_entry *log_msg = owl_log_new_entry(buffer, filename);
260  owl_select_post_task(owl_log_eventually_write_entry, log_msg,
[cc305b5]261                       owl_log_entry_free, log_context);
[42947f1]262}
263
[24ccc01]264void owl_log_outgoing_zephyr_error(const owl_zwrite *zw, const char *text)
[2b86d14]265{
[18c1925]266  owl_message *m = g_slice_new(owl_message);
[e5da3fe]267  /* recip_index = 0 because there can only be one recipient anyway */
268  owl_message_create_from_zwrite(m, zw, text, 0);
[18c1925]269  g_free(owl_perlconfig_call_with_message("BarnOwl::Logging::log_outgoing_error", m));
[cd6521f]270  owl_message_delete(m);
[2b86d14]271}
272
[cc305b5]273static gpointer owl_log_thread_func(gpointer data)
274{
[ec36247]275  log_context = g_main_context_new();
[cc305b5]276  log_loop = g_main_loop_new(log_context, FALSE);
277  g_main_loop_run(log_loop);
278  return NULL;
279}
280
[ec36247]281void owl_log_init(void)
[cc305b5]282{
[0a9ffc5]283  log_context = g_main_context_new();
[0792d99]284#if GLIB_CHECK_VERSION(2, 31, 0)
285  logging_thread = g_thread_new("logging",
286                                owl_log_thread_func,
287                                NULL);
288#else
289  GError *error = NULL;
[cc305b5]290  logging_thread = g_thread_create(owl_log_thread_func,
291                                   NULL,
292                                   TRUE,
293                                   &error);
294  if (error) {
295    endwin();
296    fprintf(stderr, "Error spawning logging thread: %s\n", error->message);
297    fflush(stderr);
298    exit(1);
299  }
[0792d99]300#endif
[ec36247]301
302  deferred_entry_queue = g_queue_new();
[cc305b5]303}
304
305static void owl_log_quit_func(gpointer data)
306{
[ec36247]307  /* flush the deferred logs queue, trying to write the
[77beb3c]308   * entries to the disk one last time.  Drop any failed
[4511ac3]309   * entries, and be quiet about it. */
310  owl_log_options opts;
311  opts.drop_failed_logs = true;
312  opts.display_initial_log_count = false;
313  owl_log_write_deferred_entries(&opts);
[ec36247]314#if GLIB_CHECK_VERSION(2, 32, 0)
315  g_queue_free_full(deferred_entry_queue, owl_log_entry_free);
316#else
317  g_queue_foreach(deferred_entry_queue, owl_log_entry_free_gfunc, NULL);
318  g_queue_free(deferred_entry_queue);
319#endif
320
[cc305b5]321  g_main_loop_quit(log_loop);
322}
323
324void owl_log_shutdown(void)
325{
326  owl_select_post_task(owl_log_quit_func, NULL,
327                       NULL, log_context);
328  g_thread_join(logging_thread);
329}
Note: See TracBrowser for help on using the repository browser.