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