api-v3: First draft
[src/app-framework-binder.git] / src / verbose.c
1 /*
2  Copyright (C) 2016, 2017, 2018 "IoT.bzh"
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #include <stdio.h>
20 #include <stdarg.h>
21
22 #include "verbose.h"
23
24 #define MASKOF(x)               (1 << (x))
25
26 #if !defined(DEFAULT_LOGLEVEL)
27 # define DEFAULT_LOGLEVEL       Log_Level_Warning
28 #endif
29
30 #if !defined(DEFAULT_LOGMASK)
31 # define DEFAULT_LOGMASK        (MASKOF((DEFAULT_LOGLEVEL) + 1) - 1)
32 #endif
33
34 #if !defined(MINIMAL_LOGLEVEL)
35 # define MINIMAL_LOGLEVEL       Log_Level_Error
36 #endif
37
38 #if !defined(MINIMAL_LOGMASK)
39 # define MINIMAL_LOGMASK        (MASKOF((MINIMAL_LOGLEVEL) + 1) - 1)
40 #endif
41
42 static const char *names[] = {
43         "emergency",
44         "alert",
45         "critical",
46         "error",
47         "warning",
48         "notice",
49         "info",
50         "debug"
51 };
52
53 static const char asort[] = { 1, 2, 7, 0, 3, 6, 5, 4 };
54
55 int logmask = DEFAULT_LOGMASK | MINIMAL_LOGMASK;
56
57 void (*verbose_observer)(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args);
58
59 #define CROP_LOGLEVEL(x) \
60         ((x) < Log_Level_Emergency ? Log_Level_Emergency \
61                                    : (x) > Log_Level_Debug ? Log_Level_Debug : (x))
62
63 #if defined(VERBOSE_WITH_SYSLOG)
64
65 #include <syslog.h>
66
67 static void _vverbose_(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args)
68 {
69         char *p;
70
71         if (file == NULL || vasprintf(&p, fmt, args) < 0)
72                 vsyslog(loglevel, fmt, args);
73         else {
74                 syslog(CROP_LOGLEVEL(loglevel), "%s [%s:%d, %s]", p, file, line, function?:"?");
75                 free(p);
76         }
77 }
78
79 void verbose_set_name(const char *name, int authority)
80 {
81         openlog(name, LOG_PERROR, authority ? LOG_AUTH : LOG_USER);
82 }
83
84 #elif defined(VERBOSE_WITH_SYSTEMD)
85
86 #define SD_JOURNAL_SUPPRESS_LOCATION
87
88 #include <systemd/sd-journal.h>
89
90 static const char *appname;
91
92 static int appauthority;
93
94 static void _vverbose_(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args)
95 {
96         char lino[20];
97
98         if (file == NULL) {
99                 sd_journal_printv(loglevel, fmt, args);
100         } else {
101                 sprintf(lino, "%d", line);
102                 sd_journal_printv_with_location(loglevel, file, lino, function, fmt, args);
103         }
104 }
105
106 void verbose_set_name(const char *name, int authority)
107 {
108         appname = name;
109         appauthority = authority;
110 }
111
112 #else
113
114 #include <unistd.h>
115 #include <errno.h>
116 #include <string.h>
117 #include <sys/uio.h>
118 #include <pthread.h>
119
120 static const char *appname;
121
122 static int appauthority;
123
124 static const char *prefixes[] = {
125         "<0> EMERGENCY",
126         "<1> ALERT",
127         "<2> CRITICAL",
128         "<3> ERROR",
129         "<4> WARNING",
130         "<5> NOTICE",
131         "<6> INFO",
132         "<7> DEBUG"
133 };
134
135 static int tty;
136
137 static const char chars[] = { '\n', '?', ':', ' ', '[', ',', ']' };
138
139 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
140
141 static void _vverbose_(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args)
142 {
143         char buffer[4000];
144         char lino[40];
145         int saverr, n, rc;
146         struct iovec iov[20];
147
148         /* save errno */
149         saverr = errno;
150
151         /* check if tty (2) or not (1) */
152         if (!tty)
153                 tty = 1 + isatty(STDERR_FILENO);
154
155         /* prefix */
156         iov[0].iov_base = (void*)prefixes[CROP_LOGLEVEL(loglevel)] + (tty - 1 ? 4 : 0);
157         iov[0].iov_len = strlen(iov[0].iov_base);
158
159         /* " " */
160         iov[1].iov_base = (void*)&chars[2];
161         iov[1].iov_len = 2;
162
163         n = 2;
164         if (fmt) {
165                 iov[n].iov_base = buffer;
166                 errno = saverr;
167                 rc = vsnprintf(buffer, sizeof buffer, fmt, args);
168                 if (rc < 0)
169                         rc = 0;
170                 else if ((size_t)rc > sizeof buffer) {
171                         /* if too long, ellipsis the end with ... */
172                         rc = (int)sizeof buffer;
173                         buffer[rc - 1] = buffer[rc - 2]  = buffer[rc - 3] = '.';
174                 }
175                 iov[n++].iov_len = (size_t)rc;
176         }
177         if (file && (!fmt || tty == 1 || loglevel <= Log_Level_Warning)) {
178                 /* "[" (!fmt) or " [" (fmt) */
179                 iov[n].iov_base = (void*)&chars[3 + !fmt];
180                 iov[n++].iov_len = 2 - !fmt;
181                 /* file */
182                 iov[n].iov_base = (void*)file;
183                 iov[n++].iov_len = strlen(file);
184                 /* ":" */
185                 iov[n].iov_base = (void*)&chars[2];
186                 iov[n++].iov_len = 1;
187                 if (line) {
188                         /* line number */
189                         iov[n].iov_base = lino;
190                         iov[n++].iov_len = snprintf(lino, sizeof lino, "%d", line);
191                 } else {
192                         /* "?" */
193                         iov[n].iov_base = (void*)&chars[1];
194                         iov[n++].iov_len = 1;
195                 }
196                 /* "," */
197                 iov[n].iov_base = (void*)&chars[5];
198                 iov[n++].iov_len = 1;
199                 if (function) {
200                         /* function name */
201                         iov[n].iov_base = (void*)function;
202                         iov[n++].iov_len = strlen(function);
203                 } else {
204                         /* "?" */
205                         iov[n].iov_base = (void*)&chars[1];
206                         iov[n++].iov_len = 1;
207                 }
208                 iov[n].iov_base = (void*)&chars[6];
209                 iov[n++].iov_len = 1;
210         }
211         if (n == 2) {
212                 /* "?" */
213                 iov[n].iov_base = (void*)&chars[1];
214                 iov[n++].iov_len = 1;
215         }
216         /* "\n" */
217         iov[n].iov_base = (void*)&chars[0];
218         iov[n++].iov_len = 1;
219
220         /* emit the message */
221         pthread_mutex_lock(&mutex);
222         writev(STDERR_FILENO, iov, n);
223         pthread_mutex_unlock(&mutex);
224
225         /* restore errno */
226         errno = saverr;
227 }
228
229 void verbose_set_name(const char *name, int authority)
230 {
231         appname = name;
232         appauthority = authority;
233 }
234
235 #endif
236
237 void vverbose(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args)
238 {
239         void (*observer)(int loglevel, const char *file, int line, const char *function, const char *fmt, va_list args) = verbose_observer;
240
241         if (!observer)
242                 _vverbose_(loglevel, file, line, function, fmt, args);
243         else {
244                 va_list ap;
245                 va_copy(ap, args);
246                 _vverbose_(loglevel, file, line, function, fmt, args);
247                 observer(loglevel, file, line, function, fmt, ap);
248                 va_end(ap);
249         }
250 }
251
252 void verbose(int loglevel, const char *file, int line, const char *function, const char *fmt, ...)
253 {
254         va_list ap;
255
256         va_start(ap, fmt);
257         vverbose(loglevel, file, line, function, fmt, ap);
258         va_end(ap);
259 }
260
261 void set_logmask(int lvl)
262 {
263         logmask = lvl | MINIMAL_LOGMASK;
264 }
265
266 void verbose_add(int level)
267 {
268         set_logmask(logmask | MASKOF(level));
269 }
270
271 void verbose_sub(int level)
272 {
273         set_logmask(logmask & ~MASKOF(level));
274 }
275
276 void verbose_clear()
277 {
278         set_logmask(0);
279 }
280
281 void verbose_dec()
282 {
283         verbosity_set(verbosity_get() - 1);
284 }
285
286 void verbose_inc()
287 {
288         verbosity_set(verbosity_get() + 1);
289 }
290
291 int verbosity_to_mask(int verbo)
292 {
293         int x = verbo + Log_Level_Error;
294         int l = CROP_LOGLEVEL(x);
295         return (1 << (l + 1)) - 1;
296 }
297
298 int verbosity_from_mask(int mask)
299 {
300         int v = 0;
301         while (mask > verbosity_to_mask(v))
302                 v++;
303         return v;
304 }
305
306 void verbosity_set(int verbo)
307 {
308         set_logmask(verbosity_to_mask(verbo));
309 }
310
311 int verbosity_get()
312 {
313         return verbosity_from_mask(logmask);
314 }
315
316 int verbose_level_of_name(const char *name)
317 {
318         int c, i, r, l = 0, u = sizeof names / sizeof * names;
319         while (l < u) {
320                 i = (l + u) >> 1;
321                 r = (int)asort[i];
322                 c = strcasecmp(names[r], name);
323                 if (!c)
324                         return r;
325                 if (c < 0)
326                         l = i + 1;
327                 else
328                         u = i;
329         }
330         return -1;
331 }
332
333 const char *verbose_name_of_level(int level)
334 {
335         return level == CROP_LOGLEVEL(level) ? names[level] : NULL;
336 }
337