Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2008 litl, LLC
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <errno.h>
8 : : #include <stdarg.h>
9 : : #include <stdio.h> // for FILE, fprintf, fflush, fopen, fputs, fseek
10 : : #include <string.h> // for strchr, strcmp
11 : :
12 : : #ifdef _WIN32
13 : : # include <io.h>
14 : : # include <process.h>
15 : : # ifndef F_OK
16 : : # define F_OK 0
17 : : # endif
18 : : #else
19 : : # include <unistd.h> // for getpid
20 : : #endif
21 : :
22 : : #include <array>
23 : : #include <atomic> // for atomic_bool
24 : : #include <memory> // for unique_ptr
25 : : #include <string> // for string
26 : :
27 : : #include <glib.h>
28 : :
29 : : #include "gjs/auto.h"
30 : : #include "util/log.h"
31 : : #include "util/misc.h"
32 : :
33 : : static std::atomic_bool s_initialized = ATOMIC_VAR_INIT(false);
34 : : static bool s_debug_log_enabled = false;
35 : : static bool s_print_thread = false;
36 : : static std::unique_ptr<LogFile> s_log_file;
37 : 0 : static Gjs::AutoPointer<GTimer, GTimer, g_timer_destroy> s_timer;
38 : : static std::array<bool, GJS_DEBUG_LAST> s_enabled_topics;
39 : :
40 : 0 : static const char* topic_to_prefix(GjsDebugTopic topic) {
41 [ # # # # : 0 : switch (topic) {
# # # # #
# # # # #
# # # # #
# # ]
42 : 0 : case GJS_DEBUG_GI_USAGE:
43 : 0 : return "JS GI USE";
44 : 0 : case GJS_DEBUG_MEMORY:
45 : 0 : return "JS MEMORY";
46 : 0 : case GJS_DEBUG_CONTEXT:
47 : 0 : return "JS CTX";
48 : 0 : case GJS_DEBUG_IMPORTER:
49 : 0 : return "JS IMPORT";
50 : 0 : case GJS_DEBUG_NATIVE:
51 : 0 : return "JS NATIVE";
52 : 0 : case GJS_DEBUG_CAIRO:
53 : 0 : return "JS CAIRO";
54 : 0 : case GJS_DEBUG_KEEP_ALIVE:
55 : 0 : return "JS KP ALV";
56 : 0 : case GJS_DEBUG_MAINLOOP:
57 : 0 : return "JS MAINLOOP";
58 : 0 : case GJS_DEBUG_GREPO:
59 : 0 : return "JS G REPO";
60 : 0 : case GJS_DEBUG_GNAMESPACE:
61 : 0 : return "JS G NS";
62 : 0 : case GJS_DEBUG_GOBJECT:
63 : 0 : return "JS G OBJ";
64 : 0 : case GJS_DEBUG_GFUNCTION:
65 : 0 : return "JS G FUNC";
66 : 0 : case GJS_DEBUG_GFUNDAMENTAL:
67 : 0 : return "JS G FNDMTL";
68 : 0 : case GJS_DEBUG_GCLOSURE:
69 : 0 : return "JS G CLSR";
70 : 0 : case GJS_DEBUG_GBOXED:
71 : 0 : return "JS G BXD";
72 : 0 : case GJS_DEBUG_GENUM:
73 : 0 : return "JS G ENUM";
74 : 0 : case GJS_DEBUG_GPARAM:
75 : 0 : return "JS G PRM";
76 : 0 : case GJS_DEBUG_GERROR:
77 : 0 : return "JS G ERR";
78 : 0 : case GJS_DEBUG_GINTERFACE:
79 : 0 : return "JS G IFACE";
80 : 0 : case GJS_DEBUG_GTYPE:
81 : 0 : return "JS GTYPE";
82 : 0 : default:
83 : 0 : return "???";
84 : : }
85 : : }
86 : :
87 : 0 : static GjsDebugTopic prefix_to_topic(const char* prefix) {
88 [ # # ]: 0 : for (unsigned i = 0; i < GJS_DEBUG_LAST; i++) {
89 : 0 : auto topic = static_cast<GjsDebugTopic>(i);
90 [ # # ]: 0 : if (g_str_equal(topic_to_prefix(topic), prefix))
91 : 0 : return topic;
92 : : }
93 : :
94 : 0 : return GJS_DEBUG_LAST;
95 : : }
96 : :
97 : 371 : void gjs_log_init() {
98 : 371 : bool expected = false;
99 [ + + ]: 371 : if (!s_initialized.compare_exchange_strong(expected, true))
100 : 115 : return;
101 : :
102 [ - + ]: 256 : if (gjs_environment_variable_is_set("GJS_DEBUG_TIMESTAMP"))
103 : 0 : s_timer = g_timer_new();
104 : :
105 : 256 : s_print_thread = gjs_environment_variable_is_set("GJS_DEBUG_THREAD");
106 : :
107 : 256 : const char* debug_output = g_getenv("GJS_DEBUG_OUTPUT");
108 [ + - + - ]: 256 : if (debug_output && g_str_equal(debug_output, "stderr")) {
109 : 256 : s_debug_log_enabled = true;
110 [ # # ]: 0 : } else if (debug_output) {
111 : 0 : std::string log_file;
112 : :
113 : : /* Allow debug-%u.log for per-pid logfiles as otherwise log messages
114 : : * from multiple processes can overwrite each other.
115 : : *
116 : : * (printf below should be safe as we check '%u' is the only format
117 : : * string)
118 : : */
119 : 0 : char* c = strchr(const_cast<char*>(debug_output), '%');
120 [ # # # # : 0 : if (c && c[1] == 'u' && !strchr(c + 1, '%')) {
# # ]
121 : 0 : Gjs::AutoChar file_name;
122 : : #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
123 : : _Pragma("GCC diagnostic push")
124 : : _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
125 : : #endif
126 : 0 : file_name = g_strdup_printf(debug_output, getpid());
127 : : #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
128 : : _Pragma("GCC diagnostic pop")
129 : : #endif
130 : 0 : log_file = file_name.get();
131 : 0 : } else {
132 : 0 : log_file = debug_output;
133 : : }
134 : :
135 : : // avoid truncating in case we're using shared logfile
136 : 0 : s_log_file = std::make_unique<LogFile>(log_file.c_str());
137 [ # # ]: 0 : if (s_log_file->has_error()) {
138 : 0 : fprintf(stderr, "Failed to open log file `%s': %s\n",
139 : 0 : log_file.c_str(), g_strerror(errno));
140 : : }
141 : :
142 : 0 : s_debug_log_enabled = true;
143 : 0 : }
144 : :
145 [ + + ]: 256 : if (!s_log_file)
146 : 115 : s_log_file = std::make_unique<LogFile>(nullptr, stderr);
147 : :
148 [ + - ]: 256 : if (s_debug_log_enabled) {
149 : 256 : const char* topics = g_getenv("GJS_DEBUG_TOPICS");
150 : 256 : s_enabled_topics.fill(topics == nullptr);
151 [ + - ]: 256 : if (topics) {
152 : 256 : Gjs::AutoStrv prefixes{g_strsplit(topics, ";", -1)};
153 [ - + ]: 256 : for (unsigned i = 0; prefixes[i] != nullptr; i++) {
154 : 0 : GjsDebugTopic topic = prefix_to_topic(prefixes[i]);
155 [ # # ]: 0 : if (topic != GJS_DEBUG_LAST)
156 : 0 : s_enabled_topics[topic] = true;
157 : : }
158 : 256 : }
159 : : }
160 : : }
161 : :
162 : 254 : void gjs_log_cleanup() {
163 : 254 : bool expected = true;
164 [ - + ]: 254 : if (!s_initialized.compare_exchange_strong(expected, false))
165 : 0 : return;
166 : :
167 : 254 : s_timer = nullptr;
168 : 254 : s_enabled_topics.fill(false);
169 : : }
170 : :
171 : : #define PREFIX_LENGTH 12
172 : :
173 : 0 : static void write_to_stream(FILE* logfp, const char* prefix, const char* s) {
174 : : // seek to end to avoid truncating in case we're using shared logfile
175 : 0 : (void)fseek(logfp, 0, SEEK_END);
176 : :
177 : 0 : fprintf(logfp, "%*s: %s", PREFIX_LENGTH, prefix, s);
178 [ # # # # : 0 : if (!g_str_has_suffix(s, "\n"))
# # ]
179 : 0 : fputs("\n", logfp);
180 : 0 : fflush(logfp);
181 : 0 : }
182 : :
183 : : // COMPAT: Replace with a format string in C++20.
184 : : // NOLINTNEXTLINE(modernize-avoid-variadic-functions)
185 : 67616 : void gjs_debug(GjsDebugTopic topic, const char* format, ...) {
186 : : va_list args;
187 : :
188 [ + - + - : 67616 : if (!s_debug_log_enabled || !s_enabled_topics[topic])
+ - ]
189 : 67616 : return;
190 : :
191 : 0 : va_start(args, format);
192 : 0 : Gjs::AutoChar s{g_strdup_vprintf(format, args)};
193 : 0 : va_end(args);
194 : :
195 [ # # ]: 0 : if (s_timer) {
196 : : static double previous = 0.0;
197 : 0 : double total = g_timer_elapsed(s_timer, nullptr) * 1000.0;
198 : 0 : double since = total - previous;
199 : : const char* ts_suffix;
200 : :
201 [ # # ]: 0 : if (since > 50.0) {
202 : 0 : ts_suffix = "!! ";
203 [ # # ]: 0 : } else if (since > 100.0) {
204 : 0 : ts_suffix = "!!! ";
205 [ # # ]: 0 : } else if (since > 200.0) {
206 : 0 : ts_suffix = "!!!!";
207 : : } else {
208 : 0 : ts_suffix = " ";
209 : : }
210 : :
211 : 0 : s.reset(g_strdup_printf("%g %s%s", total, ts_suffix, s.get()));
212 : :
213 : 0 : previous = total;
214 : : }
215 : :
216 [ # # ]: 0 : if (s_print_thread) {
217 : 0 : s.reset(g_strdup_printf("(thread %p) %s", g_thread_self(), s.get()));
218 : : }
219 : :
220 : 0 : write_to_stream(s_log_file->fp(), topic_to_prefix(topic), s);
221 : 0 : }
|