gst: info: Add a GstLogContext API

Add a new API to control logging behavior, particularly for implementing
"log once" functionality and periodic logging. This helps avoid spamming
logs with repetitive messages.

The API provides:
- Static and dynamic context creation
- Configurable message identity calculation
- Periodic reset capability
- Context-aware logging macros
- Element message variants with context support

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6855>
This commit is contained in:
Thibault Saunier 2025-04-09 01:51:49 -04:00 committed by GStreamer Marge Bot
parent 707024f940
commit c796abbf0a
5 changed files with 3369 additions and 15 deletions

File diff suppressed because it is too large Load Diff

View File

@ -506,6 +506,25 @@ G_STMT_START { \
("flow-return", G_TYPE_INT, flow_return, NULL));\
} G_STMT_END
/**
* GST_ELEMENT_FLOW_ERROR_WITH_LOG_CTX:
* @el: the element that generates the error
* @log_ctx: the log context
* @flow_return: the GstFlowReturn leading to that ERROR message
*
* Utility function that elements can use in case they encountered a fatal
* data processing error due to wrong flow processing.
*
* Since: 1.28
*/
#define GST_ELEMENT_FLOW_ERROR_WITH_LOG_CTX(el,log_ctx,flow_return) \
G_STMT_START { \
GST_ELEMENT_ERROR_WITH_DETAILS_AND_LOG_CTX (el, STREAM, FAILED, log_ctx, \
("Internal data stream error."), \
("streaming stopped, reason %s (%d)", gst_flow_get_name (flow_return), flow_return), \
("flow-return", G_TYPE_INT, flow_return, NULL));\
} G_STMT_END
/**
* GST_ELEMENT_ERROR_WITH_DETAILS:
* @el: the element that generates the error
@ -539,6 +558,40 @@ G_STMT_START { \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_ERROR_WITH_DETAILS_AND_LOG_CTX:
* @el: the element that generates the error
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
* @args: optional name, type, value triplets, which will be stored
* in the associated GstStructure. NULL terminator required.
* Must be enclosed within parentheses.
*
* Utility function that elements can use in case they encountered a fatal
* data processing error. The pipeline will post an error message and the
* application will be requested to stop further media processing.
*
* Since: 1.28
*/
#define GST_ELEMENT_ERROR_WITH_DETAILS_AND_LOG_CTX(el,domain,code,log_ctx,text,debug,args) \
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "error: %s", __txt); \
if (__dbg) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "error: %s", __dbg); \
gst_element_message_full_with_details (GST_ELEMENT(el), \
GST_MESSAGE_ERROR, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_ERROR:
* @el: the element that generates the error
@ -567,6 +620,37 @@ G_STMT_START { \
GST_FUNCTION, __LINE__); \
} G_STMT_END
/**
* GST_ELEMENT_ERROR_WITH_LOG_CTX:
* @el: the element that generates the error
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
*
* Utility function that elements can use in case they encountered a fatal
* data processing error. The pipeline will post an error message and the
* application will be requested to stop further media processing.
*
* Since: 1.28
*/
#define GST_ELEMENT_ERROR_WITH_LOG_CTX(el,domain,code,log_ctx,text,debug) \
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "error: %s", __txt); \
if (__dbg) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "error: %s", __dbg); \
gst_element_message_full (GST_ELEMENT(el), \
GST_MESSAGE_ERROR, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__); \
} G_STMT_END
/**
* GST_ELEMENT_WARNING_WITH_DETAILS:
* @el: the element that generates the warning
@ -600,6 +684,71 @@ G_STMT_START { \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_WARNING_WITH_DETAILS_AND_LOG_CTX:
* @el: the element that generates the warning
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
* @args: optional name, type, value triplets, which will be stored
* in the associated GstStructure. NULL terminator required.
* Must be enclosed within parentheses.
*
* Utility function that elements can use in case they encountered a non-fatal
* data processing problem. The pipeline will post a warning message and the
* application will be informed.
*
* Since: 1.28
*/
#define GST_ELEMENT_WARNING_WITH_DETAILS_AND_LOG_CTX(el, domain, code, log_ctx, text, debug, args)\
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "warning: %s", __txt); \
if (__dbg) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "warning: %s", __dbg); \
gst_element_message_full_with_details (GST_ELEMENT(el), \
GST_MESSAGE_WARNING, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_WARNING_WITH_LOG_CTX:
* @el: the element that generates the warning
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
*
* Utility function that elements can use in case they encountered a non-fatal
* data processing problem. The pipeline will post a warning message and the
* application will be informed.
*
* Since: 1.28
*/
#define GST_ELEMENT_WARNING_WITH_LOG_CTX(el, domain, code, log_ctx, text, debug) \
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "warning: %s", __txt); \
if (__dbg) \
GST_CTX_WARNING_OBJECT (log_ctx, el, "warning: %s", __dbg); \
gst_element_message_full (GST_ELEMENT(el), \
GST_MESSAGE_WARNING, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__); \
} G_STMT_END
/**
* GST_ELEMENT_WARNING:
* @el: the element that generates the warning
@ -664,6 +813,43 @@ G_STMT_START { \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_INFO_WITH_DETAILS_AND_LOG_CTX:
* @el: the element that generates the information
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
* @args: optional name, type, value triplets, which will be stored
* in the associated GstStructure. NULL terminator required.
* Must be enclosed within parentheses.
*
* Utility function that elements can use in case they want to inform
* the application of something noteworthy that is not an error.
* The pipeline will post a info message and the
* application will be informed.
* Optional name, type, value triplets may be supplied, and will be stored
* in the associated GstStructure. NULL terminator required.
*
* Since: 1.28
*/
#define GST_ELEMENT_INFO_WITH_DETAILS_AND_LOG_CTX(el, domain, code, log_ctx, text, debug, args) \
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_INFO_OBJECT (log_ctx, el, "info: %s", __txt); \
if (__dbg) \
GST_CTX_INFO_OBJECT (log_ctx, el, "info: %s", __dbg); \
gst_element_message_full_with_details (GST_ELEMENT(el), \
GST_MESSAGE_INFO, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__, GST_ELEMENT_MESSAGE_MAKE_DETAILS(args)); \
} G_STMT_END
/**
* GST_ELEMENT_INFO:
* @el: the element that generates the information
@ -693,6 +879,38 @@ G_STMT_START { \
GST_FUNCTION, __LINE__); \
} G_STMT_END
/**
* GST_ELEMENT_INFO_WITH_LOG_CTX:
* @el: the element that generates the information
* @domain: like CORE, LIBRARY, RESOURCE or STREAM (see [GstGError](gsterror))
* @code: error code defined for that domain (see [GstGError](gsterror))
* @log_ctx: the log context
* @text: the message to display (format string and args enclosed in
parentheses)
* @debug: debugging information for the message (format string and args
enclosed in parentheses)
*
* Utility function that elements can use in case they want to inform
* the application of something noteworthy that is not an error.
* The pipeline will post a info message and the
* application will be informed.
*
* Since: 1.28
*/
#define GST_ELEMENT_INFO_WITH_LOG_CTX(el, domain, code, log_ctx, text, debug) \
G_STMT_START { \
gchar *__txt = _gst_element_error_printf text; \
gchar *__dbg = _gst_element_error_printf debug; \
if (__txt) \
GST_CTX_INFO_OBJECT (log_ctx, el, "info: %s", __txt); \
if (__dbg) \
GST_CTX_INFO_OBJECT (log_ctx, el, "info: %s", __dbg); \
gst_element_message_full (GST_ELEMENT(el), \
GST_MESSAGE_INFO, GST_ ## domain ## _ERROR, \
GST_ ## domain ## _ERROR_ ## code, __txt, __dbg, __FILE__, \
GST_FUNCTION, __LINE__); \
} G_STMT_END
/* the state change mutexes and conds */
/**
* GST_STATE_GET_LOCK:

View File

@ -121,6 +121,19 @@ static char *gst_info_printf_pointer_extension_func (const char *format,
# include <unistd.h> /* getpid on UNIX */
#endif
#ifdef __clang__
#define GST_DISABLE_FORMAT_NONLITERAL_WARNING \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wformat-nonliteral\"")
#define GST_ENABLE_FORMAT_NONLITERAL_WARNING \
_Pragma("clang diagnostic pop")
#else
/* For non-clang compilers, these macros do nothing */
#define GST_DISABLE_FORMAT_NONLITERAL_WARNING
#define GST_ENABLE_FORMAT_NONLITERAL_WARNING
#endif
#ifdef G_OS_WIN32
# define WIN32_LEAN_AND_MEAN /* prevents from including too many things */
# include <windows.h> /* GetStdHandle, windows console */
@ -316,6 +329,30 @@ struct _GstDebugMessage
gchar tmp_id[32];
};
struct _GstLogContext
{
GstLogContextHashFlags hash_flags;
GstLogContextFlags flags;
GstClockTime interval;
GstDebugCategory *category;
GMutex lock;
GHashTable *logged_messages;
GstClockTime last_reset_time;
};
struct _GstLogContextBuilder
{
GstLogContextHashFlags hash_flags;
GstLogContextFlags flags;
GstDebugCategory *category;
GstClockTime interval;
};
/* Global registry for cleanup */
static GHashTable *_log_contexts_registry = NULL;
static GMutex _log_contexts_registry_lock;
/* list of all name/level pairs from --gst-debug and GST_DEBUG */
static GMutex __level_name_mutex;
static GSList *__level_name = NULL;
@ -584,10 +621,139 @@ gst_path_basename (const gchar * file_name)
return file_name;
}
static gchar *
_gst_log_ctx_get_id_literal (GstLogContext * ctx,
const gchar * file, gint line, GObject * object, const gchar * object_id,
const gchar * message)
{
return g_strdup_printf ("%s:%d/%p/%s/%s",
(ctx->hash_flags & GST_LOG_CONTEXT_IGNORE_FILE) ? "" : file,
(ctx->hash_flags & GST_LOG_CONTEXT_USE_LINE_NUMBER) ? line : -1,
(ctx->hash_flags & GST_LOG_CONTEXT_IGNORE_OBJECT) ? 0 : object,
(ctx->hash_flags & GST_LOG_CONTEXT_IGNORE_OBJECT) ? "" : object_id,
(ctx->hash_flags & GST_LOG_CONTEXT_IGNORE_FORMAT) ? "" : message);
}
/* Message hashing based on context flags */
static gchar *
_gst_log_ctx_get_id_valist (GstLogContext * ctx, const gchar * file, gint line,
GObject * object, const gchar * object_id, const gchar * format,
va_list args)
{
gchar *full_message = NULL;
if (ctx->hash_flags & GST_LOG_CONTEXT_USE_STRING_ARGS) {
g_assert (!(ctx->hash_flags & GST_LOG_CONTEXT_IGNORE_FORMAT));
GST_DISABLE_FORMAT_NONLITERAL_WARNING;
full_message = g_strdup_vprintf (format, args);
GST_ENABLE_FORMAT_NONLITERAL_WARNING;
}
gchar *res = _gst_log_ctx_get_id_literal (ctx, file, line, object, object_id,
full_message ? full_message : format);
g_free (full_message);
return res;
}
static gboolean
_gst_log_ctx_check_id (GstLogContext * ctx, gchar * id)
{
/* If throttling is not enabled, always return TRUE to allow logging */
if (!(ctx->flags & GST_LOG_CONTEXT_FLAG_THROTTLE))
return TRUE;
g_mutex_lock (&ctx->lock);
gboolean res = g_hash_table_add (ctx->logged_messages, id);
g_mutex_unlock (&ctx->lock);
return res;
}
static void
gst_debug_log_full_valist (GstDebugCategory * category, GstDebugLevel level,
const gchar * file, const gchar * function, gint line,
GObject * object, const gchar * id, const gchar * format, va_list args)
_gst_log_context_reset_unlocked (GstLogContext * ctx)
{
if (ctx->logged_messages) {
g_hash_table_remove_all (ctx->logged_messages);
}
ctx->last_reset_time = gst_util_get_timestamp ();
}
static gboolean
_gst_log_ctx_check_periodic_reset (GstLogContext * ctx)
{
gboolean ret = TRUE;
g_mutex_lock (&ctx->lock);
if (ctx->interval == 0)
goto done;
if (!GST_CLOCK_TIME_IS_VALID (ctx->last_reset_time)) {
ctx->last_reset_time = gst_util_get_timestamp ();
goto done;
}
GstClockTime now = gst_util_get_timestamp ();
gint64 elapsed = GST_CLOCK_DIFF (ctx->last_reset_time, now);
if (elapsed >= ctx->interval) {
_gst_log_context_reset_unlocked (ctx);
goto done;
}
ret = FALSE;
done:
g_mutex_unlock (&ctx->lock);
return ret;
}
static gboolean
_gst_log_ctx_check_id_literal (GstLogContext * ctx,
const gchar * file, gint line, GObject * object, const gchar * id,
const gchar * message)
{
if (!ctx)
return TRUE;
/* Check for periodic reset if needed */
if (!_gst_log_ctx_check_periodic_reset (ctx))
return FALSE;
return _gst_log_ctx_check_id (ctx, _gst_log_ctx_get_id_literal (ctx, file,
line, object, id, message));
}
static gboolean
_gst_log_ctx_check_id_valist (GstLogContext * ctx,
const gchar * file, gint line,
GObject * object, const gchar * object_id, const gchar * format,
va_list args)
{
if (!ctx)
return TRUE;
if (!_gst_log_ctx_check_periodic_reset (ctx)) {
return FALSE;
}
return _gst_log_ctx_check_id (ctx, _gst_log_ctx_get_id_valist (ctx, file,
line, object, object_id, format, args));
}
static void
gst_debug_log_full_valist (GstDebugCategory * category, GstLogContext * ctx,
GstDebugLevel level, const gchar * file, const gchar * function, gint line,
GObject * object, const gchar * object_id, const gchar * format,
va_list args)
{
GstDebugMessage message;
LogFuncEntry *entry;
@ -598,18 +764,31 @@ gst_debug_log_full_valist (GstDebugCategory * category, GstDebugLevel level,
if (level > gst_debug_category_get_threshold (category))
return;
if (ctx) {
va_list arguments;
G_VA_COPY (arguments, args);
if (!_gst_log_ctx_check_id_valist (ctx, file, line, object, object_id,
format, arguments)) {
va_end (arguments);
return;
}
va_end (arguments);
}
g_return_if_fail (file != NULL);
g_return_if_fail (function != NULL);
g_return_if_fail (format != NULL);
#ifdef GST_ENABLE_EXTRA_CHECKS
g_return_if_fail (id != NULL || object == NULL || G_IS_OBJECT (object));
g_return_if_fail (object_id != NULL || object == NULL
|| G_IS_OBJECT (object));
#endif
message.message = NULL;
message.format = format;
message.object = object;
message.object_id = (gchar *) id;
message.object_id = (gchar *) object_id;
message.free_object_id = FALSE;
G_VA_COPY (message.arguments, args);
@ -653,8 +832,8 @@ gst_debug_log_valist (GstDebugCategory * category, GstDebugLevel level,
g_warn_if_fail (object == NULL || G_IS_OBJECT (object));
#endif
gst_debug_log_full_valist (category, level, file, function, line, object,
NULL, format, args);
gst_debug_log_full_valist (category, NULL, level, file, function, line,
object, NULL, format, args);
}
/**
@ -678,13 +857,13 @@ gst_debug_log_id_valist (GstDebugCategory * category, GstDebugLevel level,
const gchar * file, const gchar * function, gint line,
const gchar * id, const gchar * format, va_list args)
{
gst_debug_log_full_valist (category, level, file, function, line, NULL, id,
format, args);
gst_debug_log_full_valist (category, NULL, level, file, function, line, NULL,
id, format, args);
}
static void
gst_debug_log_literal_full (GstDebugCategory * category, GstDebugLevel level,
const gchar * file, const gchar * function, gint line,
gst_debug_log_literal_full (GstDebugCategory * category, GstLogContext * ctx,
GstDebugLevel level, const gchar * file, const gchar * function, gint line,
GObject * object, const gchar * id, const gchar * message_string)
{
GstDebugMessage message;
@ -696,6 +875,12 @@ gst_debug_log_literal_full (GstDebugCategory * category, GstDebugLevel level,
if (level > gst_debug_category_get_threshold (category))
return;
if (ctx) {
if (!_gst_log_ctx_check_id_literal (ctx, file, line, object, id,
message_string))
return;
}
#ifdef GST_ENABLE_EXTRA_CHECKS
g_return_if_fail (id != NULL || object == NULL || G_IS_OBJECT (object));
#endif
@ -747,8 +932,8 @@ gst_debug_log_literal (GstDebugCategory * category, GstDebugLevel level,
g_warn_if_fail (object == NULL || G_IS_OBJECT (object));
#endif
gst_debug_log_literal_full (category, level, file, function, line, object,
NULL, message_string);
gst_debug_log_literal_full (category, NULL, level, file, function, line,
object, NULL, message_string);
}
/**
@ -771,8 +956,8 @@ gst_debug_log_id_literal (GstDebugCategory * category, GstDebugLevel level,
const gchar * file, const gchar * function, gint line,
const gchar * id, const gchar * message_string)
{
gst_debug_log_literal_full (category, level, file, function, line, NULL, id,
message_string);
gst_debug_log_literal_full (category, NULL, level, file, function, line, NULL,
id, message_string);
}
/**
@ -2532,9 +2717,421 @@ clear_level_names (void)
g_mutex_unlock (&__level_name_mutex);
}
/* Logging context implementation */
static void
_gst_log_context_free (GstLogContext * ctx)
{
if (!ctx)
return;
g_mutex_lock (&ctx->lock);
if (ctx->logged_messages) {
g_hash_table_remove_all (ctx->logged_messages);
g_hash_table_unref (ctx->logged_messages);
}
g_mutex_unlock (&ctx->lock);
g_free (ctx);
}
static void
_register_log_context (GstLogContext * ctx)
{
g_mutex_lock (&_log_contexts_registry_lock);
if (!_log_contexts_registry) {
_log_contexts_registry =
g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
(GDestroyNotify) _gst_log_context_free);
}
g_hash_table_add (_log_contexts_registry, ctx);
g_mutex_unlock (&_log_contexts_registry_lock);
}
/**
* gst_log_context_builder_new: (skip):
* @category: the debug category to use
* @flags: the flags to use for the log context
*
* Creates a new builder for configuring a #GstLogContext with the specified
* debug category and flags.
*
* Returns: (transfer full): a new #GstLogContextBuilder
*
* Since: 1.28
*/
GstLogContextBuilder *
gst_log_context_builder_new (GstDebugCategory * category,
GstLogContextFlags flags)
{
GstLogContextBuilder *builder;
g_return_val_if_fail (category, NULL);
builder = g_new0 (GstLogContextBuilder, 1);
builder->hash_flags = GST_LOG_CONTEXT_DEFAULT;
builder->flags = flags;
builder->interval = 0;
builder->category = category;
return builder;
}
/**
* gst_log_context_builder_set_category: (skip):
* @builder: (transfer full): a #GstLogContextBuilder
* @category: the debug category to use, or NULL for no specific category
*
* Sets the debug category for the log context being built.
*
* Returns: (transfer full): the same #GstLogContextBuilder
*
* Since: 1.28
*/
GstLogContextBuilder *
gst_log_context_builder_set_category (GstLogContextBuilder * builder,
GstDebugCategory * category)
{
g_return_val_if_fail (builder != NULL, NULL);
builder->category = category;
return builder;
}
/**
* gst_log_context_builder_set_hash_flags: (skip):
* @builder: (transfer full): a #GstLogContextBuilder
* @flags: the hash flags to use for the log context
*
* Sets the hash flags for the log context being built. These determine how
* message hashes are calculated for determining duplicates.
*
* Returns: (transfer full): the same #GstLogContextBuilder
* Since: 1.28
*/
GstLogContextBuilder *
gst_log_context_builder_set_hash_flags (GstLogContextBuilder * builder,
GstLogContextHashFlags flags)
{
g_return_val_if_fail (builder != NULL, NULL);
builder->hash_flags = flags;
return builder;
}
/**
* gst_log_context_builder_set_interval: (skip):
* @builder: (transfer full): a #GstLogContextBuilder
* @interval: the interval in nanoseconds for automatic reset
*
* Sets the automatic reset interval for the log context being built.
* If @interval is 0, no automatic reset will occur.
*
* Returns: (transfer full): the same #GstLogContextBuilder
*
* Since: 1.28
*/
GstLogContextBuilder *
gst_log_context_builder_set_interval (GstLogContextBuilder * builder,
GstClockTime interval)
{
g_return_val_if_fail (builder != NULL, NULL);
builder->interval = interval;
return builder;
}
/**
* gst_log_context_builder_build: (skip):
* @builder: (transfer full): a #GstLogContextBuilder
*
* Builds a #GstLogContext from the builder configuration.
* The builder is consumed by this function and should not be used afterward.
*
* Returns: (transfer full): a new #GstLogContext
*
* Since: 1.28
*/
GstLogContext *
gst_log_context_builder_build (GstLogContextBuilder * builder)
{
/* Create a new context */
GstLogContext *ctx;
ctx = g_new0 (GstLogContext, 1);
ctx->hash_flags = builder->hash_flags;
ctx->flags = builder->flags;
ctx->interval = builder->interval;
ctx->logged_messages =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
ctx->last_reset_time = GST_CLOCK_TIME_NONE;
ctx->category = builder->category;
/* Register for cleanup */
_register_log_context (ctx);
g_free (builder);
return ctx;
}
/**
* gst_log_context_get_category: (skip):
* @context: a #GstLogContext
*
* Get the #GstDebugCategory associated with this log context.
*
* Returns: the #GstDebugCategory to which the context is bound
*
* Since: 1.28
*/
GstDebugCategory *
gst_log_context_get_category (GstLogContext * context)
{
g_return_val_if_fail (context != NULL, NULL);
return context->category;
}
/**
* gst_log_context_reset:
* @ctx: a #GstLogContext
*
* Resets the logging context, clearing all tracked messages.
*
* Since: 1.28
*/
void
gst_log_context_reset (GstLogContext * ctx)
{
g_return_if_fail (ctx != NULL);
g_mutex_lock (&ctx->lock);
_gst_log_context_reset_unlocked (ctx);
g_mutex_unlock (&ctx->lock);
}
/**
* gst_log_context_free:
* @ctx: a #GstLogContext
*
* Free the logging context, clearing all tracked messages.
*
* Since: 1.28
*/
void
gst_log_context_free (GstLogContext * ctx)
{
g_return_if_fail (ctx != NULL);
g_mutex_lock (&_log_contexts_registry_lock);
if (!_log_contexts_registry) {
g_warning ("Trying to free log context %p while was not registered", ctx);
} else {
if (!g_hash_table_remove (_log_contexts_registry, ctx)) {
g_warning ("Trying to free log context %p while was not registered", ctx);
}
}
g_mutex_unlock (&_log_contexts_registry_lock);
}
/**
* gst_debug_log_with_context:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @object: (nullable): the object this message relates to,
* or %NULL if none
* @format: a printf style format string
* @...: optional arguments for the format string
*
* Logs a message with the specified context. If the context has already seen this
* message based on its flags configuration, the message will not be logged.
*
* Since: 1.28
*/
void
gst_debug_log_with_context (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function,
gint line, GObject * object, const gchar * format, ...)
{
va_list args;
va_start (args, format);
gst_debug_log_full_valist (ctx->category, ctx, level, file, function, line,
object, NULL, format, args);
va_end (args);
}
/**
* gst_debug_log_with_context_valist:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @object: (nullable): the object this message relates to,
* or %NULL if none
* @format: a printf style format string
* @args: optional arguments for the format string
*
* Logs a message with the specified context using a va_list. If the context has
* already seen this message based on its flags configuration, the message will
* not be logged.
*
* Since: 1.28
*/
void
gst_debug_log_with_context_valist (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function,
gint line, GObject * object, const gchar * format, va_list args)
{
gst_debug_log_full_valist (ctx->category, ctx, level, file, function, line,
object, NULL, format, args);
}
/**
* gst_debug_log_literal_with_context:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @object: (nullable): the object this message relates to,
* or %NULL if none
* @message: message string
*
* Logs a literal message with the specified context. Depending on the context
* state, the message may not be logged at all.
*
* Since: 1.28
*/
void
gst_debug_log_literal_with_context (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function, gint line, GObject * object, const gchar * message)
{
gst_debug_log_literal_full (ctx->category, ctx, level, file, function, line,
object, NULL, message);
}
/**
* gst_debug_log_id_with_context:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @id: (nullable): the contextual ID of the message
* @format: a printf style format string
* @...: optional arguments for the format string
*
* Logs a message with the specified context and ID. If the context has already
* seen this message based on its flags configuration, the message will not be
* logged.
*
* Since: 1.28
*/
void
gst_debug_log_id_with_context (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function,
gint line, const gchar * id, const gchar * format, ...)
{
va_list args;
va_start (args, format);
gst_debug_log_full_valist (ctx->category, ctx, level, file, function, line,
NULL, id, format, args);
va_end (args);
}
/**
* gst_debug_log_id_with_context_valist:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @id: (nullable): the contextual ID of the message
* @format: a printf style format string
* @args: optional arguments for the format string
*
* Logs a message with the specified context and ID. If the context has already
* seen this message based on its flags configuration, the message will not be
* logged.
*
* Since: 1.28
*/
void
gst_debug_log_id_with_context_valist (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function,
gint line, const gchar * id, const gchar * format, va_list args)
{
gst_debug_log_full_valist (ctx->category, ctx, level, file, function, line,
NULL, id, format, args);
}
/**
* gst_debug_log_id_literal_with_context:
* @ctx: a #GstLogContext
* @level: level of the message
* @file: the file that emitted the message, usually the __FILE__ identifier
* @function: the function that emitted the message
* @line: the line that emitted the message, usually the __LINE__ identifier
* @id: (nullable): the contextual ID of the message
* @message: message string
*
* Logs a message with the specified context and ID. If the context has already
* seen this message based on its flags configuration, the message will not be
* logged.
*
* Since: 1.28
*/
void
gst_debug_log_id_literal_with_context (GstLogContext * ctx,
GstDebugLevel level,
const gchar * file,
const gchar * function, gint line, const gchar * id, const gchar * message)
{
gst_debug_log_literal_full (ctx->category, ctx, level, file, function, line,
NULL, id, message);
}
static void
_gst_log_context_cleanup (void)
{
g_mutex_lock (&_log_contexts_registry_lock);
if (_log_contexts_registry) {
g_hash_table_unref (_log_contexts_registry);
_log_contexts_registry = NULL;
}
g_mutex_unlock (&_log_contexts_registry_lock);
}
void
_priv_gst_debug_cleanup (void)
{
/* Clean up our log contexts */
_gst_log_context_cleanup ();
g_mutex_lock (&__dbg_functions_mutex);
if (__gst_function_pointers) {

File diff suppressed because it is too large Load Diff

View File

@ -558,6 +558,234 @@ GST_START_TEST (info_set_and_reset_string)
GST_END_TEST;
static gint context_log_count = 0;
static void
context_log_counter_func (GstDebugCategory * category,
GstDebugLevel level, const gchar * file, const gchar * function,
gint line, GObject * object, GstDebugMessage * message, gpointer user_data)
{
/* Track the number of messages received */
context_log_count++;
/* Let the default log function handle it for output if needed */
if (g_getenv ("GST_DEBUG")) {
gst_debug_log_default (category, level, file, function, line, object,
message, NULL);
}
}
GST_START_TEST (info_context_log)
{
GstDebugCategory *cat = NULL;
GstLogContext *ctx = NULL;
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function (context_log_counter_func, NULL, NULL);
gst_debug_set_default_threshold (GST_LEVEL_DEBUG);
GST_DEBUG_CATEGORY_INIT (cat, "contextcat", 0, "Log context test category");
GST_LOG_CONTEXT_INIT (ctx, GST_LOG_CONTEXT_FLAG_THROTTLE);
context_log_count = 0;
/* Test all the different logging macros with context and verify the log level is respected */
GST_CTX_ERROR (ctx, "Error message with context");
GST_CTX_WARNING (ctx, "Warning message with context");
GST_CTX_FIXME (ctx, "Fixme message with context");
GST_CTX_INFO (ctx, "Info message with context");
GST_CTX_DEBUG (ctx, "Debug message with context");
GST_CTX_LOG (ctx, "Log message with context");
GST_CTX_TRACE (ctx, "Trace message with context");
/* Since trace and log are above our threshold, it won't be counted */
fail_unless_equals_int (context_log_count, 5);
gst_debug_set_default_threshold (GST_LEVEL_NONE);
gst_debug_add_log_function (gst_debug_log_default, NULL, NULL);
gst_debug_remove_log_function (context_log_counter_func);
gst_log_context_free (ctx);
}
GST_END_TEST;
GST_START_TEST (info_context_log_once)
{
GstDebugCategory *cat = NULL;
GstLogContext *ctx = NULL;
/* Set up our counting log function */
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function (context_log_counter_func, NULL, NULL);
/* Enable debug logging to ensure our logs get processed */
gst_debug_set_default_threshold (GST_LEVEL_DEBUG);
GST_DEBUG_CATEGORY_INIT (cat, "contextcat", 0, "Log context test category");
GST_LOG_CONTEXT_INIT (ctx, GST_LOG_CONTEXT_FLAG_THROTTLE);
context_log_count = 0;
/* Log the same message multiple times */
GST_CTX_DEBUG (ctx, "This message should only appear once");
GST_CTX_DEBUG (ctx, "This message should only appear once");
GST_CTX_DEBUG (ctx, "This message should only appear once");
/* Different messages should appear */
GST_CTX_DEBUG (ctx, "A different message");
GST_CTX_DEBUG (ctx, "Another different message");
/* Should see 3 messages total */
fail_unless_equals_int (context_log_count, 3);
/* Clean up */
gst_debug_set_default_threshold (GST_LEVEL_NONE);
gst_debug_add_log_function (gst_debug_log_default, NULL, NULL);
gst_debug_remove_log_function (context_log_counter_func);
gst_log_context_free (ctx);
}
GST_END_TEST;
GST_START_TEST (info_context_log_periodic)
{
GstDebugCategory *cat = NULL;
GstLogContext *ctx = NULL;
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function (context_log_counter_func, NULL, NULL);
gst_debug_set_default_threshold (GST_LEVEL_DEBUG);
GST_DEBUG_CATEGORY_INIT (cat, "contextcat", 0, "Log context test category");
GST_LOG_CONTEXT_INIT (ctx, GST_LOG_CONTEXT_FLAG_THROTTLE, {
GST_LOG_CONTEXT_BUILDER_SET_INTERVAL (10 * GST_MSECOND);
}
);
/* Reset the counter */
context_log_count = 0;
GST_CTX_DEBUG (ctx, "This message should appear the first time");
GST_CTX_DEBUG (ctx, "This message should appear the first time");
GST_CTX_DEBUG (ctx, "This message should appear the first time");
/* Should see the message only once, unless it took more than 10ms to print 3
* debug message ... */
fail_unless_equals_int (context_log_count, 1);
/* Sleep to ensure the reset interval passes */
g_usleep (20000); /* 20ms */
/* Log the same message again - it should appear after the interval */
GST_CTX_DEBUG (ctx, "This message should appear the first time");
/* Should see both messages now */
fail_unless_equals_int (context_log_count, 2);
/* Clean up */
gst_debug_set_default_threshold (GST_LEVEL_NONE);
gst_debug_add_log_function (gst_debug_log_default, NULL, NULL);
gst_debug_remove_log_function (context_log_counter_func);
gst_log_context_free (ctx);
}
GST_END_TEST;
/* Test the static context macros */
GST_LOG_CONTEXT_STATIC_DEFINE (static_ctx, GST_LOG_CONTEXT_FLAG_THROTTLE);
#define STATIC_CTX GST_LOG_CONTEXT_LAZY_INIT(static_ctx)
GST_LOG_CONTEXT_STATIC_DEFINE (static_periodic_ctx,
GST_LOG_CONTEXT_FLAG_THROTTLE, GST_LOG_CONTEXT_BUILDER_SET_INTERVAL (1);
);
#define STATIC_PERIODIC_CTX GST_LOG_CONTEXT_LAZY_INIT(static_periodic_ctx)
GST_START_TEST (info_context_log_static)
{
GstDebugCategory *cat = NULL;
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function (context_log_counter_func, NULL, NULL);
gst_debug_set_default_threshold (GST_LEVEL_DEBUG);
GST_DEBUG_CATEGORY_INIT (cat, "contextcat", 0, "Log context test category");
context_log_count = 0;
GST_CTX_DEBUG (STATIC_CTX, "Static context message");
GST_CTX_DEBUG (STATIC_CTX, "Static context default category message");
fail_unless_equals_int (context_log_count, 2);
context_log_count = 0;
GST_CTX_DEBUG (STATIC_PERIODIC_CTX, "Static periodic context message");
fail_unless_equals_int (context_log_count, 1);
/* Sleep to ensure the reset interval passes */
g_usleep (2000); /* 2ms */
GST_CTX_DEBUG (STATIC_PERIODIC_CTX, "Static periodic context message");
fail_unless_equals_int (context_log_count, 2);
gst_debug_set_default_threshold (GST_LEVEL_NONE);
gst_debug_add_log_function (gst_debug_log_default, NULL, NULL);
gst_debug_remove_log_function (context_log_counter_func);
}
GST_END_TEST;
GST_START_TEST (info_context_log_flags)
{
GstDebugCategory *cat = NULL;
GstElement *element;
GstLogContext *ctx1 = NULL, *ctx2 = NULL, *ctx3 = NULL;
/* Set up our counting log function */
gst_debug_remove_log_function (gst_debug_log_default);
gst_debug_add_log_function (context_log_counter_func, NULL, NULL);
/* Enable debug logging to ensure our logs get processed */
gst_debug_set_default_threshold (GST_LEVEL_DEBUG);
GST_DEBUG_CATEGORY_INIT (cat, "contextcat", 0, "Log context test category");
/* Create an element for object-based logging */
element = gst_element_factory_make ("identity", NULL);
fail_unless (element != NULL);
/* Test DEFAULT context */
GST_LOG_CONTEXT_INIT (ctx1, GST_LOG_CONTEXT_FLAG_THROTTLE);
context_log_count = 0;
GST_CTX_DEBUG_OBJECT (ctx1, element, "Test message with default context");
GST_CTX_DEBUG_OBJECT (ctx1, NULL, "Test message with default context");
/* Should see both messages since objects are different */
fail_unless_equals_int (context_log_count, 2);
/* Test IGNORE_OBJECT context */
GST_LOG_CONTEXT_INIT (ctx2, GST_LOG_CONTEXT_FLAG_THROTTLE, {
GST_LOG_CONTEXT_BUILDER_SET_HASH_FLAGS (GST_LOG_CONTEXT_IGNORE_OBJECT);
}
);
context_log_count = 0;
GST_CTX_DEBUG_OBJECT (ctx2, element,
"Test message with ignore object context");
GST_CTX_DEBUG_OBJECT (ctx2, NULL, "Test message with ignore object context");
/* Should see only one message since objects are ignored in hash calculation */
fail_unless_equals_int (context_log_count, 1);
/* Test USE_LINE_NUMBER context */
GST_LOG_CONTEXT_INIT (ctx3, GST_LOG_CONTEXT_FLAG_THROTTLE, {
GST_LOG_CONTEXT_BUILDER_SET_HASH_FLAGS
(GST_LOG_CONTEXT_USE_LINE_NUMBER);
}
);
context_log_count = 0;
GST_CTX_DEBUG (ctx3, "Test message with line context");
GST_CTX_DEBUG (ctx3, "Test message with line context");
/* Should see the 2 messages since line numbers are taken into account */
fail_unless_equals_int (context_log_count, 2);
gst_object_unref (element);
gst_debug_set_default_threshold (GST_LEVEL_NONE);
gst_debug_add_log_function (gst_debug_log_default, NULL, NULL);
gst_debug_remove_log_function (context_log_counter_func);
gst_log_context_free (ctx1);
gst_log_context_free (ctx2);
gst_log_context_free (ctx3);
}
GST_END_TEST;
static Suite *
gst_info_suite (void)
{
@ -581,6 +809,12 @@ gst_info_suite (void)
tcase_add_test (tc_chain, info_set_and_unset_multiple);
tcase_add_test (tc_chain, info_post_gst_init_category_registration);
tcase_add_test (tc_chain, info_set_and_reset_string);
tcase_add_test (tc_chain, info_context_log);
tcase_add_test (tc_chain, info_context_log_once);
tcase_add_test (tc_chain, info_context_log_periodic);
tcase_add_test (tc_chain, info_context_log_static);
tcase_add_test (tc_chain, info_context_log_flags);
#endif
return s;