gstreamer/validate/gst/validate/gst-validate-reporter.c
Thibault Saunier d86fa2c44a validate: Enhance error reporting for errors in struct files
Get a sense of files and line numbers in the parsed GstStructure
and take that information when reporting GstValidateAction errors
by letting the user know where the action comes from in the messages.

And accept non-literal string in printing formats.
2020-02-25 16:22:10 -03:00

524 lines
16 KiB
C

/* GStreamer
*
* Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com>
*
* gst-validate-reporter.c
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:gst-validate-reporter
* @title: GstValidateReporter
* @short_description: A #GInterface that allows #GObject to be used as originator of
* issues in the GstValidate reporting system
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <math.h>
#include "gst-validate-internal.h"
#include "gst-validate-reporter.h"
#include "gst-validate-report.h"
#define REPORTER_PRIVATE "gst-validate-reporter-private"
typedef struct _GstValidateReporterPrivate
{
GWeakRef runner;
GHashTable *reports;
char *name;
guint log_handler_id;
GMutex reports_lock;
} GstValidateReporterPrivate;
static GstValidateReporterPrivate *g_log_handler = NULL;
G_DEFINE_INTERFACE (GstValidateReporter, gst_validate_reporter, G_TYPE_OBJECT);
static void
gst_validate_reporter_default_init (GstValidateReporterInterface * iface)
{
g_object_interface_install_property (iface,
g_param_spec_object ("validate-runner", "Validate Runner",
"The Validate runner to report errors to",
GST_TYPE_VALIDATE_RUNNER,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
}
static void
_free_priv (GstValidateReporterPrivate * priv)
{
if (g_log_handler == priv) {
g_log_set_default_handler (g_log_default_handler, NULL);
g_log_handler = NULL;
}
g_hash_table_unref (priv->reports);
g_free (priv->name);
g_mutex_clear (&priv->reports_lock);
g_weak_ref_clear (&priv->runner);
g_slice_free (GstValidateReporterPrivate, priv);
}
static GstValidateReporterPrivate *
gst_validate_reporter_get_priv (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv;
priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE);
if (priv == NULL) {
priv = g_slice_new0 (GstValidateReporterPrivate);
priv->reports = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify) gst_validate_report_unref);
g_mutex_init (&priv->reports_lock);
g_object_set_data_full (G_OBJECT (reporter), REPORTER_PRIVATE, priv,
(GDestroyNotify) _free_priv);
}
return priv;
}
#define GST_VALIDATE_REPORTER_REPORTS_LOCK(r) \
G_STMT_START { \
(g_mutex_lock (&gst_validate_reporter_get_priv(GST_VALIDATE_REPORTER_CAST(r))->reports_lock)); \
} G_STMT_END
#define GST_VALIDATE_REPORTER_REPORTS_UNLOCK(r) \
G_STMT_START { \
(g_mutex_unlock (&gst_validate_reporter_get_priv(GST_VALIDATE_REPORTER_CAST(r))->reports_lock)); \
} G_STMT_END
static GstValidateInterceptionReturn
gst_validate_reporter_intercept_report (GstValidateReporter * reporter,
GstValidateReport * report)
{
GstValidateInterceptionReturn ret = GST_VALIDATE_REPORTER_REPORT;
GstValidateReporterInterface *iface =
GST_VALIDATE_REPORTER_GET_INTERFACE (reporter);
if (iface->intercept_report) {
ret = iface->intercept_report (reporter, report);
}
return ret;
}
GstValidateReportingDetails
gst_validate_reporter_get_reporting_level (GstValidateReporter * reporter)
{
GstValidateReportingDetails ret = GST_VALIDATE_SHOW_UNKNOWN;
GstValidateReporterInterface *iface =
GST_VALIDATE_REPORTER_GET_INTERFACE (reporter);
if (iface->get_reporting_level) {
ret = iface->get_reporting_level (reporter);
}
return ret;
}
/**
* gst_validate_reporter_get_pipeline:
* @reporter: The reporter to get the pipeline from
*
* Returns: (transfer full) (allow-none): The #GstPipeline
*/
GstPipeline *
gst_validate_reporter_get_pipeline (GstValidateReporter * reporter)
{
GstValidateReporterInterface *iface =
GST_VALIDATE_REPORTER_GET_INTERFACE (reporter);
if (iface->get_pipeline)
return iface->get_pipeline (reporter);
return NULL;
}
GstValidateReport *
gst_validate_reporter_get_report (GstValidateReporter * reporter,
GstValidateIssueId issue_id)
{
GstValidateReport *report;
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter);
report = g_hash_table_lookup (priv->reports, (gconstpointer) issue_id);
GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter);
return report;
}
void
gst_validate_report_valist (GstValidateReporter * reporter,
GstValidateIssueId issue_id, const gchar * format, va_list var_args)
{
GstValidateReport *report, *prev_report;
gchar *message, *combo;
va_list vacopy;
GstValidateIssue *issue;
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
GstValidateInterceptionReturn int_ret;
GstValidateRunner *runner = NULL;
issue = gst_validate_issue_from_id (issue_id);
g_return_if_fail (issue != NULL);
g_return_if_fail (GST_IS_VALIDATE_REPORTER (reporter));
G_VA_COPY (vacopy, var_args);
message = g_strdup_vprintf (format, vacopy);
report = gst_validate_report_new (issue, reporter, message);
#ifndef GST_DISABLE_GST_DEBUG
combo =
g_strdup_printf ("<%s> %" GST_VALIDATE_ISSUE_FORMAT " : %s", priv->name,
GST_VALIDATE_ISSUE_ARGS (issue), format);
G_VA_COPY (vacopy, var_args);
if (report->level == GST_VALIDATE_REPORT_LEVEL_CRITICAL) {
gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_ERROR, __FILE__,
GST_FUNCTION, __LINE__, NULL, combo, vacopy);
} else if (report->level == GST_VALIDATE_REPORT_LEVEL_WARNING)
gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING, __FILE__,
GST_FUNCTION, __LINE__, NULL, combo, vacopy);
else if (report->level == GST_VALIDATE_REPORT_LEVEL_ISSUE)
gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_LOG, __FILE__,
GST_FUNCTION, __LINE__, (GObject *) NULL, combo, vacopy);
else
gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_DEBUG, __FILE__,
GST_FUNCTION, __LINE__, NULL, combo, vacopy);
g_free (combo);
#endif
va_end (vacopy);
int_ret = gst_validate_reporter_intercept_report (reporter, report);
if (int_ret == GST_VALIDATE_REPORTER_DROP) {
gst_validate_report_unref (report);
goto done;
}
prev_report = g_hash_table_lookup (priv->reports, (gconstpointer) issue_id);
runner = gst_validate_reporter_get_runner (reporter);
if (prev_report) {
GstValidateReportingDetails reporter_level =
gst_validate_reporter_get_reporting_level (reporter);
GstValidateReportingDetails runner_level = GST_VALIDATE_SHOW_UNKNOWN;
if (runner)
runner_level = gst_validate_runner_get_default_reporting_level (runner);
if ((reporter_level == GST_VALIDATE_SHOW_ALL ||
(runner_level == GST_VALIDATE_SHOW_ALL &&
reporter_level == GST_VALIDATE_SHOW_UNKNOWN)) ||
(issue->flags & GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS)) {
gst_validate_report_add_repeated_report (prev_report, report);
}
gst_validate_report_unref (report);
goto done;
}
GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter);
g_hash_table_insert (priv->reports, (gpointer) issue_id, report);
GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter);
if (runner && int_ret == GST_VALIDATE_REPORTER_REPORT) {
gst_validate_runner_add_report (runner, report);
}
if (gst_validate_report_check_abort (report)) {
if (runner)
gst_validate_runner_printf (runner);
g_error ("Fatal report received: %" GST_VALIDATE_ERROR_REPORT_PRINT_FORMAT,
GST_VALIDATE_REPORT_PRINT_ARGS (report));
}
done:
if (runner)
gst_object_unref (runner);
g_free (message);
}
static void
gst_validate_default_log_hanlder (const gchar * log_domain,
GLogLevelFlags log_level, const gchar * message, gpointer user_data)
{
gchar *trace = gst_debug_get_stack_trace (GST_STACK_TRACE_SHOW_FULL);
if (trace) {
g_print ("\nStack trace:\n%s\n", trace);
g_free (trace);
}
g_log_default_handler (log_domain, log_level, message, user_data);
}
static void
gst_validate_reporter_destroyed (gpointer udata, GObject * freed_reporter)
{
g_log_set_handler ("GStreamer",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL);
g_log_set_handler ("GLib",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL);
g_log_set_handler ("GLib-GObject",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_default_log_hanlder, NULL);
}
static void
gst_validate_reporter_g_log_func (const gchar * log_domain,
GLogLevelFlags log_level, const gchar * message,
GstValidateReporter * reporter)
{
if (log_level & G_LOG_LEVEL_ERROR)
gst_validate_default_log_hanlder (log_domain, log_level, message, reporter);
else if (log_level & G_LOG_LEVEL_CRITICAL)
GST_VALIDATE_REPORT (reporter, G_LOG_CRITICAL, "%s", message);
else if (log_level & G_LOG_LEVEL_WARNING)
GST_VALIDATE_REPORT (reporter, G_LOG_WARNING, "%s", message);
else
GST_VALIDATE_REPORT (reporter, G_LOG_ISSUE, "%s", message);
}
/**
* gst_validate_report:
* @reporter: The source of the new report
* @issue_id: The #GstValidateIssueId of the issue
* @format: The format of the message describing the issue in a printf
* format followed by the parameters.
* @...: Substitution arguments for @format
*
* Reports a new issue in the GstValidate reporting system.
*
* You can also use #GST_VALIDATE_REPORT instead.
*/
void
gst_validate_report (GstValidateReporter * reporter,
GstValidateIssueId issue_id, const gchar * format, ...)
{
va_list var_args;
va_start (var_args, format);
gst_validate_report_valist (reporter, issue_id, format, var_args);
va_end (var_args);
}
/**
* gst_validate_report_action:
* @reporter: The source of the new report
* @action: The action reporting the issue
* @issue_id: The #GstValidateIssueId of the issue
* @format: The format of the message describing the issue in a printf
* format followed by the parameters.
* @...: Substitution arguments for @format
*
* Reports a new issue in the GstValidate reporting system specifying @action
* as failling action .
*
* You can also use #GST_VALIDATE_REPORT instead.
*/
void
gst_validate_report_action (GstValidateReporter * reporter,
GstValidateAction * action, GstValidateIssueId issue_id,
const gchar * format, ...)
{
va_list var_args;
gchar *f = g_strdup_printf ("\n> %s:%d\n> %d | %s\n> %*c|\n",
GST_VALIDATE_ACTION_FILENAME (action),
GST_VALIDATE_ACTION_LINENO (action), GST_VALIDATE_ACTION_LINENO (action),
format,
(gint) floor (log10 (abs ((GST_VALIDATE_ACTION_LINENO (action))))) + 1,
' ');
va_start (var_args, format);
gst_validate_report_valist (reporter, issue_id, f, var_args);
va_end (var_args);
g_free (f);
}
void
gst_validate_reporter_report_simple (GstValidateReporter * reporter,
GstValidateIssueId issue_id, const gchar * message)
{
gst_validate_report (reporter, issue_id, "%s", message);
}
/**
* gst_validate_reporter_set_name:
* @reporter: The reporter to set the name on
* @name: (transfer full): The name of the reporter
*
* Sets @ name on @reporter
*/
void
gst_validate_reporter_set_name (GstValidateReporter * reporter, gchar * name)
{
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
g_free (priv->name);
priv->name = name;
}
const gchar *
gst_validate_reporter_get_name (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
return priv->name;
}
/**
* gst_validate_reporter_get_runner:
* @reporter: The reporter to get the runner from
*
* Returns: (transfer full): The runner
*/
GstValidateRunner *
gst_validate_reporter_get_runner (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
return g_weak_ref_get (&priv->runner);
}
void
gst_validate_reporter_set_runner (GstValidateReporter * reporter,
GstValidateRunner * runner)
{
GstValidateReporterPrivate *priv = gst_validate_reporter_get_priv (reporter);
g_weak_ref_set (&priv->runner, runner);
g_object_notify (G_OBJECT (reporter), "validate-runner");
}
/**
* gst_validate_reporter_set_handle_g_logs:
* @reporter: The #GstValidateReporter to set has the handler for g_log
*
* Set @reporter has the 'source' of any g_log happening during the
* execution. Usually the monitor of the first #GstPipeline is used
* to handle g_logs.
*
* Basically this function is used in order to start tracking any
* issue reported with g_log in the process into GstValidate report
* in the GstValidate reporting system.
*/
void
gst_validate_reporter_set_handle_g_logs (GstValidateReporter * reporter)
{
g_log_set_default_handler ((GLogFunc) gst_validate_reporter_g_log_func,
reporter);
g_log_set_handler ("GStreamer",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter);
g_log_set_handler ("GLib",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter);
g_log_set_handler ("GLib-GObject",
G_LOG_LEVEL_MASK, (GLogFunc) gst_validate_reporter_g_log_func, reporter);
g_log_handler = gst_validate_reporter_get_priv (reporter);
g_object_weak_ref (G_OBJECT (reporter), gst_validate_reporter_destroyed,
NULL);
}
/**
* gst_validate_reporter_get_reports:
* @reporter: a #GstValidateReporter
*
* Get the list of reports present in the reporter.
*
* Returns: (transfer full) (element-type GstValidateReport): the list of
* #GstValidateReport present in the reporter.
* The caller should unref each report once it is done with them.
*/
GList *
gst_validate_reporter_get_reports (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv;
GList *reports, *tmp;
GList *ret = NULL;
priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE);
GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter);
reports = g_hash_table_get_values (priv->reports);
for (tmp = reports; tmp; tmp = tmp->next) {
ret =
g_list_append (ret,
gst_validate_report_ref ((GstValidateReport *) (tmp->data)));
}
g_list_free (reports);
GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter);
return ret;
}
/**
* gst_validate_reporter_get_reports_count:
* @reporter: a #GstValidateReporter
*
* Get the number of reports present in the reporter.
*
* Returns: the number of reports currently present in @reporter.
*/
gint
gst_validate_reporter_get_reports_count (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv;
gint ret;
priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE);
GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter);
ret = g_hash_table_size (priv->reports);
GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter);
return ret;
}
/**
* gst_validate_reporter_purge_reports:
* @reporter: a #GstValidateReporter
*
* Remove all the #GstValidateReport from @reporter. This should be called
* before unreffing the reporter to break cyclic references.
*/
void
gst_validate_reporter_purge_reports (GstValidateReporter * reporter)
{
GstValidateReporterPrivate *priv;
priv = g_object_get_data (G_OBJECT (reporter), REPORTER_PRIVATE);
GST_VALIDATE_REPORTER_REPORTS_LOCK (reporter);
g_hash_table_remove_all (priv->reports);
GST_VALIDATE_REPORTER_REPORTS_UNLOCK (reporter);
}