]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/log.c
log: avoid function loop
[thirdparty/systemd.git] / src / shared / log.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <stddef.h>
30
31 #include "log.h"
32 #include "util.h"
33 #include "missing.h"
34 #include "macro.h"
35 #include "socket-util.h"
36
37 #define SNDBUF_SIZE (8*1024*1024)
38
39 static LogTarget log_target = LOG_TARGET_CONSOLE;
40 static int log_max_level = LOG_INFO;
41 static int log_facility = LOG_DAEMON;
42
43 static int console_fd = STDERR_FILENO;
44 static int syslog_fd = -1;
45 static int kmsg_fd = -1;
46 static int journal_fd = -1;
47
48 static bool syslog_is_stream = false;
49
50 static bool show_color = false;
51 static bool show_location = false;
52
53 /* Akin to glibc's __abort_msg; which is private and we hence cannot
54 * use here. */
55 static char *log_abort_msg = NULL;
56
57 void log_close_console(void) {
58
59 if (console_fd < 0)
60 return;
61
62 if (getpid() == 1) {
63 if (console_fd >= 3)
64 close_nointr_nofail(console_fd);
65
66 console_fd = -1;
67 }
68 }
69
70 static int log_open_console(void) {
71
72 if (console_fd >= 0)
73 return 0;
74
75 if (getpid() == 1) {
76 console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
77 if (console_fd < 0)
78 return console_fd;
79 } else
80 console_fd = STDERR_FILENO;
81
82 return 0;
83 }
84
85 void log_close_kmsg(void) {
86
87 if (kmsg_fd < 0)
88 return;
89
90 close_nointr_nofail(kmsg_fd);
91 kmsg_fd = -1;
92 }
93
94 static int log_open_kmsg(void) {
95
96 if (kmsg_fd >= 0)
97 return 0;
98
99 kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC);
100 if (kmsg_fd < 0)
101 return -errno;
102
103 return 0;
104 }
105
106 void log_close_syslog(void) {
107
108 if (syslog_fd < 0)
109 return;
110
111 close_nointr_nofail(syslog_fd);
112 syslog_fd = -1;
113 }
114
115 static int create_log_socket(int type) {
116 int fd;
117
118 /* All output to the syslog/journal fds we do asynchronously,
119 * and if the buffers are full we just drop the messages */
120
121 fd = socket(AF_UNIX, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
122 if (fd < 0)
123 return -errno;
124
125 fd_inc_sndbuf(fd, SNDBUF_SIZE);
126
127 return fd;
128 }
129
130 static int log_open_syslog(void) {
131 union sockaddr_union sa;
132 int r;
133
134 if (syslog_fd >= 0)
135 return 0;
136
137 zero(sa);
138 sa.un.sun_family = AF_UNIX;
139 strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
140
141 syslog_fd = create_log_socket(SOCK_DGRAM);
142 if (syslog_fd < 0) {
143 r = syslog_fd;
144 goto fail;
145 }
146
147 if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
148 close_nointr_nofail(syslog_fd);
149
150 /* Some legacy syslog systems still use stream
151 * sockets. They really shouldn't. But what can we
152 * do... */
153 syslog_fd = create_log_socket(SOCK_STREAM);
154 if (syslog_fd < 0) {
155 r = syslog_fd;
156 goto fail;
157 }
158
159 if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
160 r = -errno;
161 goto fail;
162 }
163
164 syslog_is_stream = true;
165 } else
166 syslog_is_stream = false;
167
168 return 0;
169
170 fail:
171 log_close_syslog();
172 return r;
173 }
174
175 void log_close_journal(void) {
176
177 if (journal_fd < 0)
178 return;
179
180 close_nointr_nofail(journal_fd);
181 journal_fd = -1;
182 }
183
184 static int log_open_journal(void) {
185 union sockaddr_union sa;
186 int r;
187
188 if (journal_fd >= 0)
189 return 0;
190
191 journal_fd = create_log_socket(SOCK_DGRAM);
192 if (journal_fd < 0) {
193 r = journal_fd;
194 goto fail;
195 }
196
197 zero(sa);
198 sa.un.sun_family = AF_UNIX;
199 strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path));
200
201 if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) {
202 r = -errno;
203 goto fail;
204 }
205
206 return 0;
207
208 fail:
209 log_close_journal();
210 return r;
211 }
212
213 int log_open(void) {
214 int r;
215
216 /* If we don't use the console we close it here, to not get
217 * killed by SAK. If we don't use syslog we close it here so
218 * that we are not confused by somebody deleting the socket in
219 * the fs. If we don't use /dev/kmsg we still keep it open,
220 * because there is no reason to close it. */
221
222 if (log_target == LOG_TARGET_NULL) {
223 log_close_journal();
224 log_close_syslog();
225 log_close_console();
226 return 0;
227 }
228
229 if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) ||
230 getpid() == 1 ||
231 isatty(STDERR_FILENO) <= 0) {
232
233 if (log_target == LOG_TARGET_AUTO ||
234 log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
235 log_target == LOG_TARGET_JOURNAL) {
236 r = log_open_journal();
237 if (r >= 0) {
238 log_close_syslog();
239 log_close_console();
240 return r;
241 }
242 }
243
244 if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
245 log_target == LOG_TARGET_SYSLOG) {
246 r = log_open_syslog();
247 if (r >= 0) {
248 log_close_journal();
249 log_close_console();
250 return r;
251 }
252 }
253
254 if (log_target == LOG_TARGET_AUTO ||
255 log_target == LOG_TARGET_SAFE ||
256 log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
257 log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
258 log_target == LOG_TARGET_KMSG) {
259 r = log_open_kmsg();
260 if (r >= 0) {
261 log_close_journal();
262 log_close_syslog();
263 log_close_console();
264 return r;
265 }
266 }
267 }
268
269 log_close_journal();
270 log_close_syslog();
271
272 /* Get the real /dev/console if we are PID=1, hence reopen */
273 log_close_console();
274 return log_open_console();
275 }
276
277 void log_set_target(LogTarget target) {
278 assert(target >= 0);
279 assert(target < _LOG_TARGET_MAX);
280
281 log_target = target;
282 }
283
284 void log_close(void) {
285 log_close_journal();
286 log_close_syslog();
287 log_close_kmsg();
288 log_close_console();
289 }
290
291 void log_forget_fds(void) {
292 console_fd = kmsg_fd = syslog_fd = journal_fd = -1;
293 }
294
295 void log_set_max_level(int level) {
296 assert((level & LOG_PRIMASK) == level);
297
298 log_max_level = level;
299 }
300
301 void log_set_facility(int facility) {
302 log_facility = facility;
303 }
304
305 static int write_to_console(
306 int level,
307 const char*file,
308 int line,
309 const char *func,
310 const char *buffer) {
311
312 char location[64];
313 struct iovec iovec[5];
314 unsigned n = 0;
315 bool highlight;
316
317 if (console_fd < 0)
318 return 0;
319
320 highlight = LOG_PRI(level) <= LOG_ERR && show_color;
321
322 zero(iovec);
323
324 if (show_location) {
325 snprintf(location, sizeof(location), "(%s:%u) ", file, line);
326 char_array_0(location);
327 IOVEC_SET_STRING(iovec[n++], location);
328 }
329
330 if (highlight)
331 IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED_ON);
332 IOVEC_SET_STRING(iovec[n++], buffer);
333 if (highlight)
334 IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_OFF);
335 IOVEC_SET_STRING(iovec[n++], "\n");
336
337 if (writev(console_fd, iovec, n) < 0)
338 return -errno;
339
340 return 1;
341 }
342
343 static int write_to_syslog(
344 int level,
345 const char*file,
346 int line,
347 const char *func,
348 const char *buffer) {
349
350 char header_priority[16], header_time[64], header_pid[16];
351 struct iovec iovec[5];
352 struct msghdr msghdr;
353 time_t t;
354 struct tm *tm;
355
356 if (syslog_fd < 0)
357 return 0;
358
359 snprintf(header_priority, sizeof(header_priority), "<%i>", level);
360 char_array_0(header_priority);
361
362 t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
363 if (!(tm = localtime(&t)))
364 return -EINVAL;
365
366 if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
367 return -EINVAL;
368
369 snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
370 char_array_0(header_pid);
371
372 zero(iovec);
373 IOVEC_SET_STRING(iovec[0], header_priority);
374 IOVEC_SET_STRING(iovec[1], header_time);
375 IOVEC_SET_STRING(iovec[2], program_invocation_short_name);
376 IOVEC_SET_STRING(iovec[3], header_pid);
377 IOVEC_SET_STRING(iovec[4], buffer);
378
379 /* When using syslog via SOCK_STREAM separate the messages by NUL chars */
380 if (syslog_is_stream)
381 iovec[4].iov_len++;
382
383 zero(msghdr);
384 msghdr.msg_iov = iovec;
385 msghdr.msg_iovlen = ELEMENTSOF(iovec);
386
387 for (;;) {
388 ssize_t n;
389
390 n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL);
391 if (n < 0)
392 return -errno;
393
394 if (!syslog_is_stream ||
395 (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec)))
396 break;
397
398 IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n);
399 }
400
401 return 1;
402 }
403
404 static int write_to_kmsg(
405 int level,
406 const char*file,
407 int line,
408 const char *func,
409 const char *buffer) {
410
411 char header_priority[16], header_pid[16];
412 struct iovec iovec[5];
413
414 if (kmsg_fd < 0)
415 return 0;
416
417 snprintf(header_priority, sizeof(header_priority), "<%i>", level);
418 char_array_0(header_priority);
419
420 snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) getpid());
421 char_array_0(header_pid);
422
423 zero(iovec);
424 IOVEC_SET_STRING(iovec[0], header_priority);
425 IOVEC_SET_STRING(iovec[1], program_invocation_short_name);
426 IOVEC_SET_STRING(iovec[2], header_pid);
427 IOVEC_SET_STRING(iovec[3], buffer);
428 IOVEC_SET_STRING(iovec[4], "\n");
429
430 if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0)
431 return -errno;
432
433 return 1;
434 }
435
436 static int write_to_journal(
437 int level,
438 const char*file,
439 int line,
440 const char *func,
441 const char *buffer) {
442
443 char header[LINE_MAX];
444 struct iovec iovec[3];
445 struct msghdr mh;
446
447 if (journal_fd < 0)
448 return 0;
449
450 snprintf(header, sizeof(header),
451 "PRIORITY=%i\n"
452 "SYSLOG_FACILITY=%i\n"
453 "CODE_FILE=%s\n"
454 "CODE_LINE=%i\n"
455 "CODE_FUNCTION=%s\n"
456 "SYSLOG_IDENTIFIER=%s\n"
457 "MESSAGE=",
458 LOG_PRI(level),
459 LOG_FAC(level),
460 file,
461 line,
462 func,
463 program_invocation_short_name);
464
465 char_array_0(header);
466
467 zero(iovec);
468 IOVEC_SET_STRING(iovec[0], header);
469 IOVEC_SET_STRING(iovec[1], buffer);
470 IOVEC_SET_STRING(iovec[2], "\n");
471
472 zero(mh);
473 mh.msg_iov = iovec;
474 mh.msg_iovlen = ELEMENTSOF(iovec);
475
476 if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0)
477 return -errno;
478
479 return 1;
480 }
481
482 static int log_dispatch(
483 int level,
484 const char*file,
485 int line,
486 const char *func,
487 char *buffer) {
488
489 int r = 0;
490
491 if (log_target == LOG_TARGET_NULL)
492 return 0;
493
494 /* Patch in LOG_DAEMON facility if necessary */
495 if ((level & LOG_FACMASK) == 0)
496 level = log_facility | LOG_PRI(level);
497
498 do {
499 char *e;
500 int k = 0;
501
502 buffer += strspn(buffer, NEWLINE);
503
504 if (buffer[0] == 0)
505 break;
506
507 if ((e = strpbrk(buffer, NEWLINE)))
508 *(e++) = 0;
509
510 if (log_target == LOG_TARGET_AUTO ||
511 log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
512 log_target == LOG_TARGET_JOURNAL) {
513
514 k = write_to_journal(level, file, line, func, buffer);
515 if (k < 0) {
516 if (k != -EAGAIN)
517 log_close_journal();
518 log_open_kmsg();
519 } else if (k > 0)
520 r++;
521 }
522
523 if (log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
524 log_target == LOG_TARGET_SYSLOG) {
525
526 k = write_to_syslog(level, file, line, func, buffer);
527 if (k < 0) {
528 if (k != -EAGAIN)
529 log_close_syslog();
530 log_open_kmsg();
531 } else if (k > 0)
532 r++;
533 }
534
535 if (k <= 0 &&
536 (log_target == LOG_TARGET_AUTO ||
537 log_target == LOG_TARGET_SAFE ||
538 log_target == LOG_TARGET_SYSLOG_OR_KMSG ||
539 log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
540 log_target == LOG_TARGET_KMSG)) {
541
542 k = write_to_kmsg(level, file, line, func, buffer);
543 if (k < 0) {
544 log_close_kmsg();
545 log_open_console();
546 } else if (k > 0)
547 r++;
548 }
549
550 if (k <= 0) {
551 k = write_to_console(level, file, line, func, buffer);
552 if (k < 0)
553 return k;
554 }
555
556 buffer = e;
557 } while (buffer);
558
559 return r;
560 }
561
562 int log_dump_internal(
563 int level,
564 const char*file,
565 int line,
566 const char *func,
567 char *buffer) {
568
569 int saved_errno, r;
570
571 /* This modifies the buffer... */
572
573 if (_likely_(LOG_PRI(level) > log_max_level))
574 return 0;
575
576 saved_errno = errno;
577 r = log_dispatch(level, file, line, func, buffer);
578 errno = saved_errno;
579
580 return r;
581 }
582
583 int log_metav(
584 int level,
585 const char*file,
586 int line,
587 const char *func,
588 const char *format,
589 va_list ap) {
590
591 char buffer[LINE_MAX];
592 int saved_errno, r;
593
594 if (_likely_(LOG_PRI(level) > log_max_level))
595 return 0;
596
597 saved_errno = errno;
598 vsnprintf(buffer, sizeof(buffer), format, ap);
599 char_array_0(buffer);
600
601 r = log_dispatch(level, file, line, func, buffer);
602 errno = saved_errno;
603
604 return r;
605 }
606
607 int log_meta(
608 int level,
609 const char*file,
610 int line,
611 const char *func,
612 const char *format, ...) {
613
614 int r;
615 va_list ap;
616
617 va_start(ap, format);
618 r = log_metav(level, file, line, func, format, ap);
619 va_end(ap);
620
621 return r;
622 }
623
624 #pragma GCC diagnostic push
625 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
626 _noreturn_ static void log_assert(const char *text, const char *file, int line, const char *func, const char *format) {
627 static char buffer[LINE_MAX];
628
629 snprintf(buffer, sizeof(buffer), format, text, file, line, func);
630
631 char_array_0(buffer);
632 log_abort_msg = buffer;
633
634 log_dispatch(LOG_CRIT, file, line, func, buffer);
635 abort();
636 }
637 #pragma GCC diagnostic pop
638
639 _noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func) {
640 log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.");
641 }
642
643 _noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) {
644 log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.");
645 }
646
647 int log_oom_internal(const char *file, int line, const char *func) {
648 log_meta(LOG_ERR, file, line, func, "Out of memory.");
649 return -ENOMEM;
650 }
651
652 int log_struct_internal(
653 int level,
654 const char *file,
655 int line,
656 const char *func,
657 const char *format, ...) {
658
659 int saved_errno;
660 va_list ap;
661 int r;
662
663 if (_likely_(LOG_PRI(level) > log_max_level))
664 return 0;
665
666 if (log_target == LOG_TARGET_NULL)
667 return 0;
668
669 if ((level & LOG_FACMASK) == 0)
670 level = log_facility | LOG_PRI(level);
671
672 saved_errno = errno;
673
674 if ((log_target == LOG_TARGET_AUTO ||
675 log_target == LOG_TARGET_JOURNAL_OR_KMSG ||
676 log_target == LOG_TARGET_JOURNAL) &&
677 journal_fd >= 0) {
678
679 char header[LINE_MAX];
680 struct iovec iovec[17];
681 unsigned n = 0, i;
682 struct msghdr mh;
683 const char nl = '\n';
684
685 /* If the journal is available do structured logging */
686
687 snprintf(header, sizeof(header),
688 "PRIORITY=%i\n"
689 "SYSLOG_FACILITY=%i\n"
690 "CODE_FILE=%s\n"
691 "CODE_LINE=%i\n"
692 "CODE_FUNCTION=%s\n"
693 "SYSLOG_IDENTIFIER=%s\n",
694 LOG_PRI(level),
695 LOG_FAC(level),
696 file,
697 line,
698 func,
699 program_invocation_short_name);
700 char_array_0(header);
701
702 zero(iovec);
703 IOVEC_SET_STRING(iovec[n++], header);
704
705 va_start(ap, format);
706 while (format && n + 1 < ELEMENTSOF(iovec)) {
707 char *buf;
708
709 if (vasprintf(&buf, format, ap) < 0) {
710 r = -ENOMEM;
711 goto finish;
712 }
713
714 IOVEC_SET_STRING(iovec[n++], buf);
715
716 iovec[n].iov_base = (char*) &nl;
717 iovec[n].iov_len = 1;
718 n++;
719
720 format = va_arg(ap, char *);
721 }
722 va_end(ap);
723
724 zero(mh);
725 mh.msg_iov = iovec;
726 mh.msg_iovlen = n;
727
728 if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0)
729 r = -errno;
730 else
731 r = 1;
732
733 finish:
734 for (i = 1; i < n; i += 2)
735 free(iovec[i].iov_base);
736
737 } else {
738 char buf[LINE_MAX];
739 bool found = false;
740
741 /* Fallback if journal logging is not available */
742
743 va_start(ap, format);
744 while (format) {
745
746 vsnprintf(buf, sizeof(buf), format, ap);
747 char_array_0(buf);
748
749 if (startswith(buf, "MESSAGE=")) {
750 found = true;
751 break;
752 }
753
754 format = va_arg(ap, char *);
755 }
756 va_end(ap);
757
758 if (found)
759 r = log_dispatch(level, file, line, func, buf + 8);
760 else
761 r = -EINVAL;
762 }
763
764 errno = saved_errno;
765 return r;
766 }
767
768 int log_set_target_from_string(const char *e) {
769 LogTarget t;
770
771 t = log_target_from_string(e);
772 if (t < 0)
773 return -EINVAL;
774
775 log_set_target(t);
776 return 0;
777 }
778
779 int log_set_max_level_from_string(const char *e) {
780 int t;
781
782 t = log_level_from_string(e);
783 if (t < 0)
784 return t;
785
786 log_set_max_level(t);
787 return 0;
788 }
789
790 void log_parse_environment(void) {
791 const char *e;
792
793 e = secure_getenv("SYSTEMD_LOG_TARGET");
794 if (e && log_set_target_from_string(e) < 0)
795 log_warning("Failed to parse log target %s. Ignoring.", e);
796
797 e = secure_getenv("SYSTEMD_LOG_LEVEL");
798 if (e && log_set_max_level_from_string(e) < 0)
799 log_warning("Failed to parse log level %s. Ignoring.", e);
800
801 e = secure_getenv("SYSTEMD_LOG_COLOR");
802 if (e && log_show_color_from_string(e) < 0)
803 log_warning("Failed to parse bool %s. Ignoring.", e);
804
805 e = secure_getenv("SYSTEMD_LOG_LOCATION");
806 if (e && log_show_location_from_string(e) < 0)
807 log_warning("Failed to parse bool %s. Ignoring.", e);
808 }
809
810 LogTarget log_get_target(void) {
811 return log_target;
812 }
813
814 int log_get_max_level(void) {
815 return log_max_level;
816 }
817
818 void log_show_color(bool b) {
819 show_color = b;
820 }
821
822 void log_show_location(bool b) {
823 show_location = b;
824 }
825
826 int log_show_color_from_string(const char *e) {
827 int t;
828
829 t = parse_boolean(e);
830 if (t < 0)
831 return t;
832
833 log_show_color(t);
834 return 0;
835 }
836
837 int log_show_location_from_string(const char *e) {
838 int t;
839
840 t = parse_boolean(e);
841 if (t < 0)
842 return t;
843
844 log_show_location(t);
845 return 0;
846 }
847
848 bool log_on_console(void) {
849 if (log_target == LOG_TARGET_CONSOLE)
850 return true;
851
852 return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0;
853 }
854
855 static const char *const log_target_table[] = {
856 [LOG_TARGET_CONSOLE] = "console",
857 [LOG_TARGET_KMSG] = "kmsg",
858 [LOG_TARGET_JOURNAL] = "journal",
859 [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg",
860 [LOG_TARGET_SYSLOG] = "syslog",
861 [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg",
862 [LOG_TARGET_AUTO] = "auto",
863 [LOG_TARGET_SAFE] = "safe",
864 [LOG_TARGET_NULL] = "null"
865 };
866
867 DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget);