391 lines
9.9 KiB
C
391 lines
9.9 KiB
C
/* ide-log.c
|
|
*
|
|
* Copyright 2015-2019 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <sys/types.h>
|
|
# include <sys/syscall.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#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.
|
|
*
|
|
* |[<!-- Language="py" -->
|
|
* 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;
|
|
}
|