/* ide-log.c * * Copyright 2015-2019 Christian Hergert * * 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 3 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, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-log" #include "config.h" #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #ifdef __linux__ # include # include #endif #include #include #include #include #include #include "ide-debug.h" #include "ide-log.h" #include "ide-macros.h" #include "ide-private.h" /** * SECTION:ide-log * @title: Logging * @short_description: Standard logging facilities for Builder * * This module manages the logging facilities in Builder. It involves * formatting the standard output and error logs as well as filtering * logs based on their #GLogLevelFlags. * * Generally speaking, you want to continue using the GLib logging API * such as g_debug(), g_warning(), g_message(), or g_error(). These functions * will redirect their logging information to this module who will format * the log message appropriately. * * If you are writing code for Builder that is in C, you want to ensure you * set the %G_LOG_DOMAIN define at the top of your file (after the license) * as such: * * ## Logging from C * * |[ * #define G_LOG_DOMAIN "my-module" * ... * static void * some_function (void) * { * g_debug ("Use normal logging facilities"); * } * ]| * * ## Logging from Python * * If you are writing an extension to Builder from Python, you may use the * helper functions provided by our Ide python module. * * |[ * from gi.repository import Ide * * Ide.warning("This is a warning") * Ide.debug("This is a debug") * Ide.error("This is a fatal error") * ]| * * Since: 3.32 */ typedef const gchar *(*IdeLogLevelStrFunc) (GLogLevelFlags log_level); static GPtrArray *channels; static GLogFunc last_handler; static int log_verbosity; static IdeLogLevelStrFunc log_level_str_func; static gchar *domains; static gboolean has_domains; G_LOCK_DEFINE (channels_lock); /** * ide_log_get_thread: * * Retrieves task id for the current thread. This is only supported on Linux. * On other platforms, the current thread pointer is retrieved. * * Returns: The task id. * * Since: 3.32 */ static inline gint ide_log_get_thread (void) { #ifdef __linux__ return (gint) syscall (SYS_gettid); #else return GPOINTER_TO_INT (g_thread_self ()); #endif /* __linux__ */ } /** * ide_log_level_str: * @log_level: a #GLogLevelFlags. * * Retrieves the log level as a string. * * Returns: A string which shouldn't be modified or freed. * Side effects: None. * * Since: 3.32 */ static const gchar * ide_log_level_str (GLogLevelFlags log_level) { switch (((gulong)log_level & G_LOG_LEVEL_MASK)) { case G_LOG_LEVEL_ERROR: return " ERROR"; case G_LOG_LEVEL_CRITICAL: return "CRITICAL"; case G_LOG_LEVEL_WARNING: return " WARNING"; case G_LOG_LEVEL_MESSAGE: return " MESSAGE"; case G_LOG_LEVEL_INFO: return " INFO"; case G_LOG_LEVEL_DEBUG: return " DEBUG"; case IDE_LOG_LEVEL_TRACE: return " TRACE"; default: return " UNKNOWN"; } } static const gchar * ide_log_level_str_with_color (GLogLevelFlags log_level) { switch (((gulong)log_level & G_LOG_LEVEL_MASK)) { case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m"; case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m"; case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m"; case G_LOG_LEVEL_MESSAGE: return " \033[1;32mMESSAGE\033[0m"; case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m"; case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m"; case IDE_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m"; default: return " UNKNOWN"; } } /** * ide_log_write_to_channel: * @channel: a #GIOChannel. * @message: A string log message. * * Writes @message to @channel and flushes the channel. * * Since: 3.32 */ static void ide_log_write_to_channel (GIOChannel *channel, const gchar *message) { g_io_channel_write_chars (channel, message, -1, NULL, NULL); g_io_channel_flush (channel, NULL); } /** * ide_log_handler: * @log_domain: A string containing the log section. * @log_level: a #GLogLevelFlags. * @message: The string message. * @user_data: User data supplied to g_log_set_default_handler(). * * Default log handler that will dispatch log messages to configured logging * destinations. * * Since: 3.32 */ static void ide_log_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { gint64 now; struct tm tt; time_t t; const gchar *level; gchar ftime[32]; gchar *buffer; gboolean is_debug_level; /* Ignore GdkPixbuf chatty-ness */ if (g_strcmp0 ("GdkPixbuf", log_domain) == 0) return; /* Let tracer know about log message */ if (log_level < IDE_LOG_LEVEL_TRACE) _ide_trace_log (log_level, log_domain, message); if (G_LIKELY (channels->len)) { is_debug_level = (log_level == G_LOG_LEVEL_DEBUG || log_level == IDE_LOG_LEVEL_TRACE); if (is_debug_level && has_domains && (log_domain == NULL || strstr (domains, log_domain) == NULL)) return; switch ((int)log_level) { case G_LOG_LEVEL_MESSAGE: if (log_verbosity < 1) return; break; case G_LOG_LEVEL_INFO: if (log_verbosity < 2) return; break; case G_LOG_LEVEL_DEBUG: if (log_verbosity < 3) return; break; case IDE_LOG_LEVEL_TRACE: if (log_verbosity < 4) return; break; default: break; } level = log_level_str_func (log_level); now = g_get_real_time (); t = now / G_USEC_PER_SEC; tt = *localtime (&t); strftime (ftime, sizeof (ftime), "%H:%M:%S", &tt); buffer = g_strdup_printf ("%s.%04d %40s[% 5d]: %s: %s\n", ftime, (gint)((now % G_USEC_PER_SEC) / 100L), log_domain, ide_log_get_thread (), level, message); G_LOCK (channels_lock); g_ptr_array_foreach (channels, (GFunc) ide_log_write_to_channel, buffer); G_UNLOCK (channels_lock); g_free (buffer); } } /** * ide_log_init: * @stdout_: Indicates logging should be written to stdout. * @filename: An optional file in which to store logs. * * Initializes the logging subsystem. This should be called from * the application entry point only. Secondary calls to this function * will do nothing. * * Since: 3.32 */ void ide_log_init (gboolean stdout_, const gchar *filename) { static gsize initialized = FALSE; GIOChannel *channel; if (g_once_init_enter (&initialized)) { log_level_str_func = ide_log_level_str; channels = g_ptr_array_new (); if (filename) { channel = g_io_channel_new_file (filename, "a", NULL); g_ptr_array_add (channels, channel); } if (stdout_) { channel = g_io_channel_unix_new (STDOUT_FILENO); g_ptr_array_add (channels, channel); if ((filename == NULL) && isatty (STDOUT_FILENO)) log_level_str_func = ide_log_level_str_with_color; } domains = g_strdup (g_getenv ("G_MESSAGES_DEBUG")); if (!ide_str_empty0 (domains) && strcmp (domains, "all") != 0) has_domains = TRUE; g_log_set_default_handler (ide_log_handler, NULL); g_once_init_leave (&initialized, TRUE); } } /** * ide_log_shutdown: * * Cleans up after the logging subsystem and restores the original * log handler. * * Since: 3.32 */ void ide_log_shutdown (void) { if (last_handler) { g_log_set_default_handler (last_handler, NULL); last_handler = NULL; } g_clear_pointer (&domains, g_free); } /** * ide_log_increase_verbosity: * * Increases the amount of logging that will occur. By default, only * warning and above will be displayed. * * Calling this once will cause %G_LOG_LEVEL_MESSAGE to be displayed. * Calling this twice will cause %G_LOG_LEVEL_INFO to be displayed. * Calling this thrice will cause %G_LOG_LEVEL_DEBUG to be displayed. * Calling this four times will cause %IDE_LOG_LEVEL_TRACE to be displayed. * * Note that many DEBUG and TRACE level log messages are only compiled into * debug builds, and therefore will not be available in release builds. * * This method is meant to be called for every -v provided on the command * line. * * Calling this method more than four times is acceptable. * * Since: 3.32 */ void ide_log_increase_verbosity (void) { log_verbosity++; } /** * ide_log_get_verbosity: * * Retrieves the log verbosity, which is the number of times -v was * provided on the command line. * * Since: 3.32 */ gint ide_log_get_verbosity (void) { return log_verbosity; } /** * ide_log_set_verbosity: * * Sets the explicit verbosity. Generally you want to use * ide_log_increase_verbosity() instead of this function. * * Since: 3.32 */ void ide_log_set_verbosity (gint level) { log_verbosity = level; }