]>
Commit | Line | Data |
---|---|---|
064af421 | 1 | /* |
67a4917b | 2 | * Copyright (c) 2008, 2009, 2010 Nicira Networks. |
064af421 | 3 | * |
a14bc59f BP |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
064af421 | 7 | * |
a14bc59f BP |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
064af421 BP |
15 | */ |
16 | ||
17 | #include <config.h> | |
18 | #include "vlog.h" | |
19 | #include <assert.h> | |
20 | #include <ctype.h> | |
21 | #include <errno.h> | |
22 | #include <stdarg.h> | |
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | #include <sys/types.h> | |
26 | #include <syslog.h> | |
27 | #include <time.h> | |
28 | #include <unistd.h> | |
29 | #include "dirs.h" | |
30 | #include "dynamic-string.h" | |
31 | #include "sat-math.h" | |
32 | #include "timeval.h" | |
33 | #include "unixctl.h" | |
34 | #include "util.h" | |
35 | ||
36 | #define THIS_MODULE VLM_vlog | |
37 | ||
38 | /* Name for each logging level. */ | |
39 | static const char *level_names[VLL_N_LEVELS] = { | |
40 | #define VLOG_LEVEL(NAME, SYSLOG_LEVEL) #NAME, | |
41 | VLOG_LEVELS | |
42 | #undef VLOG_LEVEL | |
43 | }; | |
44 | ||
45 | /* Syslog value for each logging level. */ | |
46 | static int syslog_levels[VLL_N_LEVELS] = { | |
47 | #define VLOG_LEVEL(NAME, SYSLOG_LEVEL) SYSLOG_LEVEL, | |
48 | VLOG_LEVELS | |
49 | #undef VLOG_LEVEL | |
50 | }; | |
51 | ||
52 | /* Name for each logging module */ | |
53 | static const char *module_names[VLM_N_MODULES] = { | |
54 | #define VLOG_MODULE(NAME) #NAME, | |
55 | #include "vlog-modules.def" | |
56 | #undef VLOG_MODULE | |
57 | }; | |
58 | ||
59 | /* Information about each facility. */ | |
60 | struct facility { | |
61 | const char *name; /* Name. */ | |
62 | char *pattern; /* Current pattern. */ | |
63 | bool default_pattern; /* Whether current pattern is the default. */ | |
64 | }; | |
65 | static struct facility facilities[VLF_N_FACILITIES] = { | |
66 | #define VLOG_FACILITY(NAME, PATTERN) {#NAME, PATTERN, true}, | |
67 | VLOG_FACILITIES | |
68 | #undef VLOG_FACILITY | |
69 | }; | |
70 | ||
71 | /* Current log levels. */ | |
72 | static int levels[VLM_N_MODULES][VLF_N_FACILITIES]; | |
73 | ||
74 | /* For fast checking whether we're logging anything for a given module and | |
75 | * level.*/ | |
76 | enum vlog_level min_vlog_levels[VLM_N_MODULES]; | |
77 | ||
78 | /* Time at which vlog was initialized, in milliseconds. */ | |
79 | static long long int boot_time; | |
80 | ||
81 | /* VLF_FILE configuration. */ | |
82 | static char *log_file_name; | |
83 | static FILE *log_file; | |
84 | ||
85 | static void format_log_message(enum vlog_module, enum vlog_level, | |
86 | enum vlog_facility, unsigned int msg_num, | |
87 | const char *message, va_list, struct ds *) | |
88 | PRINTF_FORMAT(5, 0); | |
89 | ||
90 | /* Searches the 'n_names' in 'names'. Returns the index of a match for | |
91 | * 'target', or 'n_names' if no name matches. */ | |
92 | static size_t | |
93 | search_name_array(const char *target, const char **names, size_t n_names) | |
94 | { | |
95 | size_t i; | |
96 | ||
97 | for (i = 0; i < n_names; i++) { | |
98 | assert(names[i]); | |
99 | if (!strcasecmp(names[i], target)) { | |
100 | break; | |
101 | } | |
102 | } | |
103 | return i; | |
104 | } | |
105 | ||
106 | /* Returns the name for logging level 'level'. */ | |
107 | const char * | |
108 | vlog_get_level_name(enum vlog_level level) | |
109 | { | |
110 | assert(level < VLL_N_LEVELS); | |
111 | return level_names[level]; | |
112 | } | |
113 | ||
114 | /* Returns the logging level with the given 'name', or VLL_N_LEVELS if 'name' | |
115 | * is not the name of a logging level. */ | |
116 | enum vlog_level | |
117 | vlog_get_level_val(const char *name) | |
118 | { | |
119 | return search_name_array(name, level_names, ARRAY_SIZE(level_names)); | |
120 | } | |
121 | ||
122 | /* Returns the name for logging facility 'facility'. */ | |
123 | const char * | |
124 | vlog_get_facility_name(enum vlog_facility facility) | |
125 | { | |
126 | assert(facility < VLF_N_FACILITIES); | |
127 | return facilities[facility].name; | |
128 | } | |
129 | ||
130 | /* Returns the logging facility named 'name', or VLF_N_FACILITIES if 'name' is | |
131 | * not the name of a logging facility. */ | |
132 | enum vlog_facility | |
133 | vlog_get_facility_val(const char *name) | |
134 | { | |
135 | size_t i; | |
136 | ||
137 | for (i = 0; i < VLF_N_FACILITIES; i++) { | |
138 | if (!strcasecmp(facilities[i].name, name)) { | |
139 | break; | |
140 | } | |
141 | } | |
142 | return i; | |
143 | } | |
144 | ||
145 | /* Returns the name for logging module 'module'. */ | |
146 | const char *vlog_get_module_name(enum vlog_module module) | |
147 | { | |
148 | assert(module < VLM_N_MODULES); | |
149 | return module_names[module]; | |
150 | } | |
151 | ||
152 | /* Returns the logging module named 'name', or VLM_N_MODULES if 'name' is not | |
153 | * the name of a logging module. */ | |
154 | enum vlog_module | |
155 | vlog_get_module_val(const char *name) | |
156 | { | |
157 | return search_name_array(name, module_names, ARRAY_SIZE(module_names)); | |
158 | } | |
159 | ||
160 | /* Returns the current logging level for the given 'module' and 'facility'. */ | |
161 | enum vlog_level | |
162 | vlog_get_level(enum vlog_module module, enum vlog_facility facility) | |
163 | { | |
164 | assert(module < VLM_N_MODULES); | |
165 | assert(facility < VLF_N_FACILITIES); | |
166 | return levels[module][facility]; | |
167 | } | |
168 | ||
169 | static void | |
170 | update_min_level(enum vlog_module module) | |
171 | { | |
172 | enum vlog_level min_level = VLL_EMER; | |
173 | enum vlog_facility facility; | |
174 | ||
175 | for (facility = 0; facility < VLF_N_FACILITIES; facility++) { | |
176 | if (log_file || facility != VLF_FILE) { | |
177 | min_level = MAX(min_level, levels[module][facility]); | |
178 | } | |
179 | } | |
180 | min_vlog_levels[module] = min_level; | |
181 | } | |
182 | ||
183 | static void | |
184 | set_facility_level(enum vlog_facility facility, enum vlog_module module, | |
185 | enum vlog_level level) | |
186 | { | |
187 | assert(facility >= 0 && facility < VLF_N_FACILITIES); | |
188 | assert(level < VLL_N_LEVELS); | |
189 | ||
190 | if (module == VLM_ANY_MODULE) { | |
191 | for (module = 0; module < VLM_N_MODULES; module++) { | |
192 | levels[module][facility] = level; | |
193 | update_min_level(module); | |
194 | } | |
195 | } else { | |
196 | levels[module][facility] = level; | |
197 | update_min_level(module); | |
198 | } | |
199 | } | |
200 | ||
201 | /* Sets the logging level for the given 'module' and 'facility' to 'level'. */ | |
202 | void | |
203 | vlog_set_levels(enum vlog_module module, enum vlog_facility facility, | |
204 | enum vlog_level level) | |
205 | { | |
206 | assert(facility < VLF_N_FACILITIES || facility == VLF_ANY_FACILITY); | |
207 | if (facility == VLF_ANY_FACILITY) { | |
208 | for (facility = 0; facility < VLF_N_FACILITIES; facility++) { | |
209 | set_facility_level(facility, module, level); | |
210 | } | |
211 | } else { | |
212 | set_facility_level(facility, module, level); | |
213 | } | |
214 | } | |
215 | ||
216 | static void | |
217 | do_set_pattern(enum vlog_facility facility, const char *pattern) | |
218 | { | |
219 | struct facility *f = &facilities[facility]; | |
220 | if (!f->default_pattern) { | |
221 | free(f->pattern); | |
222 | } else { | |
223 | f->default_pattern = false; | |
224 | } | |
225 | f->pattern = xstrdup(pattern); | |
226 | } | |
227 | ||
228 | /* Sets the pattern for the given 'facility' to 'pattern'. */ | |
229 | void | |
230 | vlog_set_pattern(enum vlog_facility facility, const char *pattern) | |
231 | { | |
232 | assert(facility < VLF_N_FACILITIES || facility == VLF_ANY_FACILITY); | |
233 | if (facility == VLF_ANY_FACILITY) { | |
234 | for (facility = 0; facility < VLF_N_FACILITIES; facility++) { | |
235 | do_set_pattern(facility, pattern); | |
236 | } | |
237 | } else { | |
238 | do_set_pattern(facility, pattern); | |
239 | } | |
240 | } | |
241 | ||
242 | /* Returns the name of the log file used by VLF_FILE, or a null pointer if no | |
243 | * log file has been set. (A non-null return value does not assert that the | |
244 | * named log file is in use: if vlog_set_log_file() or vlog_reopen_log_file() | |
245 | * fails, it still sets the log file name.) */ | |
246 | const char * | |
247 | vlog_get_log_file(void) | |
248 | { | |
249 | return log_file_name; | |
250 | } | |
251 | ||
252 | /* Sets the name of the log file used by VLF_FILE to 'file_name', or to the | |
253 | * default file name if 'file_name' is null. Returns 0 if successful, | |
254 | * otherwise a positive errno value. */ | |
255 | int | |
256 | vlog_set_log_file(const char *file_name) | |
257 | { | |
258 | char *old_log_file_name; | |
259 | enum vlog_module module; | |
260 | int error; | |
261 | ||
262 | /* Close old log file. */ | |
263 | if (log_file) { | |
264 | VLOG_INFO("closing log file"); | |
265 | fclose(log_file); | |
266 | log_file = NULL; | |
267 | } | |
268 | ||
269 | /* Update log file name and free old name. The ordering is important | |
270 | * because 'file_name' might be 'log_file_name' or some suffix of it. */ | |
271 | old_log_file_name = log_file_name; | |
272 | log_file_name = (file_name | |
273 | ? xstrdup(file_name) | |
274 | : xasprintf("%s/%s.log", ovs_logdir, program_name)); | |
275 | free(old_log_file_name); | |
276 | file_name = NULL; /* Might have been freed. */ | |
277 | ||
278 | /* Open new log file and update min_levels[] to reflect whether we actually | |
279 | * have a log_file. */ | |
280 | log_file = fopen(log_file_name, "a"); | |
281 | for (module = 0; module < VLM_N_MODULES; module++) { | |
282 | update_min_level(module); | |
283 | } | |
284 | ||
285 | /* Log success or failure. */ | |
286 | if (!log_file) { | |
287 | VLOG_WARN("failed to open %s for logging: %s", | |
288 | log_file_name, strerror(errno)); | |
289 | error = errno; | |
290 | } else { | |
291 | VLOG_INFO("opened log file %s", log_file_name); | |
292 | error = 0; | |
293 | } | |
294 | ||
295 | return error; | |
296 | } | |
297 | ||
298 | /* Closes and then attempts to re-open the current log file. (This is useful | |
299 | * just after log rotation, to ensure that the new log file starts being used.) | |
300 | * Returns 0 if successful, otherwise a positive errno value. */ | |
301 | int | |
302 | vlog_reopen_log_file(void) | |
303 | { | |
304 | return log_file_name ? vlog_set_log_file(log_file_name) : 0; | |
305 | } | |
306 | ||
307 | /* Set debugging levels: | |
308 | * | |
309 | * mod[:facility[:level]] mod2[:facility[:level]] ... | |
310 | * | |
311 | * Return null if successful, otherwise an error message that the caller must | |
312 | * free(). | |
313 | */ | |
314 | char * | |
315 | vlog_set_levels_from_string(const char *s_) | |
316 | { | |
ba8de5cb | 317 | char *save_ptr = NULL; |
064af421 BP |
318 | char *s = xstrdup(s_); |
319 | char *module, *facility; | |
320 | ||
321 | for (module = strtok_r(s, ": \t", &save_ptr); module != NULL; | |
322 | module = strtok_r(NULL, ": \t", &save_ptr)) { | |
323 | enum vlog_module e_module; | |
324 | enum vlog_facility e_facility; | |
325 | ||
326 | facility = strtok_r(NULL, ":", &save_ptr); | |
327 | ||
328 | if (!facility || !strcmp(facility, "ANY")) { | |
329 | e_facility = VLF_ANY_FACILITY; | |
330 | } else { | |
331 | e_facility = vlog_get_facility_val(facility); | |
332 | if (e_facility >= VLF_N_FACILITIES) { | |
333 | char *msg = xasprintf("unknown facility \"%s\"", facility); | |
334 | free(s); | |
335 | return msg; | |
336 | } | |
337 | } | |
338 | ||
339 | if (!strcmp(module, "PATTERN")) { | |
340 | vlog_set_pattern(e_facility, save_ptr); | |
341 | break; | |
342 | } else { | |
343 | char *level; | |
344 | enum vlog_level e_level; | |
345 | ||
346 | if (!strcmp(module, "ANY")) { | |
347 | e_module = VLM_ANY_MODULE; | |
348 | } else { | |
349 | e_module = vlog_get_module_val(module); | |
350 | if (e_module >= VLM_N_MODULES) { | |
351 | char *msg = xasprintf("unknown module \"%s\"", module); | |
352 | free(s); | |
353 | return msg; | |
354 | } | |
355 | } | |
356 | ||
357 | level = strtok_r(NULL, ":", &save_ptr); | |
358 | e_level = level ? vlog_get_level_val(level) : VLL_DBG; | |
359 | if (e_level >= VLL_N_LEVELS) { | |
360 | char *msg = xasprintf("unknown level \"%s\"", level); | |
361 | free(s); | |
362 | return msg; | |
363 | } | |
364 | ||
365 | vlog_set_levels(e_module, e_facility, e_level); | |
366 | } | |
367 | } | |
368 | free(s); | |
369 | return NULL; | |
370 | } | |
371 | ||
372 | /* If 'arg' is null, configure maximum verbosity. Otherwise, sets | |
373 | * configuration according to 'arg' (see vlog_set_levels_from_string()). */ | |
374 | void | |
375 | vlog_set_verbosity(const char *arg) | |
376 | { | |
377 | if (arg) { | |
378 | char *msg = vlog_set_levels_from_string(arg); | |
379 | if (msg) { | |
380 | ovs_fatal(0, "processing \"%s\": %s", arg, msg); | |
381 | } | |
382 | } else { | |
383 | vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_DBG); | |
384 | } | |
385 | } | |
386 | ||
387 | static void | |
c69ee87c BP |
388 | vlog_unixctl_set(struct unixctl_conn *conn, |
389 | const char *args, void *aux OVS_UNUSED) | |
064af421 BP |
390 | { |
391 | char *msg = vlog_set_levels_from_string(args); | |
392 | unixctl_command_reply(conn, msg ? 501 : 202, msg); | |
393 | free(msg); | |
394 | } | |
395 | ||
396 | static void | |
8ca79daa | 397 | vlog_unixctl_list(struct unixctl_conn *conn, |
c69ee87c | 398 | const char *args OVS_UNUSED, void *aux OVS_UNUSED) |
064af421 BP |
399 | { |
400 | char *msg = vlog_get_levels(); | |
401 | unixctl_command_reply(conn, 200, msg); | |
402 | free(msg); | |
403 | } | |
404 | ||
405 | static void | |
8ca79daa | 406 | vlog_unixctl_reopen(struct unixctl_conn *conn, |
c69ee87c | 407 | const char *args OVS_UNUSED, void *aux OVS_UNUSED) |
064af421 BP |
408 | { |
409 | if (log_file_name) { | |
410 | int error = vlog_reopen_log_file(); | |
411 | if (error) { | |
412 | unixctl_command_reply(conn, 503, strerror(errno)); | |
413 | } else { | |
414 | unixctl_command_reply(conn, 202, NULL); | |
415 | } | |
416 | } else { | |
417 | unixctl_command_reply(conn, 403, "Logging to file not configured"); | |
418 | } | |
419 | } | |
420 | ||
421 | /* Initializes the logging subsystem. */ | |
422 | void | |
423 | vlog_init(void) | |
424 | { | |
425 | time_t now; | |
426 | ||
427 | openlog(program_name, LOG_NDELAY, LOG_DAEMON); | |
428 | vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_INFO); | |
429 | ||
430 | boot_time = time_msec(); | |
c73814a3 | 431 | now = time_wall(); |
064af421 BP |
432 | if (now < 0) { |
433 | struct tm tm; | |
434 | char s[128]; | |
435 | ||
436 | localtime_r(&now, &tm); | |
437 | strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm); | |
438 | VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now); | |
439 | } | |
440 | ||
8ca79daa BP |
441 | unixctl_command_register("vlog/set", vlog_unixctl_set, NULL); |
442 | unixctl_command_register("vlog/list", vlog_unixctl_list, NULL); | |
443 | unixctl_command_register("vlog/reopen", vlog_unixctl_reopen, NULL); | |
064af421 BP |
444 | } |
445 | ||
446 | /* Closes the logging subsystem. */ | |
447 | void | |
448 | vlog_exit(void) | |
449 | { | |
450 | closelog(); | |
451 | } | |
452 | ||
453 | /* Print the current logging level for each module. */ | |
454 | char * | |
455 | vlog_get_levels(void) | |
456 | { | |
457 | struct ds s = DS_EMPTY_INITIALIZER; | |
458 | enum vlog_module module; | |
459 | ||
460 | ds_put_format(&s, " console syslog file\n"); | |
461 | ds_put_format(&s, " ------- ------ ------\n"); | |
462 | ||
463 | for (module = 0; module < VLM_N_MODULES; module++) { | |
464 | ds_put_format(&s, "%-16s %4s %4s %4s\n", | |
465 | vlog_get_module_name(module), | |
466 | vlog_get_level_name(vlog_get_level(module, VLF_CONSOLE)), | |
467 | vlog_get_level_name(vlog_get_level(module, VLF_SYSLOG)), | |
468 | vlog_get_level_name(vlog_get_level(module, VLF_FILE))); | |
469 | } | |
470 | ||
471 | return ds_cstr(&s); | |
472 | } | |
473 | ||
474 | /* Returns true if a log message emitted for the given 'module' and 'level' | |
475 | * would cause some log output, false if that module and level are completely | |
476 | * disabled. */ | |
477 | bool | |
478 | vlog_is_enabled(enum vlog_module module, enum vlog_level level) | |
479 | { | |
480 | return min_vlog_levels[module] >= level; | |
481 | } | |
482 | ||
483 | static const char * | |
484 | fetch_braces(const char *p, const char *def, char *out, size_t out_size) | |
485 | { | |
486 | if (*p == '{') { | |
487 | size_t n = strcspn(p + 1, "}"); | |
488 | size_t n_copy = MIN(n, out_size - 1); | |
489 | memcpy(out, p + 1, n_copy); | |
490 | out[n_copy] = '\0'; | |
491 | p += n + 2; | |
492 | } else { | |
493 | ovs_strlcpy(out, def, out_size); | |
494 | } | |
495 | return p; | |
496 | } | |
497 | ||
498 | static void | |
499 | format_log_message(enum vlog_module module, enum vlog_level level, | |
500 | enum vlog_facility facility, unsigned int msg_num, | |
501 | const char *message, va_list args_, struct ds *s) | |
502 | { | |
503 | char tmp[128]; | |
504 | va_list args; | |
505 | const char *p; | |
506 | ||
507 | ds_clear(s); | |
508 | for (p = facilities[facility].pattern; *p != '\0'; ) { | |
509 | enum { LEFT, RIGHT } justify = RIGHT; | |
510 | int pad = '0'; | |
511 | size_t length, field, used; | |
512 | ||
513 | if (*p != '%') { | |
514 | ds_put_char(s, *p++); | |
515 | continue; | |
516 | } | |
517 | ||
518 | p++; | |
519 | if (*p == '-') { | |
520 | justify = LEFT; | |
521 | p++; | |
522 | } | |
523 | if (*p == '0') { | |
524 | pad = '0'; | |
525 | p++; | |
526 | } | |
527 | field = 0; | |
be2c418b | 528 | while (isdigit((unsigned char)*p)) { |
064af421 BP |
529 | field = (field * 10) + (*p - '0'); |
530 | p++; | |
531 | } | |
532 | ||
533 | length = s->length; | |
534 | switch (*p++) { | |
535 | case 'A': | |
536 | ds_put_cstr(s, program_name); | |
537 | break; | |
538 | case 'c': | |
539 | p = fetch_braces(p, "", tmp, sizeof tmp); | |
540 | ds_put_cstr(s, vlog_get_module_name(module)); | |
541 | break; | |
542 | case 'd': | |
543 | p = fetch_braces(p, "%Y-%m-%d %H:%M:%S", tmp, sizeof tmp); | |
544 | ds_put_strftime(s, tmp, NULL); | |
545 | break; | |
546 | case 'm': | |
547 | /* Format user-supplied log message and trim trailing new-lines. */ | |
548 | length = s->length; | |
549 | va_copy(args, args_); | |
550 | ds_put_format_valist(s, message, args); | |
551 | va_end(args); | |
552 | while (s->length > length && s->string[s->length - 1] == '\n') { | |
553 | s->length--; | |
554 | } | |
555 | break; | |
556 | case 'N': | |
557 | ds_put_format(s, "%u", msg_num); | |
558 | break; | |
559 | case 'n': | |
560 | ds_put_char(s, '\n'); | |
561 | break; | |
562 | case 'p': | |
563 | ds_put_cstr(s, vlog_get_level_name(level)); | |
564 | break; | |
565 | case 'P': | |
566 | ds_put_format(s, "%ld", (long int) getpid()); | |
567 | break; | |
568 | case 'r': | |
569 | ds_put_format(s, "%lld", time_msec() - boot_time); | |
570 | break; | |
571 | default: | |
572 | ds_put_char(s, p[-1]); | |
573 | break; | |
574 | } | |
575 | used = s->length - length; | |
576 | if (used < field) { | |
577 | size_t n_pad = field - used; | |
578 | if (justify == RIGHT) { | |
579 | ds_put_uninit(s, n_pad); | |
580 | memmove(&s->string[length + n_pad], &s->string[length], used); | |
581 | memset(&s->string[length], pad, n_pad); | |
582 | } else { | |
583 | ds_put_char_multiple(s, pad, n_pad); | |
584 | } | |
585 | } | |
586 | } | |
587 | } | |
588 | ||
589 | /* Writes 'message' to the log at the given 'level' and as coming from the | |
590 | * given 'module'. | |
591 | * | |
592 | * Guaranteed to preserve errno. */ | |
593 | void | |
594 | vlog_valist(enum vlog_module module, enum vlog_level level, | |
595 | const char *message, va_list args) | |
596 | { | |
597 | bool log_to_console = levels[module][VLF_CONSOLE] >= level; | |
598 | bool log_to_syslog = levels[module][VLF_SYSLOG] >= level; | |
599 | bool log_to_file = levels[module][VLF_FILE] >= level && log_file; | |
600 | if (log_to_console || log_to_syslog || log_to_file) { | |
601 | int save_errno = errno; | |
602 | static unsigned int msg_num; | |
603 | struct ds s; | |
604 | ||
605 | ds_init(&s); | |
606 | ds_reserve(&s, 1024); | |
607 | msg_num++; | |
608 | ||
609 | if (log_to_console) { | |
610 | format_log_message(module, level, VLF_CONSOLE, msg_num, | |
611 | message, args, &s); | |
612 | ds_put_char(&s, '\n'); | |
613 | fputs(ds_cstr(&s), stderr); | |
614 | } | |
615 | ||
616 | if (log_to_syslog) { | |
617 | int syslog_level = syslog_levels[level]; | |
618 | char *save_ptr = NULL; | |
619 | char *line; | |
620 | ||
621 | format_log_message(module, level, VLF_SYSLOG, msg_num, | |
622 | message, args, &s); | |
623 | for (line = strtok_r(s.string, "\n", &save_ptr); line; | |
624 | line = strtok_r(NULL, "\n", &save_ptr)) { | |
625 | syslog(syslog_level, "%s", line); | |
626 | } | |
627 | } | |
628 | ||
629 | if (log_to_file) { | |
630 | format_log_message(module, level, VLF_FILE, msg_num, | |
631 | message, args, &s); | |
632 | ds_put_char(&s, '\n'); | |
633 | fputs(ds_cstr(&s), log_file); | |
634 | fflush(log_file); | |
635 | } | |
636 | ||
637 | ds_destroy(&s); | |
638 | errno = save_errno; | |
639 | } | |
640 | } | |
641 | ||
642 | void | |
643 | vlog(enum vlog_module module, enum vlog_level level, const char *message, ...) | |
644 | { | |
645 | va_list args; | |
646 | ||
647 | va_start(args, message); | |
648 | vlog_valist(module, level, message, args); | |
649 | va_end(args); | |
650 | } | |
651 | ||
652 | bool | |
653 | vlog_should_drop(enum vlog_module module, enum vlog_level level, | |
654 | struct vlog_rate_limit *rl) | |
655 | { | |
656 | if (!vlog_is_enabled(module, level)) { | |
657 | return true; | |
658 | } | |
659 | ||
660 | if (rl->tokens < VLOG_MSG_TOKENS) { | |
661 | time_t now = time_now(); | |
662 | if (rl->last_fill > now) { | |
663 | /* Last filled in the future? Time must have gone backward, or | |
664 | * 'rl' has not been used before. */ | |
665 | rl->tokens = rl->burst; | |
666 | } else if (rl->last_fill < now) { | |
667 | unsigned int add = sat_mul(rl->rate, now - rl->last_fill); | |
668 | unsigned int tokens = sat_add(rl->tokens, add); | |
669 | rl->tokens = MIN(tokens, rl->burst); | |
670 | rl->last_fill = now; | |
671 | } | |
672 | if (rl->tokens < VLOG_MSG_TOKENS) { | |
673 | if (!rl->n_dropped) { | |
674 | rl->first_dropped = now; | |
675 | } | |
676 | rl->n_dropped++; | |
677 | return true; | |
678 | } | |
679 | } | |
680 | rl->tokens -= VLOG_MSG_TOKENS; | |
681 | ||
682 | if (rl->n_dropped) { | |
683 | vlog(module, level, | |
684 | "Dropped %u log messages in last %u seconds " | |
685 | "due to excessive rate", | |
686 | rl->n_dropped, (unsigned int) (time_now() - rl->first_dropped)); | |
687 | rl->n_dropped = 0; | |
688 | } | |
689 | return false; | |
690 | } | |
691 | ||
692 | void | |
693 | vlog_rate_limit(enum vlog_module module, enum vlog_level level, | |
694 | struct vlog_rate_limit *rl, const char *message, ...) | |
695 | { | |
696 | if (!vlog_should_drop(module, level, rl)) { | |
697 | va_list args; | |
698 | ||
699 | va_start(args, message); | |
700 | vlog_valist(module, level, message, args); | |
701 | va_end(args); | |
702 | } | |
703 | } | |
704 | ||
705 | void | |
706 | vlog_usage(void) | |
707 | { | |
708 | printf("\nLogging options:\n" | |
709 | " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" | |
710 | " -v, --verbose set maximum verbosity level\n" | |
711 | " --log-file[=FILE] enable logging to specified FILE\n" | |
712 | " (default: %s/%s.log)\n", | |
713 | ovs_logdir, program_name); | |
714 | } |