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