source: logging.c @ 9e596f5

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