]> mj.ucw.cz Git - libucw.git/blob - ucw/log-conf.c
Logging: Following streams by stderr is now configurable.
[libucw.git] / ucw / log-conf.c
1 /*
2  *      UCW Library -- Logging: Configuration of Log Streams
3  *
4  *      (c) 2009 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #include "ucw/lib.h"
11 #include "ucw/log.h"
12 #include "ucw/log-internal.h"
13 #include "ucw/conf.h"
14 #include "ucw/simple-lists.h"
15 #include "ucw/tbf.h"
16 #include "ucw/threads.h"
17
18 #include <stdio.h>
19 #include <string.h>
20 #include <syslog.h>
21 #include <sys/time.h>
22
23 /*** Configuration of streams ***/
24
25 struct stream_config {
26   cnode n;
27   char *name;
28   char *file_name;
29   char *syslog_facility;
30   u32 levels;
31   clist types;                          // simple_list of names
32   clist substreams;                     // simple_list of names
33   clist limits;                         // of struct limit_config's
34   int microseconds;                     // Enable logging of precise timestamps
35   int show_types;
36   int syslog_pids;
37   int errors_fatal;
38   int stderr_follows;
39   struct log_stream *ls;
40   int mark;                             // Used temporarily in log_config_commit()
41 };
42
43 struct limit_config {
44   cnode n;
45   clist types;                          // simple_list of names
46   double rate;
47   uns burst;
48 };
49
50 static char *
51 stream_init(void *ptr)
52 {
53   struct stream_config *c = ptr;
54
55   c->levels = ~0U;
56   return NULL;
57 }
58
59 static char *
60 stream_commit(void *ptr)
61 {
62   struct stream_config *c = ptr;
63
64   if (c->syslog_facility)
65     {
66       if (!log_syslog_facility_exists(c->syslog_facility))
67         return cf_printf("SyslogFacility `%s' is not recognized", c->syslog_facility);
68       if (c->file_name)
69         return "Both FileName and SyslogFacility selected";
70       if (c->microseconds)
71         return "Syslog streams do not support microsecond precision";
72     }
73   if (c->stderr_follows && !c->file_name)
74     return "StdErrFollows requires a file-based stream";
75   return NULL;
76 }
77
78 static const char * const level_names[] = {
79 #define P(x) #x,
80   LOG_LEVEL_NAMES
81 #undef P
82   NULL
83 };
84
85 static struct cf_section limit_config = {
86   CF_TYPE(struct limit_config),
87   CF_ITEMS {
88 #define P(x) PTR_TO(struct limit_config, x)
89     CF_LIST("Types", P(types), &cf_string_list_config),
90     CF_DOUBLE("Rate", P(rate)),
91     CF_UNS("Burst", P(burst)),
92 #undef P
93     CF_END
94   }
95 };
96
97 static struct cf_section stream_config = {
98   CF_TYPE(struct stream_config),
99   CF_INIT(stream_init),
100   CF_COMMIT(stream_commit),
101   CF_ITEMS {
102 #define P(x) PTR_TO(struct stream_config, x)
103     CF_STRING("Name", P(name)),
104     CF_STRING("FileName", P(file_name)),
105     CF_STRING("SyslogFacility", P(syslog_facility)),
106     CF_BITMAP_LOOKUP("Levels", P(levels), level_names),
107     CF_LIST("Types", P(types), &cf_string_list_config),
108     CF_LIST("Substream", P(substreams), &cf_string_list_config),
109     CF_LIST("Limit", P(limits), &limit_config),
110     CF_INT("Microseconds", P(microseconds)),
111     CF_INT("ShowTypes", P(show_types)),
112     CF_INT("SyslogPID", P(syslog_pids)),
113     CF_INT("ErrorsFatal", P(errors_fatal)),
114     CF_INT("StdErrFollows", P(stderr_follows)),
115 #undef P
116     CF_END
117   }
118 };
119
120 static clist log_stream_confs;
121
122 static struct stream_config *
123 stream_find(const char *name)
124 {
125   CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs)
126     if (!strcmp(c->name, name))
127       return c;
128   return NULL;
129 }
130
131 static char *
132 stream_resolve(struct stream_config *c)
133 {
134   if (c->mark == 2)
135     return NULL;
136   if (c->mark == 1)
137     return cf_printf("Log stream `%s' has substreams which refer to itself", c->name);
138
139   c->mark = 1;
140   char *err;
141   CLIST_FOR_EACH(simp_node *, s, c->substreams)
142     {
143       struct stream_config *d = stream_find(s->s);
144       if (!d)
145         return cf_printf("Log stream `%s' refers to unknown substream `%s'", c->name, s->s);
146       if (err = stream_resolve(d))
147         return err;
148     }
149   c->mark = 2;
150   return NULL;
151 }
152
153 static char *
154 log_config_commit(void *ptr UNUSED)
155 {
156   // Verify uniqueness of names
157   CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs)
158     if (stream_find(c->name) != c)
159       return cf_printf("Log stream `%s' defined twice", c->name);
160
161   // Check that all substreams resolve and that there are no cycles
162   char *err;
163   CLIST_FOR_EACH(struct stream_config *, c, log_stream_confs)
164     if (err = stream_resolve(c))
165       return err;
166
167   return NULL;
168 }
169
170 static struct cf_section log_config = {
171   CF_COMMIT(log_config_commit),
172   CF_ITEMS {
173     CF_LIST("Stream", &log_stream_confs, &stream_config),
174     CF_END
175   }
176 };
177
178 static void CONSTRUCTOR
179 log_config_init(void)
180 {
181   cf_declare_section("Logging", &log_config, 0);
182 }
183
184 /*** Type sets ***/
185
186 static uns
187 log_type_mask(clist *l)
188 {
189   if (clist_empty(l))
190     return ~0U;
191
192   uns types = 0;
193   CLIST_FOR_EACH(simp_node *, s, *l)
194     if (!strcmp(s->s, "all"))
195       return ~0U;
196     else
197       {
198         /*
199          *  We intentionally ignore unknown types as not all types are known
200          *  to all programs sharing a common configuration file. This is also
201          *  the reason why Types is a list and not a bitmap.
202          */
203         int type = log_find_type(s->s);
204         if (type >= 0)
205           types |= 1 << LS_GET_TYPE(type);
206       }
207   return types;
208 }
209
210 /*** Generating limiters ***/
211
212 /*
213  *  When limiting is enabled, we let log_stream->filter point to this function
214  *  and log_stream->user_data point to an array of pointers to token bucket
215  *  filters for individual message types.
216  */
217 static int
218 log_limiter(struct log_stream *ls, struct log_msg *m)
219 {
220   struct token_bucket_filter **limits = ls->user_data;
221   if (!limits)
222     return 0;
223   struct token_bucket_filter *tbf = limits[LS_GET_TYPE(m->flags)];
224   if (!tbf)
225     return 0;
226
227   ASSERT(!(m->flags & L_SIGHANDLER));
228   if (m->flags & L_LOGGER_ERR)
229     return 0;
230
231   timestamp_t now = ((timestamp_t) m->tv->tv_sec * 1000) + (m->tv->tv_usec / 1000);
232   ucwlib_lock();
233   int res = tbf_limit(tbf, now);
234   ucwlib_unlock();
235
236   if (res < 0)
237     {
238       if (res == -1)
239         {
240           struct log_msg mm = *m;
241           mm.flags |= L_LOGGER_ERR;
242           mm.raw_msg = "(maximum logging rate exceeded, some messages will be suppressed)";
243           log_pass_msg(0, ls, &mm);
244         }
245       return 1;
246     }
247   else
248     return 0;
249 }
250
251 static void
252 log_apply_limits(struct log_stream *ls, struct limit_config *lim)
253 {
254   uns mask = log_type_mask(&lim->types);
255   if (!mask)
256     return;
257
258   if (!ls->user_data)
259     {
260       ls->user_data = cf_malloc_zero(LS_NUM_TYPES * sizeof(struct token_bucket_filter *));
261       ls->filter = log_limiter;
262     }
263   struct token_bucket_filter **limits = ls->user_data;
264   struct token_bucket_filter *tbf = cf_malloc_zero(sizeof(*lim));
265   tbf->rate = lim->rate;
266   tbf->burst = lim->burst;
267   tbf_init(tbf);
268
269   for (uns i=0; i < LS_NUM_TYPES; i++)
270     if (mask & (1 << i))
271       limits[i] = tbf;
272 }
273
274 /*** Generating streams ***/
275
276 char *
277 log_check_configured(const char *name)
278 {
279   if (stream_find(name))
280     return NULL;
281   else
282     return cf_printf("Log stream `%s' not found", name);
283 }
284
285 static struct log_stream *
286 do_new_configured(struct stream_config *c)
287 {
288   struct log_stream *ls;
289   ASSERT(c);
290
291   if (c->ls)
292     return c->ls;
293
294   if (c->file_name)
295     ls = log_new_file(c->file_name, (c->stderr_follows ? FF_FD2_FOLLOWS : 0));
296   else if (c->syslog_facility)
297     ls = log_new_syslog(c->syslog_facility, (c->syslog_pids ? LOG_PID : 0));
298   else
299     ls = log_new_stream(sizeof(*ls));
300
301   CLIST_FOR_EACH(simp_node *, s, c->substreams)
302     log_add_substream(ls, do_new_configured(stream_find(s->s)));
303
304   ls->levels = c->levels;
305   if (c->microseconds)
306     ls->msgfmt |= LSFMT_USEC;
307   if (c->show_types)
308     ls->msgfmt |= LSFMT_TYPE;
309   if (c->errors_fatal)
310     ls->stream_flags |= LSFLAG_ERR_IS_FATAL;
311   ls->types = log_type_mask(&c->types);
312
313   CLIST_FOR_EACH(struct limit_config *, lim, c->limits)
314     log_apply_limits(ls, lim);
315
316   c->ls = ls;
317   return ls;
318 }
319
320 struct log_stream *
321 log_new_configured(const char *name)
322 {
323   struct stream_config *c = stream_find(name);
324   if (!c)
325     die("Unable to find log stream %s", name);
326   if (c->ls)
327     return log_ref_stream(c->ls);
328   return do_new_configured(c);
329 }
330
331 void
332 log_configured(const char *name)
333 {
334   struct log_stream *ls = log_new_configured(name);
335   struct log_stream *def = log_stream_by_flags(0);
336   log_rm_substream(def, NULL);
337   log_add_substream(def, ls);
338   log_close_stream(ls);
339 }
340
341 #ifdef TEST
342
343 #include <unistd.h>
344 #include "ucw/getopt.h"
345
346 int main(int argc, char **argv)
347 {
348   log_init(argv[0]);
349   int c;
350   while ((c = cf_getopt(argc, argv, CF_SHORT_OPTS, CF_NO_LONG_OPTS, NULL)) >= 0)
351     die("No options here.");
352
353   int type = log_register_type("foo");
354   struct log_stream *ls = log_new_configured("combined");
355   for (uns i=0; i<10; i++)
356     {
357       msg(L_INFO | ls->regnum | type, "Hello, universe!");
358       usleep(200000);
359     }
360   fprintf(stderr, "Alas, this was printed to stderr.\n");
361
362   log_close_all();
363   return 0;
364 }
365
366 #endif