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