about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorDmitry Podgorny <pasis.ua@gmail.com>2015-06-22 11:30:23 +0000
committerDmitry Podgorny <pasis.ua@gmail.com>2015-06-23 20:52:04 +0000
commite8314106693e3aeee8b5e4834fc47ddaa02efaef (patch)
treec9c22e7214845b4118f845ea94286a00535a68c3
parent2d3537515d9b0dbba51265ed7819dd80b31a33d8 (diff)
downloadprofani-tty-e8314106693e3aeee8b5e4834fc47ddaa02efaef.tar.gz
log: Introduced stderr log handler
This handler redirects stderr output to a pipe and gathers logs from the
read end.

The handler is called from main loop, therefore, if a function prints logs
to stderr they will be put to log file only after function returns.
-rw-r--r--src/log.c103
-rw-r--r--src/log.h4
-rw-r--r--src/profanity.c3
-rw-r--r--tests/unittests/log/stub_log.c4
4 files changed, 114 insertions, 0 deletions
diff --git a/src/log.c b/src/log.c
index 941bea6f..ce0cf7b0 100644
--- a/src/log.c
+++ b/src/log.c
@@ -34,6 +34,7 @@
 
 #include <assert.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -61,6 +62,16 @@ static GHashTable *logs;
 static GHashTable *groupchat_logs;
 static GDateTime *session_started;
 
+enum {
+    STDERR_BUFSIZE = 4000,
+    STDERR_RETRY_NR = 5,
+};
+static int stderr_inited;
+static log_level_t stderr_level;
+static int stderr_pipe[2];
+static char *stderr_buf;
+static GString *stderr_msg;
+
 struct dated_chat_log {
     gchar *filename;
     GDateTime *date;
@@ -690,3 +701,95 @@ _log_string_from_level(log_level_t level)
             return "LOG";
     }
 }
+
+void
+log_stderr_handler(void)
+{
+    GString * const s = stderr_msg;
+    char * const buf = stderr_buf;
+    ssize_t size;
+    int retry = 0;
+    int i;
+
+    if (!stderr_inited)
+        return;
+
+    do {
+        size = read(stderr_pipe[0], buf, STDERR_BUFSIZE);
+        if (size == -1 && errno == EINTR && retry++ < STDERR_RETRY_NR)
+            continue;
+        if (size <= 0 || retry++ >= STDERR_RETRY_NR)
+            break;
+
+        for (i = 0; i < size; ++i) {
+            if (buf[i] == '\n') {
+                log_msg(stderr_level, "stderr", s->str);
+                g_string_assign(s, "");
+            } else
+                g_string_append_c(s, buf[i]);
+        }
+    } while (1);
+
+    if (s->len > 0 && s->str[0] != '\0') {
+        log_msg(stderr_level, "stderr", s->str);
+        g_string_assign(s, "");
+    }
+}
+
+void
+log_stderr_init(log_level_t level)
+{
+    int rc;
+    int flags;
+
+    rc = pipe(stderr_pipe);
+    if (rc != 0)
+        goto err;
+
+    flags = fcntl(stderr_pipe[0], F_GETFL);
+    rc = fcntl(stderr_pipe[0], F_SETFL, flags | O_NONBLOCK);
+    if (rc != 0)
+        goto err_close;
+
+    close(STDERR_FILENO);
+    rc = dup2(stderr_pipe[1], STDERR_FILENO);
+    if (rc < 0)
+        goto err_close;
+
+    stderr_buf = malloc(STDERR_BUFSIZE);
+    stderr_msg = g_string_sized_new(STDERR_BUFSIZE);
+    stderr_level = level;
+    stderr_inited = 1;
+
+    if (stderr_buf == NULL || stderr_msg == NULL) {
+        errno = ENOMEM;
+        goto err_free;
+    }
+    return;
+
+err_free:
+    if (stderr_msg != NULL)
+        g_string_free(stderr_msg, TRUE);
+    free(stderr_buf);
+err_close:
+    close(stderr_pipe[0]);
+    close(stderr_pipe[1]);
+err:
+    stderr_inited = 0;
+    log_error("Unable to init stderr log handler: %s", strerror(errno));
+}
+
+void
+log_stderr_close(void)
+{
+    if (!stderr_inited)
+        return;
+
+    /* handle remaining logs before close */
+    log_stderr_handler();
+    stderr_inited = 0;
+    free(stderr_buf);
+    g_string_free(stderr_msg, TRUE);
+    close(stderr_pipe[0]);
+    close(stderr_pipe[1]);
+}
diff --git a/src/log.h b/src/log.h
index 3c98c3bd..df36b453 100644
--- a/src/log.h
+++ b/src/log.h
@@ -63,6 +63,10 @@ void log_msg(log_level_t level, const char * const area,
     const char * const msg);
 log_level_t log_level_from_string(char *log_level);
 
+void log_stderr_init(log_level_t level);
+void log_stderr_close(void);
+void log_stderr_handler(void);
+
 void chat_log_init(void);
 
 void chat_log_msg_out(const char * const barejid, const char * const msg);
diff --git a/src/profanity.c b/src/profanity.c
index f65d22e0..a56eb5e9 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -89,6 +89,7 @@ prof_run(const int disable_tls, char *log_level, char *account_name)
 
     char *line = NULL;
     while(cont) {
+        log_stderr_handler();
         _check_autoaway();
 
         line = ui_readline();
@@ -225,6 +226,7 @@ _init(const int disable_tls, char *log_level)
     log_level_t prof_log_level = log_level_from_string(log_level);
     prefs_load();
     log_init(prof_log_level);
+    log_stderr_init(PROF_LEVEL_ERROR);
     if (strcmp(PACKAGE_STATUS, "development") == 0) {
 #ifdef HAVE_GIT_VERSION
             log_info("Starting Profanity (%sdev.%s.%s)...", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
@@ -283,6 +285,7 @@ _shutdown(void)
     theme_close();
     accounts_close();
     cmd_uninit();
+    log_stderr_close();
     log_close();
     prefs_close();
 }
diff --git a/tests/unittests/log/stub_log.c b/tests/unittests/log/stub_log.c
index c25057d8..bf9d6975 100644
--- a/tests/unittests/log/stub_log.c
+++ b/tests/unittests/log/stub_log.c
@@ -49,6 +49,10 @@ log_level_t log_level_from_string(char *log_level)
     return (log_level_t)mock();
 }
 
+void log_stderr_init(log_level_t level) {}
+void log_stderr_close(void) {}
+void log_stderr_handler(void) {}
+
 void chat_log_init(void) {}
 
 void chat_log_msg_out(const char * const barejid, const char * const msg) {}