[geary/mjog/logging-improvements: 11/23] Geary.Logging: Move remaining code from api source to util source
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/logging-improvements: 11/23] Geary.Logging: Move remaining code from api source to util source
- Date: Thu, 16 Apr 2020 09:03:34 +0000 (UTC)
commit 00f0fb9665baefbd49c4ad3484d7eda0d624b769
Author: Michael Gratton <mike vee net>
Date: Wed Apr 15 17:17:59 2020 +1000
Geary.Logging: Move remaining code from api source to util source
Now that the flags code is gone, move the remaining code from
`src/engine/api/geary-logging.vala` into
`src/engine/util/util-logging.vala`, and remove the former source file.
po/POTFILES.in | 1 -
src/engine/api/geary-logging.vala | 453 -------------------------------------
src/engine/meson.build | 1 -
src/engine/util/util-logging.vala | 462 +++++++++++++++++++++++++++++++++++++-
4 files changed, 461 insertions(+), 456 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8c011660..ec902908 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -164,7 +164,6 @@ src/engine/api/geary-folder-supports-mark.vala
src/engine/api/geary-folder-supports-move.vala
src/engine/api/geary-folder-supports-remove.vala
src/engine/api/geary-folder.vala
-src/engine/api/geary-logging.vala
src/engine/api/geary-named-flag.vala
src/engine/api/geary-named-flags.vala
src/engine/api/geary-problem-report.vala
diff --git a/src/engine/api/geary-logging.vala b/src/engine/api/geary-logging.vala
index 435069ca..5f176c56 100644
--- a/src/engine/api/geary-logging.vala
+++ b/src/engine/api/geary-logging.vala
@@ -14,457 +14,4 @@
namespace Geary.Logging {
-/** The logging domain for the engine. */
-public const string DOMAIN = "Geary";
-
-/** Specifies the default number of log records retained. */
-public const uint DEFAULT_MAX_LOG_BUFFER_LENGTH = 4096;
-
-/**
- * A record of a single message sent to the logging system.
- *
- * A record is created for each message logged, and stored in a
- * limited-length, singly-linked buffer. Applications can retrieve
- * this by calling {@link get_earliest_record} and then {get_next},
- * and can be notified of new records via {@link set_log_listener}.
- */
-public class Record {
-
-
- /** The GLib domain of the log message, if any. */
- public string? domain { get; private set; default = null; }
-
- /** Account from which the record originated, if any. */
- public Account? account { get; private set; default = null; }
-
- /** Client service from which the record originated, if any. */
- public ClientService? service { get; private set; default = null; }
-
- /** Folder from which the record originated, if any. */
- public Folder? folder { get; private set; default = null; }
-
- /** The logged message, if any. */
- public string? message = null;
-
- /** The source filename, if any. */
- public string? source_filename = null;
-
- /** The source filename, if any. */
- public string? source_line_number = null;
-
- /** The source function, if any. */
- public string? source_function = null;
-
- /** The logged level, if any. */
- public GLib.LogLevelFlags levels;
-
- /** Time at which the log message was generated. */
- public int64 timestamp;
-
- /** The next log record in the buffer, if any. */
- public Record? next { get; internal set; default = null; }
-
- private State[] states;
- private bool filled = false;
- private bool old_log_api = false;
-
-
- internal Record(GLib.LogField[] fields,
- GLib.LogLevelFlags levels,
- int64 timestamp) {
- this.levels = levels;
- this.timestamp = timestamp;
- this.old_log_api = (
- fields.length > 0 &&
- fields[0].key == "GLIB_OLD_LOG_API"
- );
-
- // Since GLib.LogField only retains a weak ref to its value,
- // find and ref any values we wish to keep around.
- this.states = new State[fields.length];
- int state_count = 0;
- foreach (GLib.LogField field in fields) {
- switch (field.key) {
- case "GEARY_LOGGING_SOURCE":
- this.states[state_count++] =
- ((Source) field.value).to_logging_state();
- break;
-
- case "GLIB_DOMAIN":
- this.domain = field_to_string(field);
- break;
-
- case "MESSAGE":
- this.message = field_to_string(field);
- break;
-
- case "CODE_FILE":
- this.source_filename = field_to_string(field);
- break;
-
- case "CODE_LINE":
- this.source_line_number = field_to_string(field);
- break;
-
- case "CODE_FUNC":
- this.source_function = field_to_string(field);
- break;
- }
- }
-
- this.states.length = state_count;
- }
-
- /**
- * Copy constructor.
- *
- * Copies all properties of the given record except its next
- * record.
- */
- public Record.copy(Record other) {
- this.domain = other.domain;
- this.account = other.account;
- this.service = other.service;
- this.folder = other.folder;
- this.message = other.message;
- this.source_filename = other.source_filename;
- this.source_line_number = other.source_line_number;
- this.source_function = other.source_function;
- this.levels = other.levels;
- this.timestamp = other.timestamp;
-
- // Kept null deliberately so that we don't get a stack blowout
- // copying large record chains and code that does copy records
- // can copy only a fixed number.
- // this.next
-
- this.states = other.states;
- this.filled = other.filled;
- this.old_log_api = other.old_log_api;
- }
-
-
- /**
- * Sets the well-known logging source properties.
- *
- * Call this before trying to access {@link account}, {@link
- * folder} and {@link service}. Determining these can be
- * computationally complex and hence is not done by default.
- */
- public void fill_well_known_sources() {
- if (!this.filled) {
- foreach (unowned State state in this.states) {
- GLib.Type type = state.source.get_type();
- if (type.is_a(typeof(Account))) {
- this.account = (Account) state.source;
- } else if (type.is_a(typeof(ClientService))) {
- this.service = (ClientService) state.source;
- } else if (type.is_a(typeof(Folder))) {
- this.folder = (Folder) state.source;
- }
- }
- this.filled = true;
- }
- }
-
- /** Returns a formatted string representation of this record. */
- public string format() {
- fill_well_known_sources();
-
- string domain = this.domain ?? "[no domain]";
- string message = this.message ?? "[no message]";
- double float_secs = this.timestamp / 1000.0 / 1000.0;
- double floor_secs = GLib.Math.floor(float_secs);
- int ms = (int) GLib.Math.round((float_secs - floor_secs) * 1000.0);
- GLib.DateTime time = new GLib.DateTime.from_unix_utc(
- (int64) float_secs
- ).to_local();
- GLib.StringBuilder str = new GLib.StringBuilder.sized(128);
- str.printf(
- "%s %02d:%02d:%02d.%04d %s:",
- to_prefix(levels),
- time.get_hour(),
- time.get_minute(),
- time.get_second(),
- ms,
- domain
- );
-
- // Append in reverse so inner sources appear first
- for (int i = this.states.length - 1; i >= 0; i--) {
- str.append(" [");
- str.append(this.states[i].format_message());
- str.append("]");
- }
-
- // XXX Don't append source details for the moment because of
- // https://gitlab.gnome.org/GNOME/vala/issues/815
- bool disabled = true;
- if (!disabled && !this.old_log_api && this.source_filename != null) {
- str.append(" [");
- str.append(GLib.Path.get_basename(this.source_filename));
- if (this.source_line_number != null) {
- str.append_c(':');
- str.append(this.source_line_number);
- }
- if (this.source_function != null) {
- str.append_c(':');
- str.append(this.source_function.to_string());
- }
- str.append("]");
- } else if (this.states.length > 0) {
- // Print the class name of the leaf logging source to at
- // least give a general idea of where the message came
- // from.
- str.append(" ");
- str.append(this.states[0].source.get_type().name());
- str.append(": ");
- }
-
- str.append(message);
-
- return str.str;
- }
-
-}
-
-/** Specifies the function signature for {@link set_log_listener}. */
-public delegate void LogRecord(Record record);
-
-private int init_count = 0;
-
-// The two locks below can't be nullable. See
-// https://gitlab.gnome.org/GNOME/vala/issues/812
-
-private GLib.Mutex record_lock;
-private Record? first_record = null;
-private Record? last_record = null;
-private uint log_length = 0;
-private uint max_log_length = 0;
-private unowned LogRecord? listener = null;
-
-private GLib.Mutex writer_lock;
-private unowned FileStream? stream = null;
-
-
-/**
- * Must be called before ''any'' call to the Logging namespace.
- *
- * This will be initialized by the Engine when it's opened, but
- * applications may want to set up logging before that, in which case,
- * call this directly.
- */
-public void init() {
- if (init_count++ != 0)
- return;
- Logging.suppressed_domains = new Gee.HashSet<string>();
- record_lock = GLib.Mutex();
- writer_lock = GLib.Mutex();
- max_log_length = DEFAULT_MAX_LOG_BUFFER_LENGTH;
-}
-
-/**
- * Clears all log records.
- *
- * Since log records hold references to Geary engine objects, it may
- * be desirable to clear the records prior to shutdown so that the
- * objects can be destroyed.
- */
-public void clear() {
- // Keep the old first record so we don't cause any records to be
- // finalised while under the lock, leading to deadlock if
- // finalisation causes any more logging to be generated.
- Record? old_first = null;
-
- // Obtain a lock since other threads could be calling this or
- // generating more logging at the same time.
- record_lock.lock();
- old_first = first_record;
- first_record = null;
- last_record = null;
- log_length = 0;
- record_lock.unlock();
-
- // Manually clear each log record in a loop so that finalisation
- // of each is an iterative process. If we just nulled out the
- // record, finalising the first would cause second to be
- // finalised, which would finalise the third, etc., and the
- // recursion could cause the stack to blow right out for large log
- // buffers.
- while (old_first != null) {
- old_first = old_first.next;
- }
-}
-
-/** Sets a function to be called when a new log record is created. */
-public void set_log_listener(LogRecord? new_listener) {
- listener = new_listener;
-}
-
-/** Returns the oldest log record in the logging system's buffer. */
-public Record? get_earliest_record() {
- return first_record;
-}
-
-/** Returns the most recent log record in the logging system's buffer. */
-public Record? get_latest_record() {
- return last_record;
-}
-
-/**
- * Registers a FileStream to receive all log output from the engine.
- *
- * This may be via the specialized Logging calls (which use the
- * topic-based {@link Flag} or GLib's standard issue
- * debug/message/error/etc. calls ... thus, calling this will also
- * affect the Engine user's calls to those functions.
- *
- * If stream is null, no logging occurs (the default). If non-null and
- * the stream was previously null, all pending log records will be
- * output before proceeding.
- */
-public void log_to(FileStream? stream) {
- bool catch_up = (stream != null && Logging.stream == null);
- Logging.stream = stream;
- if (catch_up) {
- Record? record = Logging.first_record;
- while (record != null) {
- write_record(record, record.levels);
- record = record.next;
- }
- }
-}
-
-
-public GLib.LogWriterOutput default_log_writer(GLib.LogLevelFlags levels,
- GLib.LogField[] fields) {
- Record record = new Record(fields, levels, GLib.get_real_time());
- if (should_blacklist(record)) {
- return GLib.LogWriterOutput.HANDLED;
- }
-
- // Keep the old first record so we don't cause any records to be
- // finalised while under the lock, leading to deadlock if
- // finalisation causes any more logging to be generated.
- Record? old_record = null;
-
- // Update the record linked list. Obtain a lock since multiple
- // threads could be calling this function at the same time.
- record_lock.lock();
- old_record = first_record;
- if (first_record == null) {
- first_record = record;
- last_record = record;
- } else {
- last_record.next = record;
- last_record = record;
- }
- // Drop the first if we are already at maximum length
- if (log_length == max_log_length) {
- first_record = first_record.next;
- } else {
- log_length++;
- }
- record_lock.unlock();
-
- // Now that we are out of the lock, it is safe to finalise any old
- // records.
- old_record = null;
-
- // Ensure the listener is updated on the main loop only, since
- // this could be getting called from other threads.
- if (listener != null) {
- GLib.MainContext.default().invoke(() => {
- listener(record);
- return GLib.Source.REMOVE;
- });
- }
-
- write_record(record, levels);
-
- return GLib.LogWriterOutput.HANDLED;
-}
-
-private bool should_blacklist(Record record) {
- const string DOMAIN_PREFIX = Logging.DOMAIN + ".";
- return (
- // Don't need to check for the engine's domains, they were
- // already handled by Source's methods.
- (record.domain != Logging.DOMAIN &&
- !record.domain.has_prefix(DOMAIN_PREFIX) &&
- record.domain in Logging.suppressed_domains) ||
- // GAction does not support disabling parameterised actions
- // with specific values, but GTK complains if the parameter is
- // set to null to achieve the same effect, and they aren't
- // interested in supporting that: GNOME/gtk!1151
- (record.levels == GLib.LogLevelFlags.LEVEL_WARNING &&
- record.domain == "Gtk" &&
- record.message.has_prefix("actionhelper:") &&
- record.message.has_suffix("target type NULL)"))
- );
-}
-
-private inline void write_record(Record record,
- GLib.LogLevelFlags levels) {
- // Print a log message to the stream if configured, or if the
- // priority is high enough.
- unowned FileStream? out = stream;
- if (out != null ||
- LogLevelFlags.LEVEL_WARNING in levels ||
- LogLevelFlags.LEVEL_CRITICAL in levels ||
- LogLevelFlags.LEVEL_ERROR in levels) {
-
- if (out == null) {
- out = GLib.stderr;
- }
-
- // Lock the writer here so two different threads don't
- // interleave their lines.
- writer_lock.lock();
- out.puts(record.format());
- out.putc('\n');
- writer_lock.unlock();
- }
-}
-
-
-private inline string to_prefix(LogLevelFlags level) {
- switch (level) {
- case LogLevelFlags.LEVEL_ERROR:
- return "![err]";
-
- case LogLevelFlags.LEVEL_CRITICAL:
- return "![crt]";
-
- case LogLevelFlags.LEVEL_WARNING:
- return "*[wrn]";
-
- case LogLevelFlags.LEVEL_MESSAGE:
- return " [msg]";
-
- case LogLevelFlags.LEVEL_INFO:
- return " [inf]";
-
- case LogLevelFlags.LEVEL_DEBUG:
- return " [deb]";
-
- case LogLevelFlags.LEVEL_MASK:
- return "![***]";
-
- default:
- return "![???]";
-
- }
-}
-
-private inline string? field_to_string(GLib.LogField field) {
- string? value = null;
- if (field.length < 0) {
- value = (string) field.value;
- } else if (field.length > 0) {
- value = ((string) field.value).substring(0, field.length);
- }
- return value;
-}
-
}
diff --git a/src/engine/meson.build b/src/engine/meson.build
index c74a72be..d026575d 100644
--- a/src/engine/meson.build
+++ b/src/engine/meson.build
@@ -31,7 +31,6 @@ geary_engine_vala_sources = files(
'api/geary-folder-supports-mark.vala',
'api/geary-folder-supports-move.vala',
'api/geary-folder-supports-remove.vala',
- 'api/geary-logging.vala',
'api/geary-named-flag.vala',
'api/geary-named-flags.vala',
'api/geary-problem-report.vala',
diff --git a/src/engine/util/util-logging.vala b/src/engine/util/util-logging.vala
index 13f9763b..7048f456 100644
--- a/src/engine/util/util-logging.vala
+++ b/src/engine/util/util-logging.vala
@@ -10,8 +10,51 @@
namespace Geary.Logging {
- internal Gee.Set<string> suppressed_domains;
+ /** The logging domain for the engine. */
+ public const string DOMAIN = "Geary";
+ /** Specifies the default number of log records retained. */
+ public const uint DEFAULT_MAX_LOG_BUFFER_LENGTH = 4096;
+
+
+ /** Specifies the function signature for {@link set_log_listener}. */
+ public delegate void LogRecord(Record record);
+
+
+ private bool was_init = false;
+
+ // The two locks below can't be nullable. See
+ // https://gitlab.gnome.org/GNOME/vala/issues/812
+
+ private GLib.Mutex record_lock;
+ private Record? first_record = null;
+ private Record? last_record = null;
+ private uint log_length = 0;
+ private uint max_log_length = 0;
+ private unowned LogRecord? listener = null;
+
+ private GLib.Mutex writer_lock;
+ private unowned FileStream? stream = null;
+
+ private Gee.Set<string> suppressed_domains;
+
+
+ /**
+ * Must be called before ''any'' call to the Logging namespace.
+ *
+ * This will be initialized by the Engine when it's opened, but
+ * applications may want to set up logging before that, in which case,
+ * call this directly.
+ */
+ public void init() {
+ if (!Logging.was_init) {
+ Logging.was_init = true;
+ Logging.suppressed_domains = new Gee.HashSet<string>();
+ Logging.record_lock = GLib.Mutex();
+ Logging.writer_lock = GLib.Mutex();
+ Logging.max_log_length = DEFAULT_MAX_LOG_BUFFER_LENGTH;
+ }
+ }
/**
* Suppresses debug logging for a given logging domain.
@@ -34,6 +77,215 @@ namespace Geary.Logging {
Logging.suppressed_domains.remove(domain);
}
+ /**
+ * Clears all log records.
+ *
+ * Since log records hold references to Geary engine objects, it may
+ * be desirable to clear the records prior to shutdown so that the
+ * objects can be destroyed.
+ */
+ public void clear() {
+ // Keep the old first record so we don't cause any records to be
+ // finalised while under the lock, leading to deadlock if
+ // finalisation causes any more logging to be generated.
+ Record? old_first = null;
+
+ // Obtain a lock since other threads could be calling this or
+ // generating more logging at the same time.
+ Logging.record_lock.lock();
+ old_first = first_record;
+ Logging.first_record = null;
+ Logging.last_record = null;
+ Logging.log_length = 0;
+ Logging.record_lock.unlock();
+
+ // Manually clear each log record in a loop so that finalisation
+ // of each is an iterative process. If we just nulled out the
+ // record, finalising the first would cause second to be
+ // finalised, which would finalise the third, etc., and the
+ // recursion could cause the stack to blow right out for large log
+ // buffers.
+ while (old_first != null) {
+ old_first = old_first.next;
+ }
+ }
+
+ /** Sets a function to be called when a new log record is created. */
+ public void set_log_listener(LogRecord? new_listener) {
+ Logging.listener = new_listener;
+ }
+
+ /** Returns the oldest log record in the logging system's buffer. */
+ public Record? get_earliest_record() {
+ return Logging.first_record;
+ }
+
+ /** Returns the most recent log record in the logging system's buffer. */
+ public Record? get_latest_record() {
+ return Logging.last_record;
+ }
+
+ /**
+ * Registers a destination for log output from {@link default_log_writer}.
+ *
+ * If stream is null, no logging occurs (the default). If non-null
+ * and the stream was previously null, all pending log records
+ * will be output before proceeding.
+ *
+ * This only has effect if {@link default_log_writer} has been set
+ * as the GLib structured log writer via a call to {@link
+ * GLib.Log.set_writer_func}.
+ */
+ public void log_to(GLib.FileStream? stream) {
+ bool catch_up = (stream != null && Logging.stream == null);
+ Logging.stream = stream;
+ if (catch_up) {
+ Record? record = Logging.first_record;
+ while (record != null) {
+ write_record(record, record.levels);
+ record = record.next;
+ }
+ }
+ }
+
+
+ /**
+ * A log writer function for printing GLib structured logging.
+ *
+ * This only has effect if set as the GLib structured log writer
+ * via a call to {@link GLib.Log.set_writer_func} and a non-null
+ * stream has been passed to {@link log_to}.
+ */
+ public GLib.LogWriterOutput default_log_writer(GLib.LogLevelFlags levels,
+ GLib.LogField[] fields) {
+ Record record = new Record(fields, levels, GLib.get_real_time());
+ if (!should_blacklist(record)) {
+ // Keep the old first record so we don't cause any records
+ // to be finalised while under the lock, leading to
+ // deadlock if finalisation causes any more logging to be
+ // generated.
+ Record? old_record = null;
+
+ // Update the record linked list. Obtain a lock since multiple
+ // threads could be calling this function at the same time.
+ Logging.record_lock.lock();
+ old_record = first_record;
+ if (Logging.first_record == null) {
+ Logging.first_record = record;
+ Logging.last_record = record;
+ } else {
+ Logging.last_record.next = record;
+ Logging.last_record = record;
+ }
+ // Drop the first if we are already at maximum length
+ if (Logging.log_length == Logging.max_log_length) {
+ Logging.first_record = Logging.first_record.next;
+ } else {
+ Logging.log_length++;
+ }
+ Logging.record_lock.unlock();
+
+ // Now that we are out of the lock, it is safe to finalise any old
+ // records.
+ old_record = null;
+
+ // Ensure the listener is updated on the main loop only, since
+ // this could be getting called from other threads.
+ if (Logging.listener != null) {
+ GLib.MainContext.default().invoke(() => {
+ Logging.listener(record);
+ return GLib.Source.REMOVE;
+ });
+ }
+
+ write_record(record, levels);
+ }
+
+ return HANDLED;
+ }
+
+ private bool should_blacklist(Record record) {
+ const string DOMAIN_PREFIX = Logging.DOMAIN + ".";
+ return (
+ // Don't need to check for the engine's domains, they were
+ // already handled by Source's methods.
+ (record.domain != Logging.DOMAIN &&
+ !record.domain.has_prefix(DOMAIN_PREFIX) &&
+ record.domain in Logging.suppressed_domains) ||
+ // GAction does not support disabling parameterised actions
+ // with specific values, but GTK complains if the parameter is
+ // set to null to achieve the same effect, and they aren't
+ // interested in supporting that: GNOME/gtk!1151
+ (record.levels == GLib.LogLevelFlags.LEVEL_WARNING &&
+ record.domain == "Gtk" &&
+ record.message.has_prefix("actionhelper:") &&
+ record.message.has_suffix("target type NULL)"))
+ );
+ }
+
+ private inline void write_record(Record record,
+ GLib.LogLevelFlags levels) {
+ // Print a log message to the stream if configured, or if the
+ // priority is high enough.
+ unowned FileStream? out = Logging.stream;
+ if (out != null ||
+ GLib.LogLevelFlags.LEVEL_WARNING in levels ||
+ GLib.LogLevelFlags.LEVEL_CRITICAL in levels ||
+ GLib.LogLevelFlags.LEVEL_ERROR in levels) {
+
+ if (out == null) {
+ out = GLib.stderr;
+ }
+
+ // Lock the writer here so two different threads don't
+ // interleave their lines.
+ Logging.writer_lock.lock();
+ out.puts(record.format());
+ out.putc('\n');
+ Logging.writer_lock.unlock();
+ }
+ }
+
+
+ private inline string to_prefix(GLib.LogLevelFlags level) {
+ switch (level) {
+ case LEVEL_ERROR:
+ return "![err]";
+
+ case LEVEL_CRITICAL:
+ return "![crt]";
+
+ case LEVEL_WARNING:
+ return "*[wrn]";
+
+ case LEVEL_MESSAGE:
+ return " [msg]";
+
+ case LEVEL_INFO:
+ return " [inf]";
+
+ case LEVEL_DEBUG:
+ return " [deb]";
+
+ case LEVEL_MASK:
+ return "![***]";
+
+ default:
+ return "![???]";
+
+ }
+ }
+
+ private inline string? field_to_string(GLib.LogField field) {
+ string? value = null;
+ if (field.length < 0) {
+ value = (string) field.value;
+ } else if (field.length > 0) {
+ value = ((string) field.value).substring(0, field.length);
+ }
+ return value;
+ }
+
}
/**
@@ -287,3 +539,211 @@ public class Geary.Logging.State {
}
}
+
+/**
+ * A record of a single message sent to the logging system.
+ *
+ * A record is created for each message logged, and stored in a
+ * limited-length, singly-linked buffer. Applications can retrieve
+ * this by calling {@link get_earliest_record} and then {get_next},
+ * and can be notified of new records via {@link set_log_listener}.
+ */
+public class Geary.Logging.Record {
+
+
+ /** The GLib domain of the log message, if any. */
+ public string? domain { get; private set; default = null; }
+
+ /** Account from which the record originated, if any. */
+ public Account? account { get; private set; default = null; }
+
+ /** Client service from which the record originated, if any. */
+ public ClientService? service { get; private set; default = null; }
+
+ /** Folder from which the record originated, if any. */
+ public Folder? folder { get; private set; default = null; }
+
+ /** The logged message, if any. */
+ public string? message = null;
+
+ /** The source filename, if any. */
+ public string? source_filename = null;
+
+ /** The source filename, if any. */
+ public string? source_line_number = null;
+
+ /** The source function, if any. */
+ public string? source_function = null;
+
+ /** The logged level, if any. */
+ public GLib.LogLevelFlags levels;
+
+ /** Time at which the log message was generated. */
+ public int64 timestamp;
+
+ /** The next log record in the buffer, if any. */
+ public Record? next { get; internal set; default = null; }
+
+ private State[] states;
+ private bool filled = false;
+ private bool old_log_api = false;
+
+
+ internal Record(GLib.LogField[] fields,
+ GLib.LogLevelFlags levels,
+ int64 timestamp) {
+ this.levels = levels;
+ this.timestamp = timestamp;
+ this.old_log_api = (
+ fields.length > 0 &&
+ fields[0].key == "GLIB_OLD_LOG_API"
+ );
+
+ // Since GLib.LogField only retains a weak ref to its value,
+ // find and ref any values we wish to keep around.
+ this.states = new State[fields.length];
+ int state_count = 0;
+ foreach (GLib.LogField field in fields) {
+ switch (field.key) {
+ case "GEARY_LOGGING_SOURCE":
+ this.states[state_count++] =
+ ((Source) field.value).to_logging_state();
+ break;
+
+ case "GLIB_DOMAIN":
+ this.domain = field_to_string(field);
+ break;
+
+ case "MESSAGE":
+ this.message = field_to_string(field);
+ break;
+
+ case "CODE_FILE":
+ this.source_filename = field_to_string(field);
+ break;
+
+ case "CODE_LINE":
+ this.source_line_number = field_to_string(field);
+ break;
+
+ case "CODE_FUNC":
+ this.source_function = field_to_string(field);
+ break;
+ }
+ }
+
+ this.states.length = state_count;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * Copies all properties of the given record except its next
+ * record.
+ */
+ public Record.copy(Record other) {
+ this.domain = other.domain;
+ this.account = other.account;
+ this.service = other.service;
+ this.folder = other.folder;
+ this.message = other.message;
+ this.source_filename = other.source_filename;
+ this.source_line_number = other.source_line_number;
+ this.source_function = other.source_function;
+ this.levels = other.levels;
+ this.timestamp = other.timestamp;
+
+ // Kept null deliberately so that we don't get a stack blowout
+ // copying large record chains and code that does copy records
+ // can copy only a fixed number.
+ // this.next
+
+ this.states = other.states;
+ this.filled = other.filled;
+ this.old_log_api = other.old_log_api;
+ }
+
+
+ /**
+ * Sets the well-known logging source properties.
+ *
+ * Call this before trying to access {@link account}, {@link
+ * folder} and {@link service}. Determining these can be
+ * computationally complex and hence is not done by default.
+ */
+ public void fill_well_known_sources() {
+ if (!this.filled) {
+ foreach (unowned State state in this.states) {
+ GLib.Type type = state.source.get_type();
+ if (type.is_a(typeof(Account))) {
+ this.account = (Account) state.source;
+ } else if (type.is_a(typeof(ClientService))) {
+ this.service = (ClientService) state.source;
+ } else if (type.is_a(typeof(Folder))) {
+ this.folder = (Folder) state.source;
+ }
+ }
+ this.filled = true;
+ }
+ }
+
+ /** Returns a formatted string representation of this record. */
+ public string format() {
+ fill_well_known_sources();
+
+ string domain = this.domain ?? "[no domain]";
+ string message = this.message ?? "[no message]";
+ double float_secs = this.timestamp / 1000.0 / 1000.0;
+ double floor_secs = GLib.Math.floor(float_secs);
+ int ms = (int) GLib.Math.round((float_secs - floor_secs) * 1000.0);
+ GLib.DateTime time = new GLib.DateTime.from_unix_utc(
+ (int64) float_secs
+ ).to_local();
+ GLib.StringBuilder str = new GLib.StringBuilder.sized(128);
+ str.printf(
+ "%s %02d:%02d:%02d.%04d %s:",
+ to_prefix(levels),
+ time.get_hour(),
+ time.get_minute(),
+ time.get_second(),
+ ms,
+ domain
+ );
+
+ // Append in reverse so inner sources appear first
+ for (int i = this.states.length - 1; i >= 0; i--) {
+ str.append(" [");
+ str.append(this.states[i].format_message());
+ str.append("]");
+ }
+
+ // XXX Don't append source details for the moment because of
+ // https://gitlab.gnome.org/GNOME/vala/issues/815
+ bool disabled = true;
+ if (!disabled && !this.old_log_api && this.source_filename != null) {
+ str.append(" [");
+ str.append(GLib.Path.get_basename(this.source_filename));
+ if (this.source_line_number != null) {
+ str.append_c(':');
+ str.append(this.source_line_number);
+ }
+ if (this.source_function != null) {
+ str.append_c(':');
+ str.append(this.source_function.to_string());
+ }
+ str.append("]");
+ } else if (this.states.length > 0) {
+ // Print the class name of the leaf logging source to at
+ // least give a general idea of where the message came
+ // from.
+ str.append(" ");
+ str.append(this.states[0].source.get_type().name());
+ str.append(": ");
+ }
+
+ str.append(message);
+
+ return str.str;
+ }
+
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]