[gjs/ewlsh/glogfield-support: 90/90] overrides: Implement overrides for structured logging hook
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/glogfield-support: 90/90] overrides: Implement overrides for structured logging hook
- Date: Fri, 13 Aug 2021 02:42:02 +0000 (UTC)
commit 2632a664b9e90129e96e82ced546f1c5f387cf6f
Author: Evan Welsh <contact evanwelsh com>
Date: Thu Aug 12 19:41:54 2021 -0700
overrides: Implement overrides for structured logging hook
installed-tests/js/.eslintrc.yml | 1 +
installed-tests/js/meson.build | 1 +
installed-tests/js/testGLibLogWriter.js | 91 +++++++++++++++++++++++++++++++
libgjs-private/gjs-util.c | 94 ++++++++++++++++++++++++++++++++-
libgjs-private/gjs-util.h | 17 ++++++
modules/core/overrides/GLib.js | 59 +++++++++++++++++++--
6 files changed, 258 insertions(+), 5 deletions(-)
---
diff --git a/installed-tests/js/.eslintrc.yml b/installed-tests/js/.eslintrc.yml
index bcdadbea..cdf5cf9f 100644
--- a/installed-tests/js/.eslintrc.yml
+++ b/installed-tests/js/.eslintrc.yml
@@ -35,6 +35,7 @@ overrides:
- testCairoModule.js
- testESModules.js
- testEncoding.js
+ - testGLibLogWriter.js
- modules/importmeta.js
- modules/exports.js
- modules/say.js
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index 73ea6852..f85b9586 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -243,6 +243,7 @@ endif
modules_tests = [
'ESModules',
'Encoding',
+ 'GLibLogWriter',
]
if build_cairo
modules_tests += 'CairoModule'
diff --git a/installed-tests/js/testGLibLogWriter.js b/installed-tests/js/testGLibLogWriter.js
new file mode 100644
index 00000000..d69935d8
--- /dev/null
+++ b/installed-tests/js/testGLibLogWriter.js
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact evanwelsh com>
+
+// eslint-disable-next-line
+/// <reference types="jasmine" />
+
+import GLib from 'gi://GLib';
+import {arrayLikeWithExactContents} from './matchers.js';
+
+function encodedString(str) {
+ const encoder = new TextEncoder();
+ const encoded = encoder.encode(str);
+
+ return arrayLikeWithExactContents(encoded);
+}
+
+describe('GLib Structured logging handler', function () {
+ /** @type {jasmine.Spy<(_level: any, _fields: any) => any>} */
+ let writer_func;
+
+ beforeAll(function () {
+ writer_func = jasmine.createSpy(
+ 'Log test writer func',
+ function (_level, _fields) {
+ return GLib.LogWriterOutput.HANDLED;
+ }
+ );
+
+ writer_func.and.callThrough();
+
+ GLib.log_set_writer_func(writer_func);
+ });
+
+ beforeEach(function () {
+ writer_func.calls.reset();
+ });
+
+ it('writes a message', function () {
+ GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, {
+ MESSAGE: 'a message',
+ });
+
+ expect(writer_func).toHaveBeenCalledWith(
+ GLib.LogLevelFlags.LEVEL_MESSAGE,
+ jasmine.objectContaining({MESSAGE: encodedString('a message')})
+ );
+ });
+
+ it('writes a warning', function () {
+ GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_WARNING, {
+ MESSAGE: 'a warning',
+ });
+
+ expect(writer_func).toHaveBeenCalledWith(
+ GLib.LogLevelFlags.LEVEL_WARNING,
+ jasmine.objectContaining({MESSAGE: encodedString('a warning')})
+ );
+ });
+
+ it('preserves a custom string field', function () {
+ GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, {
+ MESSAGE: 'with a custom field',
+ GJS_CUSTOM_FIELD: 'a custom value',
+ });
+
+ expect(writer_func).toHaveBeenCalledWith(
+ GLib.LogLevelFlags.LEVEL_MESSAGE,
+ jasmine.objectContaining({
+ MESSAGE: encodedString('with a custom field'),
+ GJS_CUSTOM_FIELD: encodedString('a custom value'),
+ })
+ );
+ });
+
+ it('preserves a custom byte array field', function () {
+ GLib.log_structured('Gjs-Console', GLib.LogLevelFlags.LEVEL_MESSAGE, {
+ MESSAGE: 'with a custom field',
+ GJS_CUSTOM_FIELD: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
+ });
+
+ expect(writer_func).toHaveBeenCalledWith(
+ GLib.LogLevelFlags.LEVEL_MESSAGE,
+ jasmine.objectContaining({
+ MESSAGE: encodedString('with a custom field'),
+ GJS_CUSTOM_FIELD: arrayLikeWithExactContents([
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ ]),
+ })
+ );
+ });
+});
diff --git a/libgjs-private/gjs-util.c b/libgjs-private/gjs-util.c
index 15060950..deac1a7b 100644
--- a/libgjs-private/gjs-util.c
+++ b/libgjs-private/gjs-util.c
@@ -6,8 +6,10 @@
#include <config.h>
-#include <locale.h> /* for setlocale */
-#include <stddef.h> /* for size_t */
+#include <locale.h> /* for setlocale */
+#include <stdbool.h>
+#include <stddef.h> /* for size_t */
+#include <string.h>
#include <glib-object.h>
#include <girepository.h>
@@ -213,3 +215,91 @@ void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
void *user_data) {
g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data);
}
+
+typedef struct WriterFuncData {
+ GjsGLogWriterFunc func;
+ void* wrapped_user_data;
+ GDestroyNotify wrapped_user_data_free;
+} WriterFuncData;
+
+static void* log_writer_user_data = NULL;
+static GDestroyNotify log_writer_user_data_free = NULL;
+
+GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level,
+ const GLogField* fields,
+ size_t n_fields, void* user_data) {
+ GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data;
+ GVariantDict dict;
+ g_variant_dict_init(&dict, NULL);
+
+ size_t f;
+ for (f = 0; f < n_fields; f++) {
+ const GLogField* field = &fields[f];
+
+ GVariant* value;
+ if (field->length < 0) {
+ size_t bytes_len = strlen(field->value);
+ GBytes* bytes = g_bytes_new(field->value, bytes_len);
+
+ value = g_variant_new_maybe(
+ G_VARIANT_TYPE_BYTESTRING,
+ g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
+ true));
+ g_bytes_unref(bytes);
+ } else if (field->length > 0) {
+ GBytes* bytes = g_bytes_new(field->value, field->length);
+
+ value = g_variant_new_maybe(
+ G_VARIANT_TYPE_BYTESTRING,
+ g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
+ true));
+ g_bytes_unref(bytes);
+ } else {
+ value = g_variant_new_maybe(G_VARIANT_TYPE_STRING, NULL);
+ }
+
+ g_variant_dict_insert_value(&dict, field->key, value);
+ }
+
+ GVariant* string_fields = g_variant_dict_end(&dict);
+ g_variant_ref(string_fields);
+
+ GLogWriterOutput output =
+ func(log_level, string_fields, log_writer_user_data);
+
+ g_variant_unref(string_fields);
+ return output;
+}
+
+/**
+ * gjs_log_set_writer_default:
+ *
+ * Sets the structured logging writer function back to the platform default.
+ */
+void gjs_log_set_writer_default() {
+ if (log_writer_user_data_free) {
+ log_writer_user_data_free(log_writer_user_data);
+ }
+
+ g_log_set_writer_func(g_log_writer_default, NULL, NULL);
+ log_writer_user_data_free = NULL;
+ log_writer_user_data = NULL;
+}
+
+/**
+ * gjs_log_set_writer_func:
+ * @func: (scope notified): callback with log data
+ * @user_data: (closure): user data for @func
+ * @user_data_free: (destroy user_data_free): destroy for @user_data
+ *
+ * Sets a given function as the writer function for structured logging,
+ * passing log fields as a variant. If called from JavaScript the application
+ * must call gjs_log_set_writer_default prior to exiting.
+ */
+void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data,
+ GDestroyNotify user_data_free) {
+ log_writer_user_data = user_data;
+ log_writer_user_data_free = user_data_free;
+
+ g_log_set_writer_func(gjs_log_writer_func_wrapper, func, NULL);
+}
diff --git a/libgjs-private/gjs-util.h b/libgjs-private/gjs-util.h
index 320337c5..dc4380e7 100644
--- a/libgjs-private/gjs-util.h
+++ b/libgjs-private/gjs-util.h
@@ -48,6 +48,23 @@ GJS_EXPORT
void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
void *user_data);
+/**
+ * GjsGLogWriterFunc:
+ * @level: the log level
+ * @fields: a dictionary variant with type a{sms}
+ * @user_data: user data
+ */
+typedef GLogWriterOutput (*GjsGLogWriterFunc)(GLogLevelFlags level,
+ const GVariant* fields,
+ void* user_data);
+
+GJS_EXPORT
+void gjs_log_set_writer_func(GjsGLogWriterFunc func, gpointer user_data,
+ GDestroyNotify user_data_free);
+
+GJS_EXPORT
+void gjs_log_set_writer_default();
+
/* For imports.gettext */
typedef enum
{
diff --git a/modules/core/overrides/GLib.js b/modules/core/overrides/GLib.js
index e4dca1a1..20d38980 100644
--- a/modules/core/overrides/GLib.js
+++ b/modules/core/overrides/GLib.js
@@ -304,14 +304,67 @@ function _init() {
return imports._byteArrayNative.fromGBytes(this);
};
- this.log_structured = function (logDomain, logLevel, stringFields) {
+ this.log_structured =
+ /**
+ * @param {string} logDomain
+ * @param {GLib.LogLevelFlags} logLevel
+ * @param {Record<string, unknown>} stringFields
+ * @returns {void}
+ */
+ function log_structured(logDomain, logLevel, stringFields) {
+ /** @type {Record<string, GLib.Variant>} */
let fields = {};
- for (let key in stringFields)
- fields[key] = new GLib.Variant('s', stringFields[key]);
+
+ for (let key in stringFields) {
+ const field = stringFields[key];
+
+ if (field instanceof Uint8Array) {
+ fields[key] = new GLib.Variant('ay', field);
+ } else if (typeof field === 'string') {
+ fields[key] = new GLib.Variant('s', field);
+ } else if (field instanceof GLib.Variant) {
+ // GLib.log_variant converts all Variants that are
+ // not 'ay' or 's' type to strings by printing
+ // them.
+ //
+ //
https://gitlab.gnome.org/GNOME/glib/-/blob/a380bfdf93cb3bfd3cd4caedc0127c4e5717545b/glib/gmessages.c#L1894
+ fields[key] = field;
+ } else {
+ throw new TypeError(`Unsupported value ${field}, log_structured supports GLib.Variant,
Uint8Array, and string values.`);
+ }
+ }
GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', fields));
};
+ // GjsPrivate depends on GLib so we cannot import it
+ // before GLib is fully resolved.
+
+ this.log_set_writer_func_variant = function (...args) {
+ const {log_set_writer_func} = imports.gi.GjsPrivate;
+
+ log_set_writer_func(...args);
+ };
+
+ this.log_set_writer_default = function (...args) {
+ const {log_set_writer_default} = imports.gi.GjsPrivate;
+
+ log_set_writer_default(...args);
+ };
+
+ this.log_set_writer_func = function (writer_func) {
+ const {log_set_writer_func} = imports.gi.GjsPrivate;
+
+ if (typeof writer_func !== 'function') {
+ log_set_writer_func(writer_func);
+ } else {
+ log_set_writer_func(function (logLevel, stringFields) {
+ const stringFieldsObj = {...stringFields.recursiveUnpack()};
+ return writer_func(logLevel, stringFieldsObj);
+ });
+ }
+ };
+
this.VariantDict.prototype.lookup = function (key, variantType = null, deep = false) {
if (typeof variantType === 'string')
variantType = new GLib.VariantType(variantType);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]