3f1525a79aba362e61509d50202e05b8d383b589
[protos/libecoli.git] / lib / ecoli_log.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #define _GNU_SOURCE /* for vasprintf */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <errno.h>
10 #include <syslog.h>
11
12 #include <ecoli_malloc.h>
13 #include <ecoli_string.h>
14 #include <ecoli_log.h>
15 #include <ecoli_test.h>
16
17 EC_LOG_TYPE_REGISTER(log);
18
19 static ec_log_t ec_log_fct = ec_log_default_cb;
20 static void *ec_log_opaque;
21
22 struct ec_log_type {
23         char *name;
24         enum ec_log_level level;
25 };
26
27 static struct ec_log_type *log_types;
28 static size_t log_types_len;
29 static enum ec_log_level global_level = EC_LOG_WARNING;
30
31 int ec_log_level_set(enum ec_log_level level)
32 {
33         if (level < 0 || level > EC_LOG_DEBUG)
34                 return -1;
35         global_level = level;
36
37         return 0;
38 }
39
40 enum ec_log_level ec_log_level_get(void)
41 {
42         return global_level;
43 }
44
45 int ec_log_default_cb(int type, enum ec_log_level level, void *opaque,
46                 const char *str)
47 {
48         (void)opaque;
49
50         if (level > ec_log_level_get())
51                 return 0;
52
53         if (fprintf(stderr, "[%d] %-12s %s", level, ec_log_name(type), str) < 0)
54                 return -1;
55
56         return 0;
57 }
58
59 int ec_log_fct_register(ec_log_t usr_log, void *opaque)
60 {
61         errno = 0;
62
63         if (usr_log == NULL) {
64                 ec_log_fct = ec_log_default_cb;
65                 ec_log_opaque = NULL;
66         } else {
67                 ec_log_fct = usr_log;
68                 ec_log_opaque = opaque;
69         }
70
71         return 0;
72 }
73
74 static int
75 ec_log_lookup(const char *name)
76 {
77         size_t i;
78
79         for (i = 0; i < log_types_len; i++) {
80                 if (log_types[i].name == NULL)
81                         continue;
82                 if (strcmp(name, log_types[i].name) == 0)
83                         return i;
84         }
85
86         return -1;
87 }
88
89 int
90 ec_log_type_register(const char *name)
91 {
92         struct ec_log_type *new_types;
93         char *copy;
94         int id;
95
96         id = ec_log_lookup(name);
97         if (id >= 0)
98                 return id;
99
100         new_types = ec_realloc(log_types,
101                 sizeof(*new_types) * (log_types_len + 1));
102         if (new_types == NULL)
103                 return -1; /* errno is set */
104         log_types = new_types;
105
106         copy = ec_strdup(name);
107         if (copy == NULL)
108                 return -1; /* errno is set */
109
110         id = log_types_len++;
111         log_types[id].name = copy;
112         log_types[id].level = EC_LOG_DEBUG;
113
114         return id;
115 }
116
117 const char *
118 ec_log_name(int type)
119 {
120         if (type < 0 || (unsigned int)type >= log_types_len)
121                 return "unknown";
122         return log_types[type].name;
123 }
124
125 int ec_vlog(int type, enum ec_log_level level, const char *format, va_list ap)
126 {
127         char *s;
128         int ret;
129
130         /* don't use ec_vasprintf here, because it will call
131          * ec_malloc(), then ec_log(), ec_vasprintf()...
132          * -> stack overflow */
133         ret = vasprintf(&s, format, ap);
134         if (ret < 0)
135                 return ret;
136
137         ret = ec_log_fct(type, level, ec_log_opaque, s);
138         free(s);
139
140         return ret;
141 }
142
143 int ec_log(int type, enum ec_log_level level, const char *format, ...)
144 {
145         va_list ap;
146         int ret;
147
148         va_start(ap, format);
149         ret = ec_vlog(type, level, format, ap);
150         va_end(ap);
151
152         return ret;
153 }
154
155 /* LCOV_EXCL_START */
156 static int
157 log_cb(int type, enum ec_log_level level, void *opaque, const char *str)
158 {
159         (void)type;
160         (void)level;
161         (void)str;
162         *(int *)opaque = 1;
163
164         return 0;
165 }
166
167 static int ec_log_testcase(void)
168 {
169         ec_log_t prev_log_cb;
170         void *prev_opaque;
171         const char *logname;
172         int testres = 0;
173         int check_cb = 0;
174         int logtype;
175         int level;
176         int ret;
177
178         prev_log_cb = ec_log_fct;
179         prev_opaque = ec_log_opaque;
180
181         ret = ec_log_fct_register(log_cb, &check_cb);
182         testres |= EC_TEST_CHECK(ret == 0,
183                                 "cannot register log function\n");
184         EC_LOG(LOG_ERR, "test\n");
185         testres |= EC_TEST_CHECK(check_cb == 1,
186                                 "log callback was not invoked\n");
187         logtype = ec_log_lookup("dsdedesdes");
188         testres |= EC_TEST_CHECK(logtype == -1,
189                                 "lookup invalid name should return -1");
190         logtype = ec_log_lookup("log");
191         logname = ec_log_name(logtype);
192         testres |= EC_TEST_CHECK(logname != NULL &&
193                                 !strcmp(logname, "log"),
194                                 "cannot get log name\n");
195         logname = ec_log_name(-1);
196         testres |= EC_TEST_CHECK(logname != NULL &&
197                                 !strcmp(logname, "unknown"),
198                                 "cannot get invalid log name\n");
199         logname = ec_log_name(34324);
200         testres |= EC_TEST_CHECK(logname != NULL &&
201                                 !strcmp(logname, "unknown"),
202                                 "cannot get invalid log name\n");
203         level = ec_log_level_get();
204         ret = ec_log_level_set(2);
205         testres |= EC_TEST_CHECK(ret == 0 && ec_log_level_get() == 2,
206                                 "cannot set log level\n");
207         ret = ec_log_level_set(10);
208         testres |= EC_TEST_CHECK(ret != 0,
209                                 "should not be able to set log level\n");
210
211         ret = ec_log_fct_register(NULL, NULL);
212         ec_log_level_set(LOG_DEBUG);
213         EC_LOG(LOG_DEBUG, "test log\n");
214         ec_log_level_set(LOG_INFO);
215         EC_LOG(LOG_DEBUG, "test log (not displayed)\n");
216         ec_log_level_set(level);
217
218         ec_log_fct = prev_log_cb;
219         ec_log_opaque = prev_opaque;
220
221         return testres;
222 }
223 /* LCOV_EXCL_STOP */
224
225 static struct ec_test ec_log_test = {
226         .name = "log",
227         .test = ec_log_testcase,
228 };
229
230 EC_TEST_REGISTER(ec_log_test);