/* WirePlumber * * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: MIT */ #include "log.h" #include "wp.h" #include #include WP_DEFINE_LOCAL_LOG_TOPIC ("wp-log") /*! * \defgroup wplog Debug Logging * \{ */ /*! * \def WP_LOG_LEVEL_TRACE * \brief A custom GLib log level for trace messages (see GLogLevelFlags) */ /*! * \def WP_OBJECT_FORMAT * \brief A format string to print GObjects with WP_OBJECT_ARGS() * For example: * \code * GObject *myobj = ...; * wp_debug ("This: " WP_OBJECT_FORMAT " is an object", WP_OBJECT_ARGS (myobj)); * \endcode */ /*! * \def WP_OBJECT_ARGS(object) * \brief A macro to format an object for printing with WP_OBJECT_FORMAT */ /*! * \def wp_critical(...) * \brief Logs a critical message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_critical_object(object, ...) * \brief Logs a critical message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_critical_boxed(type, object, ...) * \brief Logs a critical message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_warning(...) * \brief Logs a warning message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_warning_object(object, ...) * \brief Logs a warning message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_warning_boxed(type, object, ...) * \brief Logs a warning message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_notice(...) * \brief Logs a notice message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_notice_object(object, ...) * \brief Logs a notice message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_notice_boxed(type, object, ...) * \brief Logs a notice message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_info(...) * \brief Logs a info message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_info_object(object, ...) * \brief Logs a info message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_info_boxed(type, object, ...) * \brief Logs a info message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_debug(...) * \brief Logs a debug message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_debug_object(object, ...) * \brief Logs a debug message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_debug_boxed(type, object, ...) * \brief Logs a debug message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_trace(...) * \brief Logs a trace message to the standard log via GLib's logging system. * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_trace_object(object, ...) * \brief Logs a trace message to the standard log via GLib's logging system. * \param object A GObject associated with the log; this is printed in a special * way to make it easier to track messages from a specific object * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_trace_boxed(type, object, ...) * \brief Logs a trace message to the standard log via GLib's logging system. * \param type The type of \a object * \param object A boxed object associated with the log; this is printed in a * special way to make it easier to track messages from a specific object. * For some object types, contents from the object are also printed (ex WpSpaPod) * \param ... A format string, followed by format arguments in printf() style */ /*! * \def wp_log(level, type, object, ...) * \brief The generic form of all the logging macros * \remark Don't use this directly, use one of the other logging macros */ /*! \} */ static GString *spa_dbg_str = NULL; #define spa_debug(...) \ ({ \ g_string_append_printf (spa_dbg_str, __VA_ARGS__); \ g_string_append_c (spa_dbg_str, '\n'); \ }) #include struct log_topic_pattern { GPatternSpec *spec; gint log_level; }; static struct { gboolean use_color; gboolean output_is_journal; gint global_log_level; GLogLevelFlags global_log_level_flags; struct log_topic_pattern *patterns; } log_state = { .use_color = FALSE, .output_is_journal = FALSE, .global_log_level = 4 /* MESSAGE */, .global_log_level_flags = G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR, .patterns = NULL, }; /* reference: https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit */ #define COLOR_RED "\033[1;31m" #define COLOR_GREEN "\033[1;32m" #define COLOR_YELLOW "\033[1;33m" #define COLOR_BLUE "\033[1;34m" #define COLOR_MAGENTA "\033[1;35m" #define COLOR_CYAN "\033[1;36m" #define COLOR_BRIGHT_RED "\033[1;91m" #define COLOR_BRIGHT_GREEN "\033[1;92m" #define COLOR_BRIGHT_YELLOW "\033[1;93m" #define COLOR_BRIGHT_BLUE "\033[1;94m" #define COLOR_BRIGHT_MAGENTA "\033[1;95m" #define COLOR_BRIGHT_CYAN "\033[1;96m" #define RESET_COLOR "\033[0m" /* our palette */ #define DOMAIN_COLOR COLOR_MAGENTA #define LOCATION_COLOR COLOR_BLUE /* available colors for object printouts (the ) */ static const gchar *object_colors[] = { COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN, COLOR_BRIGHT_RED, COLOR_BRIGHT_GREEN, COLOR_BRIGHT_YELLOW, COLOR_BRIGHT_MAGENTA, COLOR_BRIGHT_CYAN }; /* * priority numbers are based on GLib's gmessages.c * reference: http://man7.org/linux/man-pages/man3/syslog.3.html#DESCRIPTION */ static const struct { GLogLevelFlags log_level_flags; enum spa_log_level spa_level; gchar name[6]; gchar priority[2]; gchar color[8]; } log_level_info[] = { { 0, 0, "U", "0", COLOR_BRIGHT_MAGENTA }, { G_LOG_LEVEL_ERROR, 0, "F", "3" /* LOG_ERR */, COLOR_BRIGHT_RED }, { G_LOG_LEVEL_CRITICAL,SPA_LOG_LEVEL_ERROR,"E", "4" /* LOG_WARNING */, COLOR_RED }, { G_LOG_LEVEL_WARNING, SPA_LOG_LEVEL_WARN, "W", "4" /* LOG_WARNING */, COLOR_BRIGHT_YELLOW }, { G_LOG_LEVEL_MESSAGE, SPA_LOG_LEVEL_WARN, "N", "5" /* LOG_NOTICE */, COLOR_BRIGHT_GREEN }, { G_LOG_LEVEL_INFO, SPA_LOG_LEVEL_INFO, "I", "6" /* LOG_INFO */, COLOR_GREEN }, { G_LOG_LEVEL_DEBUG, SPA_LOG_LEVEL_DEBUG,"D", "7" /* LOG_DEBUG */, COLOR_BRIGHT_CYAN }, { WP_LOG_LEVEL_TRACE, SPA_LOG_LEVEL_TRACE,"T", "7" /* LOG_DEBUG */, COLOR_CYAN }, }; /* map glib's log levels, which are flags in the range (1<<2) to (1<<8), to the 1-7 range; first calculate the integer part of log2(log_level) to bring it down to 2-8 and substract 1 */ static G_GNUC_CONST inline gint level_index_from_flags (GLogLevelFlags log_level) { gint logarithm = 0; while ((log_level >>= 1) != 0) logarithm += 1; return (logarithm >= 2 && logarithm <= 8) ? (logarithm - 1) : 0; } /* map an index in the log_level_info table to a single GLogLevelFlags flag */ static G_GNUC_CONST inline GLogLevelFlags level_index_to_flag (gint lvl_index) { return log_level_info [lvl_index].log_level_flags; } /* map an index in the log_level_info table to an OR combination of all the GLogLevelFlags that are enabled at this level */ static G_GNUC_CONST inline GLogLevelFlags level_index_to_full_flags (gint lvl_index) { GLogLevelFlags flags = 0; for (gint i = 1; i <= lvl_index; i++) { flags |= level_index_to_flag (i); } return flags; } /* map a SPA_LOG_LEVEL_* to an index in the log_level_info table; note that SPA_LOG_LEVEL_WARN maps to 4 (G_LOG_LEVEL_MESSAGE) and index 3 (G_LOG_LEVEL_WARNING) can never be returned */ static G_GNUC_CONST inline gint level_index_from_spa (gint spa_lvl) { if (G_UNLIKELY (spa_lvl <= SPA_LOG_LEVEL_NONE)) return 0; else if (spa_lvl < SPA_LOG_LEVEL_WARN) return spa_lvl + 1; else if (G_UNLIKELY (spa_lvl > SPA_LOG_LEVEL_TRACE)) return (gint) G_N_ELEMENTS (log_level_info) - 1; else return spa_lvl + 2; } /* map an index in the log_level_info table to a SPA_LOG_LEVEL_* here, G_LOG_LEVEL_MESSAGE maps to SPA_LOG_LEVEL_WARN */ static G_GNUC_CONST inline gint level_index_to_spa (gint lvl_index) { return log_level_info [lvl_index].spa_level; } static gboolean level_index_from_string (const char *str, gint *lvl) { g_return_val_if_fail (str != NULL, FALSE); /* level is always 1 character */ if (str[0] != '\0' && str[1] == '\0') { for (guint i = 1; i < G_N_ELEMENTS (log_level_info); i++) { if (str[0] == log_level_info[i].name[0]) { *lvl = i; return TRUE; } } if (str[0] >= '0' && str[0] <= '5') { *lvl = level_index_from_spa (str[0] - '0'); return TRUE; } } return FALSE; } /* private, called from wp_init() */ void wp_log_init (gint flags) { const gchar *level_str; gint global_log_level = log_state.global_log_level; struct log_topic_pattern *patterns = NULL, *pttrn; gint n_tokens = 0; gchar **tokens = NULL; level_str = g_getenv ("WIREPLUMBER_DEBUG"); log_state.use_color = g_log_writer_supports_color (fileno (stderr)); log_state.output_is_journal = g_log_writer_is_journald (fileno (stderr)); if (level_str && level_str[0] != '\0') { /* [:],..., */ tokens = pw_split_strv (level_str, ",", INT_MAX, &n_tokens); } /* allocate enough space to hold all pattern specs */ patterns = g_malloc_n ((n_tokens + 2), sizeof (struct log_topic_pattern)); pttrn = patterns; if (!patterns) g_error ("unable to allocate space for %d log patterns", n_tokens + 2); for (gint i = 0; i < n_tokens; i++) { gint n_tok; gchar **tok; gint lvl; tok = pw_split_strv (tokens[i], ":", 2, &n_tok); if (n_tok == 2 && level_index_from_string (tok[1], &lvl)) { pttrn->spec = g_pattern_spec_new (tok[0]); pttrn->log_level = lvl; pttrn++; } else if (n_tok == 1 && level_index_from_string (tok[0], &lvl)) { global_log_level = lvl; } else { /* note that this is going to initialize the wp-log topic here */ wp_warning ("Ignoring invalid format in WIREPLUMBER_DEBUG: '%s'", tokens[i]); } pw_free_strv (tok); } /* disable pipewire connection trace by default */ pttrn->spec = g_pattern_spec_new ("conn.*"); pttrn->log_level = 0; pttrn++; /* terminate with NULL */ pttrn->spec = NULL; pttrn->log_level = 0; pw_free_strv (tokens); log_state.patterns = patterns; log_state.global_log_level = global_log_level; log_state.global_log_level_flags = level_index_to_full_flags (global_log_level); /* set the log level also on the spa_log */ wp_spa_log_get_instance()->level = level_index_to_spa (global_log_level); if (flags & WP_INIT_SET_GLIB_LOG) g_log_set_writer_func (wp_log_writer_default, NULL, NULL); /* set PIPEWIRE_DEBUG and the spa_log interface that pipewire will use */ if (flags & WP_INIT_SET_PW_LOG && !g_getenv ("WIREPLUMBER_NO_PW_LOG")) { /* always set PIPEWIRE_DEBUG for 2 reasons: * 1. to overwrite it from the environment, in case the user has set it * 2. to prevent pw_context from parsing "log.level" from the config file; * we do this ourselves here and allows us to have more control over * the whole process. */ gchar lvl_str[2]; g_snprintf (lvl_str, 2, "%d", wp_spa_log_get_instance ()->level); g_warn_if_fail (g_setenv ("PIPEWIRE_DEBUG", lvl_str, TRUE)); pw_log_set_level (wp_spa_log_get_instance ()->level); pw_log_set (wp_spa_log_get_instance ()); } } /* private, called from core.c when reading the config file */ void wp_log_set_global_level (const gchar *log_level) { gint level; if (level_index_from_string (log_level, &level)) { log_state.global_log_level = level; log_state.global_log_level_flags = level_index_to_full_flags (level); wp_spa_log_get_instance()->level = level_index_to_spa (level); pw_log_set_level (level_index_to_spa (level)); } else { wp_warning ("ignoring invalid log.level in config file: %s", log_level); } } static gint find_topic_log_level (const gchar *log_topic, bool *has_custom_level) { struct log_topic_pattern *pttrn = log_state.patterns; guint len; g_autofree gchar *reverse_topic = NULL; gint log_level = log_state.global_log_level; /* reverse string and length required for pattern match */ len = strlen (log_topic); reverse_topic = g_strreverse (g_strndup (log_topic, len)); while (pttrn && pttrn->spec && !g_pattern_match (pttrn->spec, len, log_topic, reverse_topic)) pttrn++; if (pttrn && pttrn->spec) { if (has_custom_level) *has_custom_level = true; log_level = pttrn->log_level; } else if (has_custom_level) { *has_custom_level = false; } return log_level; } /*! * \brief Initializes a log topic. Internal function, don't use it directly * \ingroup wplog */ void wp_log_topic_init (WpLogTopic *topic) { g_bit_lock (&topic->flags, 30); if ((topic->flags & (1u << 31)) == 0) { bool has_custom_level; gint log_level = find_topic_log_level (topic->topic_name, &has_custom_level); gint flags = topic->flags; flags |= level_index_to_full_flags (log_level); flags |= (1u << 31); /* initialized = true */ if (has_custom_level) flags |= (1u << 29); /* has_custom_level = true */ topic->global_flags = &log_state.global_log_level_flags; topic->flags = flags; } g_bit_unlock (&topic->flags, 30); } typedef struct _WpLogFields WpLogFields; struct _WpLogFields { const gchar *log_topic; const gchar *file; const gchar *line; const gchar *func; const gchar *message; gint log_level; GType object_type; gconstpointer object; }; static void wp_log_fields_init (WpLogFields *lf, const gchar *log_topic, gint log_level, const gchar *file, const gchar *line, const gchar *func, GType object_type, gconstpointer object, const gchar *message) { lf->log_topic = log_topic ? log_topic : "default"; lf->log_level = log_level; lf->file = file; lf->line = line; lf->func = func; lf->object_type = object_type; lf->object = object; lf->message = message ? message : "(null)"; } static void wp_log_fields_init_from_glib (WpLogFields *lf, GLogLevelFlags log_level_flags, const GLogField *fields, gsize n_fields) { wp_log_fields_init (lf, NULL, level_index_from_flags (log_level_flags), NULL, NULL, NULL, 0, NULL, NULL); for (guint i = 0; i < n_fields; i++) { if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0 && fields[i].value) { lf->log_topic = fields[i].value; } else if (g_strcmp0 (fields[i].key, "MESSAGE") == 0 && fields[i].value) { lf->message = fields[i].value; } else if (g_strcmp0 (fields[i].key, "CODE_FILE") == 0) { lf->file = fields[i].value; } else if (g_strcmp0 (fields[i].key, "CODE_LINE") == 0) { lf->line = fields[i].value; } else if (g_strcmp0 (fields[i].key, "CODE_FUNC") == 0) { lf->func = fields[i].value; } } } static void wp_log_fields_write_to_stream (WpLogFields *lf, FILE *s) { gint64 now; time_t now_secs; struct tm now_tm; gchar time_buf[128]; now = g_get_real_time (); now_secs = (time_t) (now / G_USEC_PER_SEC); localtime_r (&now_secs, &now_tm); strftime (time_buf, sizeof (time_buf), "%H:%M:%S", &now_tm); fprintf (s, "%s%s %s.%06d %s%18.18s %s%s:%s:%s:%s %s\n", /* level */ log_state.use_color ? log_level_info[lf->log_level].color : "", log_level_info[lf->log_level].name, /* timestamp */ time_buf, (gint) (now % G_USEC_PER_SEC), /* domain */ log_state.use_color ? DOMAIN_COLOR : "", lf->log_topic, /* file, line, function */ log_state.use_color ? LOCATION_COLOR : "", lf->file, lf->line, lf->func, log_state.use_color ? RESET_COLOR : "", /* message */ lf->message); fflush (s); } static gboolean wp_log_fields_write_to_journal (WpLogFields *lf) { gsize n_fields = 6; GLogField fields[6] = { { "PRIORITY", log_level_info[lf->log_level].priority, -1 }, { "CODE_FILE", lf->file, -1 }, { "CODE_LINE", lf->line, -1 }, { "CODE_FUNC", lf->func, -1 }, { "TOPIC", lf->log_topic, -1 }, { "MESSAGE", lf->message, -1 }, }; /* the log level flags are not used in this function, so we can pass 0 */ return (g_log_writer_journald (0, fields, n_fields, NULL) == G_LOG_WRITER_HANDLED); } static inline gchar * wp_log_fields_format_message (WpLogFields *lf) { g_autofree gchar *extra_message = NULL; g_autofree gchar *extra_object = NULL; const gchar *object_color = ""; if (log_state.use_color) { guint h = g_direct_hash (lf->object) % G_N_ELEMENTS (object_colors); object_color = object_colors[h]; } if (lf->object_type == WP_TYPE_SPA_POD && lf->object && !spa_dbg_str) { spa_dbg_str = g_string_new (lf->message); g_string_append (spa_dbg_str, ":\n"); spa_debug_pod (2, NULL, wp_spa_pod_get_spa_pod (lf->object)); extra_message = g_string_free (spa_dbg_str, FALSE); spa_dbg_str = NULL; } else if (lf->object && g_type_is_a (lf->object_type, WP_TYPE_PROXY) && (wp_object_get_active_features ((WpObject *) lf->object) & WP_PROXY_FEATURE_BOUND)) { extra_object = g_strdup_printf (":%u:", wp_proxy_get_bound_id ((WpProxy *) lf->object)); } return g_strdup_printf ("%s<%s%s%p>%s %s", object_color, lf->object_type != 0 ? g_type_name (lf->object_type) : "", extra_object ? extra_object : ":", lf->object, log_state.use_color ? RESET_COLOR : "", extra_message ? extra_message : lf->message); } static GLogWriterOutput wp_log_fields_log (WpLogFields *lf) { g_autofree gchar *full_message = NULL; /* in the unlikely event that someone messed with stderr... */ if (G_UNLIKELY (!stderr || fileno (stderr) < 0)) return G_LOG_WRITER_UNHANDLED; /* format the message to include the object */ if (lf->object_type) { lf->message = full_message = wp_log_fields_format_message (lf); } /* write complete field information to the journal if we are logging to it */ if (log_state.output_is_journal && wp_log_fields_write_to_journal (lf)) return G_LOG_WRITER_HANDLED; wp_log_fields_write_to_stream (lf, stderr); return G_LOG_WRITER_HANDLED; } /*! * \brief WirePlumber's GLogWriterFunc * * This is installed automatically when you call wp_init() with * WP_INIT_SET_GLIB_LOG set in the flags * \ingroup wplog */ GLogWriterOutput wp_log_writer_default (GLogLevelFlags log_level_flags, const GLogField *fields, gsize n_fields, gpointer user_data) { WpLogFields lf = {0}; g_return_val_if_fail (fields != NULL, G_LOG_WRITER_UNHANDLED); g_return_val_if_fail (n_fields > 0, G_LOG_WRITER_UNHANDLED); wp_log_fields_init_from_glib (&lf, log_level_flags, fields, n_fields); /* check if debug level & topic is enabled */ if (lf.log_level > find_topic_log_level (lf.log_topic, NULL)) return G_LOG_WRITER_HANDLED; return wp_log_fields_log (&lf); } /*! * \brief Used internally by the debug logging macros. Avoid using it directly. * * This assumes that the arguments are correct and that the log_topic is * enabled for the given log_level. No additional checks are performed. * \ingroup wplog */ void wp_log_checked ( const gchar *log_topic, GLogLevelFlags log_level_flags, const gchar *file, const gchar *line, const gchar *func, GType object_type, gconstpointer object, const gchar *message_format, ...) { WpLogFields lf = {0}; g_autofree gchar *message = NULL; va_list args; va_start (args, message_format); message = g_strdup_vprintf (message_format, args); va_end (args); wp_log_fields_init (&lf, log_topic, level_index_from_flags (log_level_flags), file, line, func, object_type, object, message); wp_log_fields_log (&lf); } static G_GNUC_PRINTF (7, 0) void wp_spa_log_logtv (void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) { WpLogFields lf = {0}; gint log_level = level_index_from_spa (level); g_autofree gchar *message = NULL; gchar line_str[11]; sprintf (line_str, "%d", line); message = g_strdup_vprintf (fmt, args); wp_log_fields_init (&lf, topic ? topic->topic : NULL, log_level, file, line_str, func, 0, NULL, message); wp_log_fields_log (&lf); } static G_GNUC_PRINTF (7, 8) void wp_spa_log_logt (void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start (args, fmt); wp_spa_log_logtv (object, level, topic, file, line, func, fmt, args); va_end (args); } static G_GNUC_PRINTF (6, 0) void wp_spa_log_logv (void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, va_list args) { wp_spa_log_logtv (object, level, NULL, file, line, func, fmt, args); } static G_GNUC_PRINTF (6, 7) void wp_spa_log_log (void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start (args, fmt); wp_spa_log_logtv (object, level, NULL, file, line, func, fmt, args); va_end (args); } static void wp_spa_log_topic_init (void *object, struct spa_log_topic *topic) { gint log_level = find_topic_log_level (topic->topic, &topic->has_custom_level); topic->level = level_index_to_spa (log_level); } static const struct spa_log_methods wp_spa_log_methods = { SPA_VERSION_LOG_METHODS, .log = wp_spa_log_log, .logv = wp_spa_log_logv, .logt = wp_spa_log_logt, .logtv = wp_spa_log_logtv, .topic_init = wp_spa_log_topic_init, }; static struct spa_log wp_spa_log = { .iface = { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, { &wp_spa_log_methods, NULL } }, .level = SPA_LOG_LEVEL_WARN, }; /*! * \brief Gets WirePlumber's instance of `spa_log` * \ingroup wplog * \returns WirePlumber's instance of `spa_log`, which can be used to redirect * PipeWire's log messages to the currently installed GLogWriterFunc. * This is installed automatically when you call wp_init() with * WP_INIT_SET_PW_LOG set in the flags */ struct spa_log * wp_spa_log_get_instance (void) { return &wp_spa_log; }