1 /*
2 * gm_log.c
3 * Copyright (C) Kevin DeKorte 2012 <kdekorte@gmail.com> and Hans Ecke
4 *
5 * gm_log.c is free software.
6 *
7 * You may redistribute it and/or modify it under the terms of the
8 * GNU General Public License, as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * gm_log.c 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.
15 * See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with playlist.c. If not, write to:
19 * The Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "gm_log.h"
25
26 /* How to control the GLib logging system: with environment variables
27
28 G_MESSAGES_PREFIXED. A list of log levels for which messages should be prefixed by the
29 program name and PID of the application. The default is to prefix everything except
30 G_LOG_LEVEL_MESSAGE and G_LOG_LEVEL_INFO. The possible values are error, warning, critical,
31 message, info and debug. You can also use the special values all and help.
32
33 G_MESSAGES_PREFIXED="" disables all prefixing
34
35 G_MESSAGES_DEBUG. Since glib 2.31, but emulated below for earlier versions.
36 A space-separated list of log domains for which informational and
37 debug messages should be printed. By default, these messages are not printed.
38
39 G_MESSAGES_DEBUG=GMLIB enables a lot of output from gmlib
40
41 */
42
43 #include <string.h>
44
45 #undef G_LOG_DOMAIN
46 #define G_LOG_DOMAIN ((gchar*) "GMLIB")
47
fixup_loglevel(gboolean force_info_to_message,GLogLevelFlags * log_level)48 static int fixup_loglevel(gboolean force_info_to_message, GLogLevelFlags * log_level)
49 {
50
51 // by default, all messages G_LOG_LEVEL_MESSAGE or above are shown
52 // if our own debug flag is set, force G_LOG_LEVEL_INFO messages to MESSAGE
53 //
54 // GLib loglevels are bitmasks, so we need to do bitmask operations here
55 if (force_info_to_message && ((*log_level) & G_LOG_LEVEL_INFO)) {
56 (*log_level) &= ~G_LOG_LEVEL_INFO;
57 (*log_level) |= G_LOG_LEVEL_MESSAGE;
58 }
59 // emulate G_MESSAGES_DEBUG for glib < 2.31. We determine version at runtime
60 // because it might have changed from the time we compiled this
61 if (glib_major_version == 2 && glib_minor_version < 31) {
62 if ((*log_level) & G_LOG_LEVEL_DEBUG) {
63 const gchar *G_MESSAGES_DEBUG = g_getenv("G_MESSAGES_DEBUG");
64
65 // if it doesn't exists or we can't find the string "GMLIB",
66 // then don't print this message
67 if (G_MESSAGES_DEBUG == NULL) {
68 return 0;
69 }
70 if (G_MESSAGES_DEBUG[0] == '\0') {
71 return 0;
72 }
73 // this is not quite proper, but for a simple emulation whose need will go away in the future...
74 if (strstr(G_MESSAGES_DEBUG, G_LOG_DOMAIN) == NULL && strstr(G_MESSAGES_DEBUG, "all") == NULL) {
75 return 0;
76 }
77 }
78 }
79 return 1;
80 }
81
82 // in newer versions of GLib you can use a static (or stack-allocated)
83 // GMutex directly, but for older versions you need to use a GStaticMutex
84 static GStaticMutex ptr2strmutex = G_STATIC_MUTEX_INIT;
85 static GHashTable *ptr2str = NULL;
86
key_equal_func(gconstpointer a,gconstpointer b)87 static gboolean key_equal_func(gconstpointer a, gconstpointer b)
88 {
89 if (a == b) {
90 return TRUE;
91 } else {
92 return FALSE;
93 }
94 }
95
96 /*
97 This function operates on static data structures, thus it needs to be
98 locked by the above mutex.
99
100 It returns a string representation of the current thread number.
101 Unfortunately, in GLib you can only access g_thread_self(), which is a
102 pointer. Using printf("%p", g_thread_self()) is certainly possible, but
103 difficult to understand at a glance, and hard to compare traces from
104 different runs.
105 */
threadid_core(gchar const * name)106 static const gchar *threadid_core(gchar const *name)
107 {
108 void *key;
109 void *value;
110
111 if (ptr2str == NULL) {
112 ptr2str = g_hash_table_new(g_direct_hash, key_equal_func);
113 }
114 key = g_thread_self();
115 value = g_hash_table_lookup(ptr2str, key);
116 if (value == NULL) {
117 if (name == NULL || name[0] == '\0') {
118 name = "th";
119 }
120 value = g_strdup_printf("[%s%u] ", name, g_hash_table_size(ptr2str));
121 g_hash_table_insert(ptr2str, key, value);
122 }
123 return value;
124 }
125
126 /*
127 If GM_DEBUG_THREADS is set, return a string representation of
128 the current thread number. If it is not set, return the empty string.
129 */
threadid()130 static const gchar *threadid()
131 {
132 const gchar *str;
133 if (!g_getenv("GM_DEBUG_THREADS")) {
134 return "";
135 }
136 g_static_mutex_lock(&ptr2strmutex);
137 str = threadid_core(NULL);
138 g_static_mutex_unlock(&ptr2strmutex);
139 return str;
140 }
141
142 // this function will only change the thread name if it is the first gm_log* function called within that thread
143 // there are pros and cons for this behavior, but consistent thread names (that don't change because somebody calls
144 // this function) are certainly a "pro".
gm_log_name_this_thread(gchar const * const name)145 void gm_log_name_this_thread(gchar const *const name)
146 {
147 if (!g_getenv("GM_DEBUG_THREADS")) {
148 return;
149 }
150 g_static_mutex_lock(&ptr2strmutex);
151 (void) threadid_core(name);
152 g_static_mutex_unlock(&ptr2strmutex);
153 }
154
155 // Note that the format should not have a trailing \n - the glib logging system adds it
gm_logv(gboolean force_info_to_message,GLogLevelFlags log_level,const gchar * format,va_list args)156 void gm_logv(gboolean force_info_to_message, GLogLevelFlags log_level, const gchar * format, va_list args)
157 {
158 gchar *f = NULL;
159
160 if (!fixup_loglevel(force_info_to_message, &log_level)) {
161 return;
162 }
163
164 f = g_strdup_printf("%s%s", threadid(), format);
165 g_logv(G_LOG_DOMAIN, log_level, f, args);
166 g_free(f);
167
168 return;
169 }
170
gm_log(gboolean force_info_to_message,GLogLevelFlags log_level,const gchar * format,...)171 void gm_log(gboolean force_info_to_message, GLogLevelFlags log_level, const gchar * format, ...)
172 {
173 va_list args;
174 va_start(args, format);
175 gm_logv(force_info_to_message, log_level, format, args);
176 va_end(args);
177 }
178
gm_logs(gboolean force_info_to_message,GLogLevelFlags log_level,const gchar * msg)179 void gm_logs(gboolean force_info_to_message, GLogLevelFlags log_level, const gchar * msg)
180 {
181 size_t len;
182 gchar *msg_nonl = NULL;
183
184 if (!fixup_loglevel(force_info_to_message, &log_level)) {
185 return;
186 }
187 // this function should be called if there might be a newline
188 // at the end of the string. it will only allocate a copy
189 // if necessary.
190 len = strlen(msg);
191
192 if (msg[len - 1] != '\n') {
193 g_log(G_LOG_DOMAIN, log_level, "%s%s", threadid(), msg);
194 return;
195 }
196
197 msg_nonl = g_strdup(msg);
198 msg_nonl[len - 1] = '\0';
199 g_log(G_LOG_DOMAIN, log_level, "%s%s", threadid(), msg_nonl);
200 g_free(msg_nonl);
201 }
202
gm_logsp(gboolean force_info_to_message,GLogLevelFlags log_level,const gchar * prefix,const gchar * msg)203 void gm_logsp(gboolean force_info_to_message, GLogLevelFlags log_level, const gchar * prefix, const gchar * msg)
204 {
205 gchar **lines = NULL;
206 gchar *nl_loc;
207 gint linei;
208
209 if (!fixup_loglevel(force_info_to_message, &log_level)) {
210 return;
211 }
212
213 nl_loc = g_strrstr(msg, "\n");
214
215 if (nl_loc == NULL) {
216 g_log(G_LOG_DOMAIN, log_level, "%s%s %s", threadid(), prefix, msg);
217 return;
218 }
219 // slow path
220 // there is a \n somewhere, so we have to do an allocation either way
221 lines = g_strsplit(msg, "\n", 0);
222 linei = 0;
223 while (lines[linei] != NULL) {
224 g_strchomp(lines[linei]);
225 if (lines[linei][0] != '\0') {
226 g_log(G_LOG_DOMAIN, log_level, "%s%s %s", threadid(), prefix, lines[linei]);
227 }
228 linei++;
229 }
230
231 g_strfreev(lines);
232 lines = NULL;
233
234 }
235