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