[gjs/ewlsh/glogfield-support] glib: Implement override for structured logging hook




commit 16230d179bd75276c233b057584b1215e9148185
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Jul 4 22:45:49 2021 -0700

    glib: Implement override for structured logging hook

 .eslintrc.yml                     |  1 +
 installed-tests/js/meson.build    |  1 +
 installed-tests/js/testConsole.js | 47 +++++++++++++++++++
 libgjs-private/gjs-util.c         | 98 +++++++++++++++++++++++++++++++++++++++
 libgjs-private/gjs-util.h         | 58 +++++++++++++++++++++++
 modules/core/overrides/GLib.js    |  7 +++
 6 files changed, 212 insertions(+)
---
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 7ddf0e38..80998ba0 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -243,6 +243,7 @@ rules:
   yield-star-spacing: error
   yoda: error
 globals:
+  console: readonly
   ARGV: readonly
   Debugger: readonly
   GIRepositoryGType: readonly
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index e11f1418..615fc967 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -94,6 +94,7 @@ subdir('libgjstesttools')
 jasmine_tests = [
     'self',
     'ByteArray',
+    'Console',
     'Exceptions',
     'Format',
     'Fundamental',
diff --git a/installed-tests/js/testConsole.js b/installed-tests/js/testConsole.js
new file mode 100644
index 00000000..b5dff629
--- /dev/null
+++ b/installed-tests/js/testConsole.js
@@ -0,0 +1,47 @@
+// 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" />
+
+const {GLib} = imports.gi;
+
+describe('Console log levels', 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('logs a message', function () {
+        console.log('a message');
+
+        expect(writer_func).toHaveBeenCalledWith(
+            GLib.LogLevelFlags.LEVEL_MESSAGE,
+            jasmine.objectContaining({MESSAGE: 'a message'})
+        );
+    });
+
+    it('logs a warning', function () {
+        console.warn('a warning');
+
+        expect(writer_func).toHaveBeenCalledWith(
+            GLib.LogLevelFlags.LEVEL_WARNING,
+            jasmine.objectContaining({MESSAGE: 'a warning'})
+        );
+    });
+});
diff --git a/libgjs-private/gjs-util.c b/libgjs-private/gjs-util.c
index 15060950..27993acd 100644
--- a/libgjs-private/gjs-util.c
+++ b/libgjs-private/gjs-util.c
@@ -213,3 +213,101 @@ void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
                          void *user_data) {
   g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data);
 }
+
+GType gjs_log_field_get_type() {
+    static GType type = 0;
+
+    if (G_UNLIKELY(!type))
+        type = g_boxed_type_register_static("GjsLogField",
+                                            (GBoxedCopyFunc)gjs_log_field_copy,
+                                            (GBoxedFreeFunc)gjs_log_field_free);
+
+    return type;
+}
+
+GjsLogField* gjs_log_field_new(const char* key, const char* value) {
+    GjsLogField* data = g_malloc0(sizeof(GjsLogField));
+    data->length = -1;
+    data->key = g_strdup(key);
+    data->value = g_strdup(value);
+    return data;
+}
+
+GjsLogField* gjs_log_field_copy(GjsLogField* data) {
+    GjsLogField* copied_data = g_malloc0(sizeof(GjsLogField));
+    memcpy(copied_data, data, sizeof(GjsLogField));
+    return copied_data;
+}
+
+void gjs_log_field_free(GjsLogField* data) { g_free(data); }
+
+typedef struct WriterFuncData {
+    GjsGLogWriterFunc func;
+    void* wrapped_user_data;
+    GDestroyNotify wrapped_user_data_free;
+} WriterFuncData;
+
+GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level,
+                                             const GLogField* fields,
+                                             gsize n_fields,
+                                             gpointer user_data) {
+    WriterFuncData* data = (struct WriterFuncData*)user_data;
+    GjsGLogWriterFunc func = data->func;
+
+    GjsLogField* string_fields = g_malloc0(n_fields * sizeof(GjsLogField));
+    size_t f;
+    for (f = 0; f < n_fields; f++) {
+        const GLogField* field = &fields[f];
+
+        GjsLogField* string_field = &string_fields[f];
+
+        string_field->key = g_strdup(field->key);
+
+        if (field->length == -1) {
+            string_field->value = g_strdup(field->value);
+            string_field->length = -1;
+        } else {
+            string_field->value = NULL;
+            string_field->length = 0;
+        }
+    }
+
+    GLogWriterOutput output =
+        func(log_level, string_fields, n_fields, data->wrapped_user_data);
+
+    g_free(string_fields);
+
+    return output;
+}
+
+const GLogField* gjs_log_field_to_glib(const GjsLogField* logfield) {
+    GLogField* field = g_malloc0(sizeof(GjsLogField));
+    field->key = g_strdup(logfield->key);
+    field->value = g_strdup(logfield->value);
+    field->length = -1;
+    return field;
+}
+
+static void gjs_log_user_data_free(void* user_data) {
+    WriterFuncData* data = (struct WriterFuncData*)user_data;
+    data->wrapped_user_data_free(data->wrapped_user_data);
+    g_free(data);
+}
+
+/**
+ * gjs_log_set_writer_func
+ * @func: (scope call): callback with log data
+ * @user_data: (closure): user data for @func
+ * @user_data_free: destroy for @user_data
+ */
+void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data,
+                             GDestroyNotify user_data_free) {
+    WriterFuncData* data =
+        (struct WriterFuncData*)g_malloc0(sizeof(WriterFuncData));
+    data->func = func;
+    data->wrapped_user_data = user_data;
+    data->wrapped_user_data_free = user_data_free;
+
+    g_log_set_writer_func(gjs_log_writer_func_wrapper, data,
+                          gjs_log_user_data_free);
+}
diff --git a/libgjs-private/gjs-util.h b/libgjs-private/gjs-util.h
index 320337c5..66ed563c 100644
--- a/libgjs-private/gjs-util.h
+++ b/libgjs-private/gjs-util.h
@@ -22,6 +22,10 @@ G_BEGIN_DECLS
 GJS_EXPORT
 char * gjs_format_int_alternative_output (int n);
 
+GJS_EXPORT
+GType gjs_log_field_get_type();
+#define GJS_LOG_FIELD_TYPE (gjs_log_field_get_type())
+
 /**
  * GjsCompareDataFunc:
  * @a: a value
@@ -48,6 +52,60 @@ GJS_EXPORT
 void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
                          void *user_data);
 
+typedef struct _GjsLogField GjsLogField;
+
+/**
+ * GjsLogField:
+ * @key: key
+ * @value: value
+ */
+struct _GjsLogField {
+    const char* key;
+    const char* value;
+    ssize_t length;
+};
+
+/**
+ * gjs_log_field_copy: (skip)
+ *
+ * Creates a copy of @data.
+ */
+GJS_EXPORT
+GjsLogField* gjs_log_field_copy(GjsLogField* data);
+
+/**
+ * gjs_log_field_free: (skip)
+ *
+ * Free's @data.
+ */
+GJS_EXPORT
+void gjs_log_field_free(GjsLogField* data);
+
+/**
+ * gjs_log_field_new: (constructor)
+ * @key: string to become the .item1 field
+ * @value: string to become the .item2 field
+ *
+ * Returns: (transfer full): new data
+ */
+GJS_EXPORT
+GjsLogField* gjs_log_field_new(const char* key, const char* value);
+
+/**
+ * GjsGLogWriterFunc:
+ * @level: the log level
+ * @fields: (array length=size): an array of fields
+ * @size: the size of fields
+ * @user_data: user data
+ */
+typedef GLogWriterOutput (*GjsGLogWriterFunc)(GLogLevelFlags level,
+                                              const GjsLogField* fields,
+                                              gsize size, gpointer user_data);
+
+GJS_EXPORT
+void gjs_log_set_writer_func(GjsGLogWriterFunc func, gpointer user_data,
+                             GDestroyNotify user_data_free);
+
 /* For imports.gettext */
 typedef enum
 {
diff --git a/modules/core/overrides/GLib.js b/modules/core/overrides/GLib.js
index 5e3800a9..a76c9e9d 100644
--- a/modules/core/overrides/GLib.js
+++ b/modules/core/overrides/GLib.js
@@ -319,6 +319,13 @@ function _init() {
         GLib.log_variant(logDomain, logLevel, new GLib.Variant('a{sv}', fields));
     };
 
+    this.log_set_writer_func = function (writer_func) {
+        imports.gi.GjsPrivate.log_set_writer_func(function (logLevel, stringFields) {
+            const stringFieldsObj = Object.fromEntries(stringFields.map(field => [field.key, field.value]));
+            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]