xref: /third_party/pulseaudio/src/pulsecore/log.c (revision 53a5a1b3)
1/***
2  This file is part of PulseAudio.
3
4  Copyright 2004-2006 Lennart Poettering
5  Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7  PulseAudio is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as
9  published by the Free Software Foundation; either version 2.1 of the
10  License, or (at your option) any later version.
11
12  PulseAudio is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <stdarg.h>
26#include <stdio.h>
27#include <unistd.h>
28#include <string.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <sys/stat.h>
32
33#ifdef HAVE_EXECINFO_H
34#include <execinfo.h>
35#endif
36
37#ifdef HAVE_SYSLOG_H
38#include <syslog.h>
39#endif
40
41#ifdef HAVE_SYSTEMD_JOURNAL
42
43/* sd_journal_send() implicitly add fields for the source file,
44 * function name and code line from where it's invoked. As the
45 * correct code location fields CODE_FILE, CODE_LINE and
46 * CODE_FUNC are already handled by this module, we do not want
47 * the automatic values supplied by the systemd journal API.
48 *
49 * Without suppressing these, both the actual log event source
50 * and the call to sd_journal_send() will be logged. */
51#define SD_JOURNAL_SUPPRESS_LOCATION
52
53#include <systemd/sd-journal.h>
54#endif
55
56#include <pulse/gccmacro.h>
57#include <pulse/rtclock.h>
58#include <pulse/utf8.h>
59#include <pulse/xmalloc.h>
60#include <pulse/util.h>
61#include <pulse/timeval.h>
62
63#include <pulsecore/macro.h>
64#include <pulsecore/core-util.h>
65#include <pulsecore/core-error.h>
66#include <pulsecore/once.h>
67#include <pulsecore/ratelimit.h>
68#include <pulsecore/thread.h>
69#include <pulsecore/i18n.h>
70
71#include "log.h"
72
73#define ENV_LOG_SYSLOG "PULSE_LOG_SYSLOG"
74#define ENV_LOG_JOURNAL "PULSE_LOG_JOURNAL"
75#define ENV_LOG_LEVEL "PULSE_LOG"
76#define ENV_LOG_COLORS "PULSE_LOG_COLORS"
77#define ENV_LOG_PRINT_TIME "PULSE_LOG_TIME"
78#define ENV_LOG_PRINT_FILE "PULSE_LOG_FILE"
79#define ENV_LOG_PRINT_META "PULSE_LOG_META"
80#define ENV_LOG_PRINT_LEVEL "PULSE_LOG_LEVEL"
81#define ENV_LOG_BACKTRACE "PULSE_LOG_BACKTRACE"
82#define ENV_LOG_BACKTRACE_SKIP "PULSE_LOG_BACKTRACE_SKIP"
83#define ENV_LOG_NO_RATELIMIT "PULSE_LOG_NO_RATE_LIMIT"
84#define LOG_MAX_SUFFIX_NUMBER 99
85
86static char *ident = NULL; /* in local charset format */
87static pa_log_target target = { PA_LOG_STDERR, NULL };
88static pa_log_target_type_t target_override;
89static bool target_override_set = false;
90static pa_log_level_t maximum_level = PA_LOG_ERROR, maximum_level_override = PA_LOG_ERROR;
91static unsigned show_backtrace = 0, show_backtrace_override = 0, skip_backtrace = 0;
92static pa_log_flags_t flags = 0, flags_override = 0;
93static bool no_rate_limit = false;
94static int log_fd = -1;
95static int write_type = 0;
96
97#ifdef HAVE_SYSLOG_H
98static const int level_to_syslog[] = {
99    [PA_LOG_ERROR] = LOG_ERR,
100    [PA_LOG_WARN] = LOG_WARNING,
101    [PA_LOG_NOTICE] = LOG_NOTICE,
102    [PA_LOG_INFO] = LOG_INFO,
103    [PA_LOG_DEBUG] = LOG_DEBUG
104};
105#endif
106
107/* These are actually equivalent to the syslog ones
108 * but we don't want to depend on syslog.h */
109#ifdef HAVE_SYSTEMD_JOURNAL
110static const int level_to_journal[] = {
111    [PA_LOG_ERROR]  = 3,
112    [PA_LOG_WARN]   = 4,
113    [PA_LOG_NOTICE] = 5,
114    [PA_LOG_INFO]   = 6,
115    [PA_LOG_DEBUG]  = 7
116};
117#endif
118
119static const char level_to_char[] = {
120    [PA_LOG_ERROR] = 'E',
121    [PA_LOG_WARN] = 'W',
122    [PA_LOG_NOTICE] = 'N',
123    [PA_LOG_INFO] = 'I',
124    [PA_LOG_DEBUG] = 'D'
125};
126
127void pa_log_set_ident(const char *p) {
128    pa_xfree(ident);
129
130    if (!(ident = pa_utf8_to_locale(p)))
131        ident = pa_ascii_filter(p);
132}
133
134/* To make valgrind shut up. */
135static void ident_destructor(void) PA_GCC_DESTRUCTOR;
136static void ident_destructor(void) {
137    if (!pa_in_valgrind())
138        return;
139
140    pa_xfree(ident);
141}
142
143void pa_log_set_level(pa_log_level_t l) {
144    pa_assert(l < PA_LOG_LEVEL_MAX);
145
146    maximum_level = l;
147}
148
149int pa_log_set_target(pa_log_target *t) {
150    int fd = -1;
151    int old_fd;
152
153    pa_assert(t);
154
155    switch (t->type) {
156        case PA_LOG_STDERR:
157        case PA_LOG_SYSLOG:
158#ifdef HAVE_SYSTEMD_JOURNAL
159        case PA_LOG_JOURNAL:
160#endif
161        case PA_LOG_NULL:
162            break;
163        case PA_LOG_FILE:
164            if ((fd = pa_open_cloexec(t->file, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
165                pa_log(_("Failed to open target file '%s'."), t->file);
166                return -1;
167            }
168            break;
169        case PA_LOG_NEWFILE: {
170            char *file_path;
171            char *p;
172            unsigned version;
173
174            file_path = pa_sprintf_malloc("%s.xx", t->file);
175            p = file_path + strlen(t->file);
176
177            for (version = 0; version <= LOG_MAX_SUFFIX_NUMBER; version++) {
178                memset(p, 0, 3); /* Overwrite the ".xx" part in file_path with zero bytes. */
179
180                if (version > 0)
181                    pa_snprintf(p, 4, ".%u", version); /* Why 4? ".xx" + termitating zero byte. */
182
183                if ((fd = pa_open_cloexec(file_path, O_WRONLY | O_TRUNC | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) >= 0)
184                    break;
185            }
186
187            if (version > LOG_MAX_SUFFIX_NUMBER) {
188                pa_log(_("Tried to open target file '%s', '%s.1', '%s.2' ... '%s.%d', but all failed."),
189                        t->file, t->file, t->file, t->file, LOG_MAX_SUFFIX_NUMBER);
190                pa_xfree(file_path);
191                return -1;
192            } else
193                pa_log_debug("Opened target file %s\n", file_path);
194
195            pa_xfree(file_path);
196            break;
197        }
198    }
199
200    target.type = t->type;
201    pa_xfree(target.file);
202    target.file = pa_xstrdup(t->file);
203
204    old_fd = log_fd;
205    log_fd = fd;
206
207    if (old_fd >= 0)
208        pa_close(old_fd);
209
210    return 0;
211}
212
213void pa_log_set_flags(pa_log_flags_t _flags, pa_log_merge_t merge) {
214    pa_assert(!(_flags & ~(PA_LOG_COLORS|PA_LOG_PRINT_TIME|PA_LOG_PRINT_FILE|PA_LOG_PRINT_META|PA_LOG_PRINT_LEVEL)));
215
216    if (merge == PA_LOG_SET)
217        flags |= _flags;
218    else if (merge == PA_LOG_UNSET)
219        flags &= ~_flags;
220    else
221        flags = _flags;
222}
223
224void pa_log_set_show_backtrace(unsigned nlevels) {
225    show_backtrace = nlevels;
226}
227
228void pa_log_set_skip_backtrace(unsigned nlevels) {
229    skip_backtrace = nlevels;
230}
231
232#ifdef HAVE_EXECINFO_H
233
234static char* get_backtrace(unsigned show_nframes) {
235    void* trace[32];
236    int n_frames;
237    char **symbols, *e, *r;
238    unsigned j, n, s;
239    size_t a;
240
241    pa_assert(show_nframes > 0);
242
243    n_frames = backtrace(trace, PA_ELEMENTSOF(trace));
244
245    if (n_frames <= 0)
246        return NULL;
247
248    symbols = backtrace_symbols(trace, n_frames);
249
250    if (!symbols)
251        return NULL;
252
253    s = skip_backtrace;
254    n = PA_MIN((unsigned) n_frames, s + show_nframes);
255
256    a = 4;
257
258    for (j = s; j < n; j++) {
259        if (j > s)
260            a += 2;
261        a += strlen(pa_path_get_filename(symbols[j]));
262    }
263
264    r = pa_xnew(char, a);
265
266    strcpy(r, " (");
267    e = r + 2;
268
269    for (j = s; j < n; j++) {
270        const char *sym;
271
272        if (j > s) {
273            strcpy(e, "<<");
274            e += 2;
275        }
276
277        sym = pa_path_get_filename(symbols[j]);
278
279        strcpy(e, sym);
280        e += strlen(sym);
281    }
282
283    strcpy(e, ")");
284
285    free(symbols);
286
287    return r;
288}
289
290#endif
291
292static void init_defaults(void) {
293    PA_ONCE_BEGIN {
294
295        const char *e;
296
297        if (!ident) {
298            char binary[256];
299            if (pa_get_binary_name(binary, sizeof(binary)))
300                pa_log_set_ident(binary);
301        }
302
303        if (getenv(ENV_LOG_SYSLOG)) {
304            target_override = PA_LOG_SYSLOG;
305            target_override_set = true;
306        }
307
308#ifdef HAVE_SYSTEMD_JOURNAL
309        if (getenv(ENV_LOG_JOURNAL)) {
310            target_override = PA_LOG_JOURNAL;
311            target_override_set = true;
312        }
313#endif
314
315        if ((e = getenv(ENV_LOG_LEVEL))) {
316            maximum_level_override = (pa_log_level_t) atoi(e);
317
318            if (maximum_level_override >= PA_LOG_LEVEL_MAX)
319                maximum_level_override = PA_LOG_LEVEL_MAX-1;
320        }
321
322        if (getenv(ENV_LOG_COLORS))
323            flags_override |= PA_LOG_COLORS;
324
325        if (getenv(ENV_LOG_PRINT_TIME))
326            flags_override |= PA_LOG_PRINT_TIME;
327
328        if (getenv(ENV_LOG_PRINT_FILE))
329            flags_override |= PA_LOG_PRINT_FILE;
330
331        if (getenv(ENV_LOG_PRINT_META))
332            flags_override |= PA_LOG_PRINT_META;
333
334        if (getenv(ENV_LOG_PRINT_LEVEL))
335            flags_override |= PA_LOG_PRINT_LEVEL;
336
337        if ((e = getenv(ENV_LOG_BACKTRACE))) {
338            show_backtrace_override = (unsigned) atoi(e);
339
340            if (show_backtrace_override <= 0)
341                show_backtrace_override = 0;
342        }
343
344        if ((e = getenv(ENV_LOG_BACKTRACE_SKIP))) {
345            skip_backtrace = (unsigned) atoi(e);
346
347            if (skip_backtrace <= 0)
348                skip_backtrace = 0;
349        }
350
351        if (getenv(ENV_LOG_NO_RATELIMIT))
352            no_rate_limit = true;
353
354    } PA_ONCE_END;
355}
356
357#ifdef HAVE_SYSLOG_H
358static void log_syslog(pa_log_level_t level, char *t, char *timestamp, char *location, char *bt) {
359    char *local_t;
360
361    openlog(ident, LOG_PID, LOG_USER);
362
363    if ((local_t = pa_utf8_to_locale(t)))
364        t = local_t;
365
366    syslog(level_to_syslog[level], "%s%s%s%s", timestamp, location, t, pa_strempty(bt));
367    pa_xfree(local_t);
368}
369#endif
370
371void pa_log_levelv_meta(
372        pa_log_level_t level,
373        const char*file,
374        int line,
375        const char *func,
376        const char *format,
377        va_list ap) {
378
379    char *t, *n;
380    int saved_errno = errno;
381    char *bt = NULL;
382    pa_log_target_type_t _target;
383    pa_log_level_t _maximum_level;
384    unsigned _show_backtrace;
385    pa_log_flags_t _flags;
386
387    /* We don't use dynamic memory allocation here to minimize the hit
388     * in RT threads */
389    char text[256], location[128], timestamp[32];
390
391    pa_assert(level < PA_LOG_LEVEL_MAX);
392    pa_assert(format);
393
394    init_defaults();
395
396    _target = target_override_set ? target_override : target.type;
397    _maximum_level = PA_MAX(maximum_level, maximum_level_override);
398    _show_backtrace = PA_MAX(show_backtrace, show_backtrace_override);
399    _flags = flags | flags_override;
400
401    if (PA_LIKELY(level > _maximum_level)) {
402        errno = saved_errno;
403        return;
404    }
405
406    pa_vsnprintf(text, sizeof(text), format, ap);
407
408    const char *filename = strrchr(file, '/');
409    if (filename) {
410        filename++;
411    } else {
412        filename = file;
413    }
414
415    if (level == PA_LOG_ERROR) {
416        AUDIO_ERR_LOG("%{public}s:%{public}u (%{public}s) %{public}s", filename, line, func, text);
417    } else if (level == PA_LOG_WARN) {
418        AUDIO_WARNING_LOG("%{public}s:%{public}u (%{public}s) %{public}s", filename, line, func, text);
419    } else if (level == PA_LOG_NOTICE || level == PA_LOG_INFO) {
420        AUDIO_INFO_LOG("%{public}s:%{public}u (%{public}s) %{public}s", filename, line, func, text);
421    } else if (level == PA_LOG_DEBUG) {
422        AUDIO_DEBUG_LOG("%{public}s:%{public}u (%{public}s) %{public}s", filename, line, func, text);
423    }
424
425    if ((_flags & PA_LOG_PRINT_META) && file && line > 0 && func)
426        pa_snprintf(location, sizeof(location), "[%s][%s:%i %s()] ",
427                    pa_strnull(pa_thread_get_name(pa_thread_self())), file, line, func);
428    else if ((_flags & (PA_LOG_PRINT_META|PA_LOG_PRINT_FILE)) && file)
429        pa_snprintf(location, sizeof(location), "[%s] %s: ",
430                    pa_strnull(pa_thread_get_name(pa_thread_self())), pa_path_get_filename(file));
431    else
432        location[0] = 0;
433
434    if (_flags & PA_LOG_PRINT_TIME) {
435        static pa_usec_t start, last;
436        pa_usec_t u, a, r;
437
438        u = pa_rtclock_now();
439
440        PA_ONCE_BEGIN {
441            start = u;
442            last = u;
443        } PA_ONCE_END;
444
445        r = u - last;
446        a = u - start;
447
448        /* This is not thread safe, but this is a debugging tool only
449         * anyway. */
450        last = u;
451
452        pa_snprintf(timestamp, sizeof(timestamp), "(%4llu.%03llu|%4llu.%03llu) ",
453                    (unsigned long long) (a / PA_USEC_PER_SEC),
454                    (unsigned long long) (((a / PA_USEC_PER_MSEC)) % 1000),
455                    (unsigned long long) (r / PA_USEC_PER_SEC),
456                    (unsigned long long) (((r / PA_USEC_PER_MSEC)) % 1000));
457
458    } else
459        timestamp[0] = 0;
460
461#ifdef HAVE_EXECINFO_H
462    if (_show_backtrace > 0)
463        bt = get_backtrace(_show_backtrace);
464#endif
465
466    if (!pa_utf8_valid(text))
467        pa_logl(level, "Invalid UTF-8 string following below:");
468
469    for (t = text; t; t = n) {
470        if ((n = strchr(t, '\n'))) {
471            *n = 0;
472            n++;
473        }
474
475        /* We ignore strings only made out of whitespace */
476        if (t[strspn(t, "\t ")] == 0)
477            continue;
478
479        switch (_target) {
480
481            case PA_LOG_STDERR: {
482                const char *prefix = "", *suffix = "", *grey = "";
483                char *local_t;
484
485#ifndef OS_IS_WIN32
486                /* Yes indeed. Useless, but fun! */
487                if ((_flags & PA_LOG_COLORS) && isatty(STDERR_FILENO)) {
488                    if (level <= PA_LOG_ERROR)
489                        prefix = "\x1B[1;31m";
490                    else if (level <= PA_LOG_WARN)
491                        prefix = "\x1B[1m";
492
493                    if (bt)
494                        grey = "\x1B[2m";
495
496                    if (grey[0] || prefix[0])
497                        suffix = "\x1B[0m";
498                }
499#endif
500
501                /* We shouldn't be using dynamic allocation here to
502                 * minimize the hit in RT threads */
503                if ((local_t = pa_utf8_to_locale(t)))
504                    t = local_t;
505
506                if (_flags & PA_LOG_PRINT_LEVEL)
507                    fprintf(stderr, "%s%c: %s%s%s%s%s%s\n", timestamp, level_to_char[level], location, prefix, t, grey, pa_strempty(bt), suffix);
508                else
509                    fprintf(stderr, "%s%s%s%s%s%s%s\n", timestamp, location, prefix, t, grey, pa_strempty(bt), suffix);
510#ifdef OS_IS_WIN32
511                fflush(stderr);
512#endif
513
514                pa_xfree(local_t);
515
516                break;
517            }
518
519#ifdef HAVE_SYSLOG_H
520            case PA_LOG_SYSLOG:
521                log_syslog(level, t, timestamp, location, bt);
522                break;
523#endif
524
525#ifdef HAVE_SYSTEMD_JOURNAL
526            case PA_LOG_JOURNAL:
527                if (sd_journal_send("MESSAGE=%s", t,
528                                "PRIORITY=%i", level_to_journal[level],
529                                "CODE_FILE=%s", file,
530                                "CODE_FUNC=%s", func,
531                                "CODE_LINE=%d", line,
532                                "PULSE_BACKTRACE=%s", pa_strempty(bt),
533                                NULL) < 0) {
534#ifdef HAVE_SYSLOG_H
535                    pa_log_target new_target = { .type = PA_LOG_SYSLOG, .file = NULL };
536
537                    syslog(level_to_syslog[PA_LOG_ERROR], "%s%s%s", timestamp, __FILE__,
538                           "Error writing logs to the journal. Redirect log messages to syslog.");
539                    log_syslog(level, t, timestamp, location, bt);
540#else
541                    pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL };
542
543                    saved_errno = errno;
544                    fprintf(stderr, "%s\n", "Error writing logs to the journal. Redirect log messages to console.");
545                    fprintf(stderr, "%s\n", t);
546#endif
547                    pa_log_set_target(&new_target);
548                }
549                break;
550#endif
551
552            case PA_LOG_FILE:
553            case PA_LOG_NEWFILE: {
554                char *local_t;
555
556                if ((local_t = pa_utf8_to_locale(t)))
557                    t = local_t;
558
559                if (log_fd >= 0) {
560                    char metadata[256];
561
562                    if (_flags & PA_LOG_PRINT_LEVEL)
563                        pa_snprintf(metadata, sizeof(metadata), "%s%c: %s", timestamp, level_to_char[level], location);
564                    else
565                        pa_snprintf(metadata, sizeof(metadata), "%s%s", timestamp, location);
566
567                    if ((pa_write(log_fd, metadata, strlen(metadata), &write_type) < 0)
568                            || (pa_write(log_fd, t, strlen(t), &write_type) < 0)
569                            || (bt && pa_write(log_fd, bt, strlen(bt), &write_type) < 0)
570                            || (pa_write(log_fd, "\n", 1, &write_type) < 0)) {
571                        pa_log_target new_target = { .type = PA_LOG_STDERR, .file = NULL };
572                        saved_errno = errno;
573                        fprintf(stderr, "%s\n", "Error writing logs to a file descriptor. Redirect log messages to console.");
574                        fprintf(stderr, "%s %s\n", metadata, t);
575                        pa_log_set_target(&new_target);
576                    }
577                }
578
579                pa_xfree(local_t);
580
581                break;
582            }
583            case PA_LOG_NULL:
584            default:
585                break;
586        }
587    }
588
589    pa_xfree(bt);
590    errno = saved_errno;
591}
592
593void PrintCallStackInfo()
594{
595    const int32_t maxDepth = 20;
596    void *stacks[maxDepth];
597
598    int stackNum = backtrace(stacks, maxDepth);
599    AUDIO_ERR_LOG("backtrace() returned %{public}d addresses\n", stackNum);
600
601    char **symbols = backtrace_symbols(stacks, stackNum);
602    if (symbols == NULL) {
603        AUDIO_ERR_LOG("backtrace_symbols faile.");
604        for (int i = 0; i < stackNum; i++) {
605            AUDIO_ERR_LOG("  [%{public}02d] addr: %{public}p\n", i, stacks[i]);
606        }
607        return;
608    }
609
610    for (int i = 0; i < stackNum; i++) {
611        AUDIO_ERR_LOG("  [%{public}02d] %{public}s\n", i, symbols[i]);
612    }
613
614    free(symbols);
615}
616
617void pa_log_level_meta(
618        pa_log_level_t level,
619        const char*file,
620        int line,
621        const char *func,
622        const char *format, ...) {
623
624    va_list ap;
625    va_start(ap, format);
626    pa_log_levelv_meta(level, file, line, func, format, ap);
627    va_end(ap);
628}
629
630void pa_log_levelv(pa_log_level_t level, const char *format, va_list ap) {
631    pa_log_levelv_meta(level, NULL, 0, NULL, format, ap);
632}
633
634void pa_log_level(pa_log_level_t level, const char *format, ...) {
635    va_list ap;
636
637    va_start(ap, format);
638    pa_log_levelv_meta(level, NULL, 0, NULL, format, ap);
639    va_end(ap);
640}
641
642bool pa_log_ratelimit(pa_log_level_t level) {
643    /* Not more than 10 messages every 5s */
644    static PA_DEFINE_RATELIMIT(ratelimit, 5 * PA_USEC_PER_SEC, 10);
645
646    init_defaults();
647
648    if (no_rate_limit)
649        return true;
650
651    return pa_ratelimit_test(&ratelimit, level);
652}
653
654pa_log_target *pa_log_target_new(pa_log_target_type_t type, const char *file) {
655    pa_log_target *t = NULL;
656
657    t = pa_xnew(pa_log_target, 1);
658
659    t->type = type;
660    t->file = pa_xstrdup(file);
661
662    return t;
663}
664
665void pa_log_target_free(pa_log_target *t) {
666    pa_assert(t);
667
668    pa_xfree(t->file);
669    pa_xfree(t);
670}
671
672pa_log_target *pa_log_parse_target(const char *string) {
673    pa_log_target *t = NULL;
674
675    pa_assert(string);
676
677    if (pa_streq(string, "stderr"))
678        t = pa_log_target_new(PA_LOG_STDERR, NULL);
679    else if (pa_streq(string, "syslog"))
680        t = pa_log_target_new(PA_LOG_SYSLOG, NULL);
681#ifdef HAVE_SYSTEMD_JOURNAL
682    else if (pa_streq(string, "journal"))
683        t = pa_log_target_new(PA_LOG_JOURNAL, NULL);
684#endif
685    else if (pa_streq(string, "null"))
686        t = pa_log_target_new(PA_LOG_NULL, NULL);
687    else if (pa_startswith(string, "file:"))
688        t = pa_log_target_new(PA_LOG_FILE, string + 5);
689    else if (pa_startswith(string, "newfile:"))
690        t = pa_log_target_new(PA_LOG_NEWFILE, string + 8);
691    else
692        pa_log(_("Invalid log target."));
693
694    return t;
695}
696
697char *pa_log_target_to_string(const pa_log_target *t) {
698    char *string = NULL;
699
700    pa_assert(t);
701
702    switch (t->type) {
703        case PA_LOG_STDERR:
704            string = pa_xstrdup("stderr");
705            break;
706        case PA_LOG_SYSLOG:
707            string = pa_xstrdup("syslog");
708            break;
709#ifdef HAVE_SYSTEMD_JOURNAL
710        case PA_LOG_JOURNAL:
711            string = pa_xstrdup("journal");
712            break;
713#endif
714        case PA_LOG_NULL:
715            string = pa_xstrdup("null");
716            break;
717        case PA_LOG_FILE:
718            string = pa_sprintf_malloc("file:%s", t->file);
719            break;
720        case PA_LOG_NEWFILE:
721            string = pa_sprintf_malloc("newfile:%s", t->file);
722            break;
723    }
724
725    return string;
726}
727