GCC Code Coverage Report


Directory: ./
File: shell/cc-log.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 210 0.0%
Functions: 0 14 0.0%
Branches: 0 220 0.0%

Line Branch Exec Source
1 /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
2 /* cc-log.c
3 *
4 * Copyright © 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
5 * Copyright 2022 Mohammed Sadiq <sadiq@sadiqpk.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * Author(s):
21 * Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
22 * Mohammed Sadiq <sadiq@sadiqpk.org>
23 *
24 * SPDX-License-Identifier: GPL-2.0-or-later
25 */
26
27 #include <glib.h>
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32
33 #include "cc-log.h"
34
35 #define DEFAULT_DOMAIN_PREFIX "cc"
36 #define BLUETOOTH_DOMAIN_PREFIX "Bluetooth"
37
38 char *domains;
39 static int verbosity;
40 gboolean any_domain;
41 gboolean no_anonymize;
42 gboolean stderr_is_journal;
43 gboolean fatal_criticals, fatal_warnings;
44
45 /* Copied from GLib, LGPLv2.1+ */
46 static void
47 _g_log_abort (gboolean breakpoint)
48 {
49 gboolean debugger_present;
50
51 if (g_test_subprocess ())
52 {
53 /* If this is a test case subprocess then it probably caused
54 * this error message on purpose, so just exit() rather than
55 * abort()ing, to avoid triggering any system crash-reporting
56 * daemon.
57 */
58 _exit (1);
59 }
60
61 #ifdef G_OS_WIN32
62 debugger_present = IsDebuggerPresent ();
63 #else
64 /* Assume GDB is attached. */
65 debugger_present = TRUE;
66 #endif /* !G_OS_WIN32 */
67
68 if (debugger_present && breakpoint)
69 G_BREAKPOINT ();
70 else
71 g_abort ();
72 }
73
74 static gboolean
75 should_show_log_for_level (GLogLevelFlags log_level,
76 int verbosity_level)
77 {
78 if (verbosity_level >= 5)
79 return TRUE;
80
81 if (log_level & CC_LOG_LEVEL_TRACE)
82 return verbosity_level >= 4;
83
84 if (log_level & G_LOG_LEVEL_DEBUG)
85 return verbosity_level >= 3;
86
87 if (log_level & G_LOG_LEVEL_INFO)
88 return verbosity_level >= 2;
89
90 if (log_level & G_LOG_LEVEL_MESSAGE)
91 return verbosity_level >= 1;
92
93 return FALSE;
94 }
95
96 static gboolean
97 matches_domain (const char *log_domains,
98 const char *domain)
99 {
100 g_auto(GStrv) domain_list = NULL;
101
102 if (!log_domains || !*log_domains ||
103 !domain || !*domain)
104 return FALSE;
105
106 domain_list = g_strsplit (log_domains, ",", -1);
107
108 for (guint i = 0; domain_list[i]; i++)
109 {
110 if (g_str_has_prefix (domain, domain_list[i]))
111 return TRUE;
112 }
113
114 return FALSE;
115 }
116
117 static gboolean
118 should_log (const char *log_domain,
119 GLogLevelFlags log_level)
120 {
121 g_assert (log_domain);
122
123 /* Ignore custom flags set */
124 log_level = log_level & ~CC_LOG_DETAILED;
125
126 /* Don't skip serious logs */
127 if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
128 return TRUE;
129
130 if (any_domain && domains) {
131 /* If domain is “all” show logs upto debug regardless of the verbosity */
132 if (log_level & ~CC_LOG_LEVEL_TRACE)
133 return TRUE;
134
135 /* If the log is trace level, log if verbosity >= 4 */
136 return verbosity >= 4;
137 }
138
139 if (!domains &&
140 (g_str_has_prefix (log_domain, DEFAULT_DOMAIN_PREFIX) ||
141 g_str_has_prefix (log_domain, BLUETOOTH_DOMAIN_PREFIX)))
142 return should_show_log_for_level (log_level, verbosity);
143
144 if (domains && matches_domain (domains, log_domain))
145 return should_show_log_for_level (log_level, verbosity);
146
147 /* If we didn't handle domains in the preceding statement,
148 * we should no longer log them */
149 if (domains)
150 return FALSE;
151
152 /* GdkPixbuf and Gvc logs are too much verbose, skip unless asked not to. */
153 if (verbosity < 8 &&
154 (g_strcmp0 (log_domain, "GdkPixbuf") == 0 ||
155 g_strcmp0 (log_domain, "Gvc") == 0) &&
156 (!domains || !strstr (domains, log_domain)))
157 return FALSE;
158
159 if (verbosity >= 6)
160 return TRUE;
161
162 return FALSE;
163 }
164
165 static void
166 log_str_append_log_domain (GString *log_str,
167 const char *log_domain,
168 gboolean color)
169 {
170 static const char *colors[] = {
171 "\033[1;32m",
172 "\033[1;33m",
173 "\033[1;35m",
174 "\033[1;36m",
175 "\033[1;91m",
176 "\033[1;92m",
177 "\033[1;93m",
178 "\033[1;94m",
179 "\033[1;95m",
180 "\033[1;96m",
181 };
182 guint i;
183
184 g_assert (log_domain && *log_domain);
185
186 i = g_str_hash (log_domain) % G_N_ELEMENTS (colors);
187
188 if (color)
189 g_string_append (log_str, colors[i]);
190 g_string_append_printf (log_str, "%20s", log_domain);
191
192 if (color)
193 g_string_append (log_str, "\033[0m");
194 }
195
196 static const char *
197 get_log_level_prefix (GLogLevelFlags log_level,
198 gboolean use_color)
199 {
200 /* Ignore custom flags set */
201 log_level = log_level & ~CC_LOG_DETAILED;
202
203 if (use_color)
204 {
205 switch ((int)log_level) /* Same colors as used in GLib */
206 {
207 case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m";
208 case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m";
209 case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m";
210 case G_LOG_LEVEL_MESSAGE: return " \033[1;32mMESSAGE\033[0m";
211 case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m";
212 case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m";
213 case CC_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m";
214 default: return " UNKNOWN";
215 }
216 }
217 else
218 {
219 switch ((int)log_level)
220 {
221 case G_LOG_LEVEL_ERROR: return " ERROR";
222 case G_LOG_LEVEL_CRITICAL: return "CRITICAL";
223 case G_LOG_LEVEL_WARNING: return " WARNING";
224 case G_LOG_LEVEL_MESSAGE: return " MESSAGE";
225 case G_LOG_LEVEL_INFO: return " INFO";
226 case G_LOG_LEVEL_DEBUG: return " DEBUG";
227 case CC_LOG_LEVEL_TRACE: return " TRACE";
228 default: return " UNKNOWN";
229 }
230 }
231 }
232
233 static GLogWriterOutput
234 cc_log_write (GLogLevelFlags log_level,
235 const char *log_domain,
236 const char *log_message,
237 const GLogField *fields,
238 gsize n_fields,
239 gpointer user_data)
240 {
241 g_autoptr(GString) log_str = NULL;
242 FILE *stream = stdout;
243 gboolean can_color;
244
245 if (stderr_is_journal &&
246 g_log_writer_journald (log_level, fields, n_fields, user_data) == G_LOG_WRITER_HANDLED)
247 return G_LOG_WRITER_HANDLED;
248
249 if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING))
250 stream = stderr;
251
252 log_str = g_string_new (NULL);
253
254 /* Add local time */
255 {
256 char buffer[32];
257 struct tm tm_now;
258 time_t sec_now;
259 gint64 now;
260
261 now = g_get_real_time ();
262 sec_now = now / G_USEC_PER_SEC;
263 tm_now = *localtime (&sec_now);
264 strftime (buffer, sizeof (buffer), "%H:%M:%S", &tm_now);
265
266 g_string_append_printf (log_str, "%s.%04d ", buffer,
267 (int)((now % G_USEC_PER_SEC) / 100));
268 }
269
270 can_color = g_log_writer_supports_color (fileno (stream));
271 log_str_append_log_domain (log_str, log_domain, can_color);
272 g_string_append_printf (log_str, "[%5d]:", getpid ());
273
274 g_string_append_printf (log_str, "%s: ", get_log_level_prefix (log_level, can_color));
275
276 if (log_level & CC_LOG_DETAILED)
277 {
278 const char *code_func = NULL, *code_line = NULL;
279 for (guint i = 0; i < n_fields; i++)
280 {
281 const GLogField *field = &fields[i];
282
283 if (!code_func && g_strcmp0 (field->key, "CODE_FUNC") == 0)
284 code_func = field->value;
285 else if (!code_line && g_strcmp0 (field->key, "CODE_LINE") == 0)
286 code_line = field->value;
287
288 if (code_func && code_line)
289 break;
290 }
291
292 if (code_func)
293 {
294 g_string_append_printf (log_str, "%s():", code_func);
295
296 if (code_line)
297 g_string_append_printf (log_str, "%s:", code_line);
298 g_string_append_c (log_str, ' ');
299 }
300 }
301
302 g_string_append (log_str, log_message);
303
304 fprintf (stream, "%s\n", log_str->str);
305 fflush (stream);
306
307 if (fatal_criticals &&
308 (log_level & G_LOG_LEVEL_CRITICAL))
309 log_level |= G_LOG_FLAG_FATAL;
310 else if (fatal_warnings &&
311 (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)))
312 log_level |= G_LOG_FLAG_FATAL;
313
314 if (log_level & (G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR))
315 _g_log_abort (!(log_level & G_LOG_FLAG_RECURSION));
316
317 return G_LOG_WRITER_HANDLED;
318 }
319
320 static GLogWriterOutput
321 cc_log_handler (GLogLevelFlags log_level,
322 const GLogField *fields,
323 gsize n_fields,
324 gpointer user_data)
325 {
326 const char *log_domain = NULL;
327 const char *log_message = NULL;
328
329 for (guint i = 0; (!log_domain || !log_message) && i < n_fields; i++)
330 {
331 const GLogField *field = &fields[i];
332
333 if (g_strcmp0 (field->key, "GLIB_DOMAIN") == 0)
334 log_domain = field->value;
335 else if (g_strcmp0 (field->key, "MESSAGE") == 0)
336 log_message = field->value;
337 }
338
339 if (!log_domain)
340 log_domain = "**";
341
342 if (!log_message)
343 log_message = "(NULL) message";
344
345 if (!should_log (log_domain, log_level))
346 return G_LOG_WRITER_HANDLED;
347
348 return cc_log_write (log_level, log_domain, log_message,
349 fields, n_fields, user_data);
350 }
351
352 static void
353 cc_log_finalize (void)
354 {
355 g_clear_pointer (&domains, g_free);
356 }
357
358 void
359 cc_log_init (void)
360 {
361 static gsize initialized = 0;
362
363 if (g_once_init_enter (&initialized))
364 {
365 domains = g_strdup (g_getenv ("G_MESSAGES_DEBUG"));
366
367 if (domains && !*domains)
368 g_clear_pointer (&domains, g_free);
369
370 if (!domains || g_str_equal (domains, "all"))
371 any_domain = TRUE;
372
373 if (domains && strstr (domains, "no-anonymize"))
374 {
375 any_domain = TRUE;
376 no_anonymize = TRUE;
377 g_clear_pointer (&domains, g_free);
378 }
379
380 if (g_strcmp0 (g_getenv ("G_DEBUG"), "fatal-criticals") == 0)
381 fatal_criticals = TRUE;
382 else if (g_strcmp0 (g_getenv ("G_DEBUG"), "fatal-warnings") == 0)
383 fatal_warnings = TRUE;
384
385 stderr_is_journal = g_log_writer_is_journald (fileno (stderr));
386 g_log_set_writer_func (cc_log_handler, NULL, NULL);
387 g_once_init_leave (&initialized, 1);
388 atexit (cc_log_finalize);
389 }
390 }
391
392 void
393 cc_log_increase_verbosity (void)
394 {
395 verbosity++;
396 }
397
398 int
399 cc_log_get_verbosity (void)
400 {
401 return verbosity;
402 }
403
404 void
405 cc_log (const char *domain,
406 GLogLevelFlags log_level,
407 const char *value,
408 const char *file,
409 const char *line,
410 const char *func,
411 const char *message_format,
412 ...)
413 {
414 g_autoptr(GString) str = NULL;
415 va_list args;
416
417 if (!message_format || !*message_format)
418 return;
419
420 if (!should_log (domain, log_level))
421 return;
422
423 str = g_string_new (NULL);
424 va_start (args, message_format);
425 g_string_append_vprintf (str, message_format, args);
426 va_end (args);
427
428 cc_log_anonymize_value (str, value);
429 g_log_structured (domain, log_level,
430 "CODE_FILE", file,
431 "CODE_LINE", line,
432 "CODE_FUNC", func,
433 "MESSAGE", "%s", str->str);
434 }
435
436 void
437 cc_log_anonymize_value (GString *str,
438 const char *value)
439 {
440 gunichar c, next_c, prev_c;
441
442 if (!value || !*value)
443 return;
444
445 g_assert (str);
446
447 if (str->len && str->str[str->len - 1] != ' ')
448 g_string_append_c (str, ' ');
449
450 if (no_anonymize)
451 {
452 g_string_append (str, value);
453 return;
454 }
455
456 if (!g_utf8_validate (value, -1, NULL))
457 {
458 g_string_append (str, "******");
459 return;
460 }
461
462 c = g_utf8_get_char (value);
463 g_string_append_unichar (str, c);
464
465 value = g_utf8_next_char (value);
466
467 while (*value)
468 {
469 prev_c = c;
470 c = g_utf8_get_char (value);
471
472 value = g_utf8_next_char (value);
473 next_c = g_utf8_get_char (value);
474
475 if (!g_unichar_isalnum (c))
476 g_string_append_unichar (str, c);
477 else if (!g_unichar_isalnum (prev_c) || !g_unichar_isalnum (next_c))
478 g_string_append_unichar (str, c);
479 else
480 g_string_append_c (str, '#');
481 }
482 }
483