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