gnome-control-center/shell/cc-log.c
Mohammed Sadiq 717cc3e0fc log: Show logs from Bluetooth panel
Logs from Bluetooth panel have "Bluetooth" as G_LOG_DOMAIN.
Show them by default on -v
2023-01-25 09:55:34 +00:00

482 lines
12 KiB
C

/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* cc-log.c
*
* Copyright © 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
* Copyright 2022 Mohammed Sadiq <sadiq@sadiqpk.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, see <http://www.gnu.org/licenses/>.
*
* Author(s):
* Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
* Mohammed Sadiq <sadiq@sadiqpk.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <glib.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "cc-log.h"
#define DEFAULT_DOMAIN_PREFIX "cc"
#define BLUETOOTH_DOMAIN_PREFIX "Bluetooth"
char *domains;
static int verbosity;
gboolean any_domain;
gboolean no_anonymize;
gboolean stderr_is_journal;
gboolean fatal_criticals, fatal_warnings;
/* Copied from GLib, LGPLv2.1+ */
static void
_g_log_abort (gboolean breakpoint)
{
gboolean debugger_present;
if (g_test_subprocess ())
{
/* If this is a test case subprocess then it probably caused
* this error message on purpose, so just exit() rather than
* abort()ing, to avoid triggering any system crash-reporting
* daemon.
*/
_exit (1);
}
#ifdef G_OS_WIN32
debugger_present = IsDebuggerPresent ();
#else
/* Assume GDB is attached. */
debugger_present = TRUE;
#endif /* !G_OS_WIN32 */
if (debugger_present && breakpoint)
G_BREAKPOINT ();
else
g_abort ();
}
static gboolean
should_show_log_for_level (GLogLevelFlags log_level,
int verbosity_level)
{
if (verbosity_level >= 5)
return TRUE;
if (log_level & CC_LOG_LEVEL_TRACE)
return verbosity_level >= 4;
if (log_level & G_LOG_LEVEL_DEBUG)
return verbosity_level >= 3;
if (log_level & G_LOG_LEVEL_INFO)
return verbosity_level >= 2;
if (log_level & G_LOG_LEVEL_MESSAGE)
return verbosity_level >= 1;
return FALSE;
}
static gboolean
matches_domain (const char *log_domains,
const char *domain)
{
g_auto(GStrv) domain_list = NULL;
if (!log_domains || !*log_domains ||
!domain || !*domain)
return FALSE;
domain_list = g_strsplit (log_domains, ",", -1);
for (guint i = 0; domain_list[i]; i++)
{
if (g_str_has_prefix (domain, domain_list[i]))
return TRUE;
}
return FALSE;
}
static gboolean
should_log (const char *log_domain,
GLogLevelFlags log_level)
{
g_assert (log_domain);
/* Ignore custom flags set */
log_level = log_level & ~CC_LOG_DETAILED;
/* Don't skip serious logs */
if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
return TRUE;
if (any_domain && domains) {
/* If domain is “all” show logs upto debug regardless of the verbosity */
if (log_level & ~CC_LOG_LEVEL_TRACE)
return TRUE;
/* If the log is trace level, log if verbosity >= 4 */
return verbosity >= 4;
}
if (!domains &&
(g_str_has_prefix (log_domain, DEFAULT_DOMAIN_PREFIX) ||
g_str_has_prefix (log_domain, BLUETOOTH_DOMAIN_PREFIX)))
return should_show_log_for_level (log_level, verbosity);
if (domains && matches_domain (domains, log_domain))
return should_show_log_for_level (log_level, verbosity);
/* If we didn't handle domains in the preceding statement,
* we should no longer log them */
if (domains)
return FALSE;
/* GdkPixbuf and Gvc logs are too much verbose, skip unless asked not to. */
if (verbosity < 8 &&
(g_strcmp0 (log_domain, "GdkPixbuf") == 0 ||
g_strcmp0 (log_domain, "Gvc") == 0) &&
(!domains || !strstr (domains, log_domain)))
return FALSE;
if (verbosity >= 6)
return TRUE;
return FALSE;
}
static void
log_str_append_log_domain (GString *log_str,
const char *log_domain,
gboolean color)
{
static const char *colors[] = {
"\033[1;32m",
"\033[1;33m",
"\033[1;35m",
"\033[1;36m",
"\033[1;91m",
"\033[1;92m",
"\033[1;93m",
"\033[1;94m",
"\033[1;95m",
"\033[1;96m",
};
guint i;
g_assert (log_domain && *log_domain);
i = g_str_hash (log_domain) % G_N_ELEMENTS (colors);
if (color)
g_string_append (log_str, colors[i]);
g_string_append_printf (log_str, "%20s", log_domain);
if (color)
g_string_append (log_str, "\033[0m");
}
static const char *
get_log_level_prefix (GLogLevelFlags log_level,
gboolean use_color)
{
/* Ignore custom flags set */
log_level = log_level & ~CC_LOG_DETAILED;
if (use_color)
{
switch ((int)log_level) /* Same colors as used in GLib */
{
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 CC_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m";
default: return " UNKNOWN";
}
}
else
{
switch ((int)log_level)
{
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 CC_LOG_LEVEL_TRACE: return " TRACE";
default: return " UNKNOWN";
}
}
}
static GLogWriterOutput
cc_log_write (GLogLevelFlags log_level,
const char *log_domain,
const char *log_message,
const GLogField *fields,
gsize n_fields,
gpointer user_data)
{
g_autoptr(GString) log_str = NULL;
FILE *stream = stdout;
gboolean can_color;
if (stderr_is_journal &&
g_log_writer_journald (log_level, fields, n_fields, user_data) == G_LOG_WRITER_HANDLED)
return G_LOG_WRITER_HANDLED;
if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
stream = stderr;
log_str = g_string_new (NULL);
/* Add local time */
{
char buffer[32];
struct tm tm_now;
time_t sec_now;
gint64 now;
now = g_get_real_time ();
sec_now = now / G_USEC_PER_SEC;
tm_now = *localtime (&sec_now);
strftime (buffer, sizeof (buffer), "%H:%M:%S", &tm_now);
g_string_append_printf (log_str, "%s.%04d ", buffer,
(int)((now % G_USEC_PER_SEC) / 100));
}
can_color = g_log_writer_supports_color (fileno (stream));
log_str_append_log_domain (log_str, log_domain, can_color);
g_string_append_printf (log_str, "[%5d]:", getpid ());
g_string_append_printf (log_str, "%s: ", get_log_level_prefix (log_level, can_color));
if (log_level & CC_LOG_DETAILED)
{
const char *code_func = NULL, *code_line = NULL;
for (guint i = 0; i < n_fields; i++)
{
const GLogField *field = &fields[i];
if (!code_func && g_strcmp0 (field->key, "CODE_FUNC") == 0)
code_func = field->value;
else if (!code_line && g_strcmp0 (field->key, "CODE_LINE") == 0)
code_line = field->value;
if (code_func && code_line)
break;
}
if (code_func)
{
g_string_append_printf (log_str, "%s():", code_func);
if (code_line)
g_string_append_printf (log_str, "%s:", code_line);
g_string_append_c (log_str, ' ');
}
}
g_string_append (log_str, log_message);
fprintf (stream, "%s\n", log_str->str);
fflush (stream);
if (fatal_criticals &&
(log_level & G_LOG_LEVEL_CRITICAL))
log_level |= G_LOG_FLAG_FATAL;
else if (fatal_warnings &&
(log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)))
log_level |= G_LOG_FLAG_FATAL;
if (log_level & (G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR))
_g_log_abort (!(log_level & G_LOG_FLAG_RECURSION));
return G_LOG_WRITER_HANDLED;
}
static GLogWriterOutput
cc_log_handler (GLogLevelFlags log_level,
const GLogField *fields,
gsize n_fields,
gpointer user_data)
{
const char *log_domain = NULL;
const char *log_message = NULL;
for (guint i = 0; (!log_domain || !log_message) && i < n_fields; i++)
{
const GLogField *field = &fields[i];
if (g_strcmp0 (field->key, "GLIB_DOMAIN") == 0)
log_domain = field->value;
else if (g_strcmp0 (field->key, "MESSAGE") == 0)
log_message = field->value;
}
if (!log_domain)
log_domain = "**";
if (!log_message)
log_message = "(NULL) message";
if (!should_log (log_domain, log_level))
return G_LOG_WRITER_HANDLED;
return cc_log_write (log_level, log_domain, log_message,
fields, n_fields, user_data);
}
static void
cc_log_finalize (void)
{
g_clear_pointer (&domains, g_free);
}
void
cc_log_init (void)
{
static gsize initialized = 0;
if (g_once_init_enter (&initialized))
{
domains = g_strdup (g_getenv ("G_MESSAGES_DEBUG"));
if (domains && !*domains)
g_clear_pointer (&domains, g_free);
if (!domains || g_str_equal (domains, "all"))
any_domain = TRUE;
if (domains && strstr (domains, "no-anonymize"))
{
any_domain = TRUE;
no_anonymize = TRUE;
g_clear_pointer (&domains, g_free);
}
if (g_strcmp0 (g_getenv ("G_DEBUG"), "fatal-criticals") == 0)
fatal_criticals = TRUE;
else if (g_strcmp0 (g_getenv ("G_DEBUG"), "fatal-warnings") == 0)
fatal_warnings = TRUE;
stderr_is_journal = g_log_writer_is_journald (fileno (stderr));
g_log_set_writer_func (cc_log_handler, NULL, NULL);
g_once_init_leave (&initialized, 1);
atexit (cc_log_finalize);
}
}
void
cc_log_increase_verbosity (void)
{
verbosity++;
}
int
cc_log_get_verbosity (void)
{
return verbosity;
}
void
cc_log (const char *domain,
GLogLevelFlags log_level,
const char *value,
const char *file,
const char *line,
const char *func,
const char *message_format,
...)
{
g_autoptr(GString) str = NULL;
va_list args;
if (!message_format || !*message_format)
return;
if (!should_log (domain, log_level))
return;
str = g_string_new (NULL);
va_start (args, message_format);
g_string_append_vprintf (str, message_format, args);
va_end (args);
cc_log_anonymize_value (str, value);
g_log_structured (domain, log_level,
"CODE_FILE", file,
"CODE_LINE", line,
"CODE_FUNC", func,
"MESSAGE", "%s", str->str);
}
void
cc_log_anonymize_value (GString *str,
const char *value)
{
gunichar c, next_c, prev_c;
if (!value || !*value)
return;
g_assert (str);
if (str->len && str->str[str->len - 1] != ' ')
g_string_append_c (str, ' ');
if (no_anonymize)
{
g_string_append (str, value);
return;
}
if (!g_utf8_validate (value, -1, NULL))
{
g_string_append (str, "******");
return;
}
c = g_utf8_get_char (value);
g_string_append_unichar (str, c);
value = g_utf8_next_char (value);
while (*value)
{
prev_c = c;
c = g_utf8_get_char (value);
value = g_utf8_next_char (value);
next_c = g_utf8_get_char (value);
if (!g_unichar_isalnum (c))
g_string_append_unichar (str, c);
else if (!g_unichar_isalnum (prev_c) || !g_unichar_isalnum (next_c))
g_string_append_unichar (str, c);
else
g_string_append_c (str, '#');
}
}