diff options
author | Max Kellermann <max@duempel.org> | 2013-09-27 22:31:24 +0200 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2013-10-02 08:57:55 +0200 |
commit | 060814daa83f6a94f5934464ae42a406c5c7e947 (patch) | |
tree | f636ec6cdbb8e52fda6db987d2a28fc73c7b94b4 /src/LogInit.cxx | |
parent | c53492a76a8a05825e1c7f699c05645eee891199 (diff) |
Log: new logging library API
Prepare to migrate away from GLib. Currently, we're still using GLib
as a backend.
Diffstat (limited to 'src/LogInit.cxx')
-rw-r--r-- | src/LogInit.cxx | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/LogInit.cxx b/src/LogInit.cxx new file mode 100644 index 000000000..d5e5c1d1f --- /dev/null +++ b/src/LogInit.cxx @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LogInit.hxx" +#include "Log.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/FatalError.hxx" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <glib.h> + +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +#define LOG_DATE_BUF_SIZE 16 +#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1) + +static constexpr Domain log_domain("log"); + +static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE; + +static const char *log_charset; + +static bool stdout_mode = true; +static int out_fd; +static Path out_path = Path::Null(); + +static void redirect_logs(int fd) +{ + assert(fd >= 0); + if (dup2(fd, STDOUT_FILENO) < 0) + FatalSystemError("Failed to dup2 stdout"); + if (dup2(fd, STDERR_FILENO) < 0) + FatalSystemError("Failed to dup2 stderr"); +} + +static const char *log_date(void) +{ + static char buf[LOG_DATE_BUF_SIZE]; + time_t t = time(NULL); + strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +/** + * Determines the length of the string excluding trailing whitespace + * characters. + */ +static int +chomp_length(const char *p) +{ + size_t length = strlen(p); + + while (length > 0 && g_ascii_isspace(p[length - 1])) + --length; + + return (int)length; +} + +static void +file_log_func(const gchar *domain, + GLogLevelFlags log_level, + const gchar *message, gcc_unused gpointer user_data) +{ + char *converted; + + if (log_level > log_threshold) + return; + + if (log_charset != NULL) { + converted = g_convert_with_fallback(message, -1, + log_charset, "utf-8", + NULL, NULL, NULL, NULL); + if (converted != NULL) + message = converted; + } else + converted = NULL; + + if (domain == nullptr) + domain = ""; + + fprintf(stderr, "%s%s%s%.*s\n", + stdout_mode ? "" : log_date(), + domain, *domain == 0 ? "" : ": ", + chomp_length(message), message); + + g_free(converted); +} + +static void +log_init_stdout(void) +{ + g_log_set_default_handler(file_log_func, NULL); +} + +static int +open_log_file(void) +{ + assert(!out_path.IsNull()); + + return OpenFile(out_path, O_CREAT | O_WRONLY | O_APPEND, 0666); +} + +static bool +log_init_file(unsigned line, Error &error) +{ + assert(!out_path.IsNull()); + + out_fd = open_log_file(); + if (out_fd < 0) { + const std::string out_path_utf8 = out_path.ToUTF8(); + error.FormatErrno("failed to open log file \"%s\" (config line %u)", + out_path_utf8.c_str(), line); + return false; + } + + g_log_set_default_handler(file_log_func, NULL); + return true; +} + +#ifdef HAVE_SYSLOG + +static int +glib_to_syslog_level(GLogLevelFlags log_level) +{ + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + case G_LOG_LEVEL_CRITICAL: + return LOG_ERR; + + case G_LOG_LEVEL_WARNING: + return LOG_WARNING; + + case G_LOG_LEVEL_MESSAGE: + return LOG_NOTICE; + + case G_LOG_LEVEL_INFO: + return LOG_INFO; + + case G_LOG_LEVEL_DEBUG: + return LOG_DEBUG; + + default: + return LOG_NOTICE; + } +} + +static void +syslog_log_func(const gchar *domain, + GLogLevelFlags log_level, const gchar *message, + gcc_unused gpointer user_data) +{ + if (stdout_mode) { + /* fall back to the file log function during + startup */ + file_log_func(domain, log_level, + message, user_data); + return; + } + + if (log_level > log_threshold) + return; + + if (domain == nullptr) + domain = ""; + + syslog(glib_to_syslog_level(log_level), "%s%s%.*s", + domain, *domain == 0 ? "" : ": ", + chomp_length(message), message); +} + +static void +log_init_syslog(void) +{ + assert(out_path.IsNull()); + + openlog(PACKAGE, 0, LOG_DAEMON); + g_log_set_default_handler(syslog_log_func, NULL); +} + +#endif + +static inline GLogLevelFlags +parse_log_level(const char *value, unsigned line) +{ + if (0 == strcmp(value, "default")) + return G_LOG_LEVEL_MESSAGE; + if (0 == strcmp(value, "secure")) + return LOG_LEVEL_SECURE; + else if (0 == strcmp(value, "verbose")) + return G_LOG_LEVEL_DEBUG; + else { + FormatFatalError("unknown log level \"%s\" at line %u", + value, line); + return G_LOG_LEVEL_MESSAGE; + } +} + +void +log_early_init(bool verbose) +{ + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + + log_init_stdout(); +} + +bool +log_init(bool verbose, bool use_stdout, Error &error) +{ + const struct config_param *param; + + g_get_charset(&log_charset); + + if (verbose) + log_threshold = G_LOG_LEVEL_DEBUG; + else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL) + log_threshold = parse_log_level(param->value, param->line); + + if (use_stdout) { + log_init_stdout(); + return true; + } else { + param = config_get_param(CONF_LOG_FILE); + if (param == NULL) { +#ifdef HAVE_SYSLOG + /* no configuration: default to syslog (if + available) */ + log_init_syslog(); + return true; +#else + error.Set(log_domain, + "config parameter 'log_file' not found"); + return false; +#endif +#ifdef HAVE_SYSLOG + } else if (strcmp(param->value, "syslog") == 0) { + log_init_syslog(); + return true; +#endif + } else { + out_path = config_get_path(CONF_LOG_FILE, error); + return !out_path.IsNull() && + log_init_file(param->line, error); + } + } +} + +static void +close_log_files(void) +{ + if (stdout_mode) + return; + +#ifdef HAVE_SYSLOG + if (out_path.IsNull()) + closelog(); +#endif +} + +void +log_deinit(void) +{ + close_log_files(); + out_path = Path::Null(); +} + + +void setup_log_output(bool use_stdout) +{ + fflush(NULL); + if (!use_stdout) { +#ifndef WIN32 + if (out_path.IsNull()) + out_fd = open("/dev/null", O_WRONLY); +#endif + + if (out_fd >= 0) { + redirect_logs(out_fd); + close(out_fd); + } + + stdout_mode = false; + log_charset = NULL; + } +} + +int cycle_log_files(void) +{ + int fd; + + if (stdout_mode || out_path.IsNull()) + return 0; + + assert(!out_path.IsNull()); + + FormatDebug(log_domain, "Cycling log files"); + close_log_files(); + + fd = open_log_file(); + if (fd < 0) { + const std::string out_path_utf8 = out_path.ToUTF8(); + FormatError(log_domain, + "error re-opening log file: %s", + out_path_utf8.c_str()); + return -1; + } + + redirect_logs(fd); + FormatDebug(log_domain, "Done cycling log files"); + return 0; +} |