From e7bdd5d189ed788e667c54824e314df496bbd0e9 Mon Sep 17 00:00:00 2001 From: Jonh Wendell Date: Fri, 9 Sep 2016 09:06:05 -0300 Subject: [PATCH] Add g_log_variant(): structured log that accepts a GVariant This makes the structured logging available to other languages via introspection. https://bugzilla.gnome.org/show_bug.cgi?id=770971 --- docs/reference/glib/glib-sections.txt | 1 + glib/gmessages.c | 110 ++++++++++++++++++++++++++ glib/gmessages.h | 6 ++ glib/tests/logging.c | 56 +++++++++++++ 4 files changed, 173 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 30d313291..d8f1643bd 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1177,6 +1177,7 @@ g_log_set_default_handler g_log_structured +g_log_variant GLogField g_log_structured_array G_DEBUG_HERE diff --git a/glib/gmessages.c b/glib/gmessages.c index db6761f13..632547a86 100644 --- a/glib/gmessages.c +++ b/glib/gmessages.c @@ -1602,6 +1602,116 @@ g_log_structured (const gchar *log_domain, va_end (args); } +/** + * g_log_variant: + * @log_domain: log domain, usually %G_LOG_DOMAIN + * @log_level: log level, either from #GLogLevelFlags, or a user-defined + * level + * @fields: a dictionary (#GVariant of the type %G_VARIANT_TYPE_VARDICT) + * containing the key-value pairs of message data. + * + * Log a message with structured data, accepting the data within a #GVariant. This + * version is especially useful for use in other languages, via introspection. + * + * The only mandatory item in the @fields dictionary is the "MESSAGE" which must + * contain the text shown to the user. + * + * The values in the @fields dictionary are likely to be of type String + * (#G_VARIANT_TYPE_STRING). Array of bytes (#G_VARIANT_TYPE_BYTESTRING) is also + * supported. In this case the message is handled as binary and will be forwarded + * to the log writer as such. The size of the array should not be higher than + * %G_MAXSSIZE. Otherwise it will be truncated to this size. For other types + * g_variant_print() will be used to convert the value into a string. + * + * For more details on its usage and about the parameters, see g_log_structured(). + * + * Since: 2.50 + */ + +void +g_log_variant (const gchar *log_domain, + GLogLevelFlags log_level, + GVariant *fields) +{ + GVariantIter iter; + GVariant *value; + gchar *key; + GArray *fields_array; + GLogField field; + GSList *values_list, *print_list; + + g_return_if_fail (g_variant_is_of_type (fields, G_VARIANT_TYPE_VARDICT)); + + values_list = print_list = NULL; + fields_array = g_array_new (FALSE, FALSE, sizeof (GLogField)); + + field.key = "PRIORITY"; + field.value = log_level_to_priority (log_level); + field.length = -1; + g_array_append_val (fields_array, field); + + if (log_domain) + { + field.key = "GLIB_DOMAIN"; + field.value = log_domain; + field.length = -1; + g_array_append_val (fields_array, field); + } + + g_variant_iter_init (&iter, fields); + while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) + { + gboolean defer_unref = TRUE; + + field.key = key; + field.length = -1; + + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + field.value = g_variant_get_string (value, NULL); + } + else if (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING)) + { + gsize s; + field.value = g_variant_get_fixed_array (value, &s, sizeof (guchar)); + if (G_LIKELY (s <= G_MAXSSIZE)) + { + field.length = s; + } + else + { + _g_fprintf (stderr, + "Byte array too large (%" G_GSIZE_FORMAT " bytes)" + " passed to g_log_variant(). Truncating to " G_STRINGIFY (G_MAXSSIZE) + " bytes.", s); + field.length = G_MAXSSIZE; + } + } + else + { + char *s = g_variant_print (value, FALSE); + field.value = s; + print_list = g_slist_prepend (print_list, s); + defer_unref = FALSE; + } + + g_array_append_val (fields_array, field); + + if (G_LIKELY (defer_unref)) + values_list = g_slist_prepend (values_list, value); + else + g_variant_unref (value); + } + + /* Log it. */ + g_log_structured_array (log_level, (GLogField *) fields_array->data, fields_array->len); + + g_array_free (fields_array, TRUE); + g_slist_free_full (values_list, (GDestroyNotify) g_variant_unref); + g_slist_free_full (print_list, g_free); +} + + #pragma GCC diagnostic pop static GLogWriterOutput _g_log_writer_fallback (GLogLevelFlags log_level, diff --git a/glib/gmessages.h b/glib/gmessages.h index 9953652b5..c923aea58 100644 --- a/glib/gmessages.h +++ b/glib/gmessages.h @@ -32,6 +32,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -196,6 +197,11 @@ void g_log_structured_array (GLogLevelFlags log_level, const GLogField *fields, gsize n_fields); +GLIB_AVAILABLE_IN_2_50 +void g_log_variant (const gchar *log_domain, + GLogLevelFlags log_level, + GVariant *fields); + GLIB_AVAILABLE_IN_2_50 void g_log_set_writer_func (GLogWriterFunc func, gpointer user_data, diff --git a/glib/tests/logging.c b/glib/tests/logging.c index 0821c27d1..da41d82f0 100644 --- a/glib/tests/logging.c +++ b/glib/tests/logging.c @@ -345,6 +345,8 @@ compare_fields (const GLogField *f1, gsize n1, const GLogField *f2, gsize n2) } static GSList *expected_messages = NULL; +static const guchar binary_field[] = {1, 2, 3, 4, 5}; + static GLogWriterOutput expect_log_writer (GLogLevelFlags log_level, @@ -500,6 +502,58 @@ test_structured_logging_roundtrip3 (void) g_assert (expected_messages == NULL); } +static GVariant * +create_variant_fields (void) +{ + GVariant *binary; + GVariantBuilder builder; + + binary = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, binary_field, G_N_ELEMENTS (binary_field), sizeof (binary_field[0])); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "MESSAGE_ID", g_variant_new_string ("06d4df59e6c24647bfe69d2c27ef0b4e")); + g_variant_builder_add (&builder, "{sv}", "MESSAGE", g_variant_new_string ("This is a debug message")); + g_variant_builder_add (&builder, "{sv}", "MY_APPLICATION_CUSTOM_FIELD", g_variant_new_string ("some debug string")); + g_variant_builder_add (&builder, "{sv}", "MY_APPLICATION_CUSTOM_FIELD_BINARY", binary); + + return g_variant_builder_end (&builder); +} + +static void +test_structured_logging_variant1 (void) +{ + GVariant *v = create_variant_fields (); + + log_count = 0; + g_log_set_writer_func (null_log_writer, NULL, NULL); + + g_log_variant ("some-domain", G_LOG_LEVEL_MESSAGE, v); + g_variant_unref (v); + g_assert_cmpint (log_count, ==, 1); +} + +static void +test_structured_logging_variant2 (void) +{ + const GLogField fields[] = { + { "GLIB_DOMAIN", "some-domain", -1 }, + { "PRIORITY", "5", -1 }, + { "MESSAGE", "This is a debug message", -1 }, + { "MESSAGE_ID", "06d4df59e6c24647bfe69d2c27ef0b4e", -1 }, + { "MY_APPLICATION_CUSTOM_FIELD", "some debug string", -1 }, + { "MY_APPLICATION_CUSTOM_FIELD_BINARY", binary_field, sizeof (binary_field) } + }; + ExpectedMessage expected = { fields, 6 }; + GVariant *v = create_variant_fields (); + + expected_messages = g_slist_append (NULL, &expected); + g_log_set_writer_func (expect_log_writer, NULL, NULL); + + g_log_variant ("some-domain", G_LOG_LEVEL_MESSAGE, v); + g_variant_unref (v); + g_assert (expected_messages == NULL); +} + int main (int argc, char *argv[]) { @@ -531,6 +585,8 @@ main (int argc, char *argv[]) g_test_add_func ("/structured-logging/roundtrip1", test_structured_logging_roundtrip1); g_test_add_func ("/structured-logging/roundtrip2", test_structured_logging_roundtrip2); g_test_add_func ("/structured-logging/roundtrip3", test_structured_logging_roundtrip3); + g_test_add_func ("/structured-logging/variant1", test_structured_logging_variant1); + g_test_add_func ("/structured-logging/variant2", test_structured_logging_variant2); return g_test_run (); }