2 * Copyright (C) 2010 Collabora Ltd.
4 * This library is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 2.1 of the License, or
7 * (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
18 * Philip Withnall <philip.withnall@collabora.co.uk>
24 /* We have to declare our own wrapping of g_log so that it doesn't have the
25 * [Diagnostics] attribute, which would cause valac to add the Vala source file
26 * name to the message format, which we don't want. This is used in
27 * Debug.print_line(). */
29 private extern void g_log (string? log_domain,
30 LogLevelFlags log_level,
35 * Manage debug output and status reporting for all folks objects. All GLib
36 * debug logging calls are passed through a log handler in this class, which
37 * allows debug domains to be outputted according to whether they've been
38 * enabled by being passed to {@link Debug.dup}.
42 public class Folks.Debug : Object
44 private enum Domains {
45 /* Zero is used for "no debug spew" */
47 TELEPATHY_BACKEND = 1 << 1,
48 KEY_FILE_BACKEND = 1 << 2
51 /* Needs to be locked when accessed: */
52 private static weak Debug? _instance = null;
53 private HashSet<string> _domains; /* needs to be locked when accessed */
54 private bool _all = false; /* needs _domains to be locked when accessed */
56 /* The current indentation level, in spaces */
57 private uint _indentation = 0;
58 private string _indentation_string = "";
60 private bool _colour_enabled = true;
61 private HashSet<string> _domains_handled;
64 * Whether colour output is enabled. If true, debug output may include
65 * terminal colour escape codes. Disabled by the environment variable
66 * FOLKS_DEBUG_NO_COLOUR being set to anything except “0”.
68 * This property is thread-safe.
72 public bool colour_enabled
76 lock (this._colour_enabled)
78 return this._colour_enabled;
84 lock (this._colour_enabled)
86 this._colour_enabled = value;
91 private bool _debug_output_enabled = true;
94 * Whether debug output is enabled. This is orthogonal to the set of enabled
95 * debug domains; filtering of debug output as a whole is done after filtering
100 public bool debug_output_enabled
104 lock (this._debug_output_enabled)
106 return this._debug_output_enabled;
112 lock (this._debug_output_enabled)
114 this._debug_output_enabled = value;
120 * Signal emitted in the main thread whenever objects should print their
121 * current status. All significant objects in the library should connect
122 * to this and print their current status in some suitable format when it's
125 * Client processes should emit this signal by calling
126 * {@link Debug.emit_print_status}.
130 public signal void print_status ();
133 * Log domain for the status messages logged as a result of calling
134 * {@link Debug.emit_print_status}.
136 * This could be used in conjunction with a log handler to redirect the
137 * status information to a debug window or log file, for example.
141 public const string STATUS_LOG_DOMAIN = "folks-status";
143 private void _print_status_log_handler_cb (string? log_domain,
144 LogLevelFlags log_levels,
147 /* Print directly to stdout without any adornments */
148 GLib.stdout.printf ("%s\n", message);
151 private void _log_handler_cb (string? log_domain,
152 LogLevelFlags log_levels,
155 if (this.debug_output_enabled == false)
157 /* Don't output anything if debug output is disabled, even for
158 * enabled debug domains. */
162 /* Otherwise, pass through to the default log handler */
163 Log.default_handler (log_domain, log_levels, message);
166 /* turn off debug output for the given domain unless it was in the FOLKS_DEBUG
167 * environment variable (or 'all' was set) */
168 internal void _register_domain (string domain)
172 if (this._all || this._domains.contains (domain.down ()))
174 this._set_handler (domain, LogLevelFlags.LEVEL_MASK,
175 this._log_handler_cb);
180 /* Install a log handler which will blackhole the log message.
181 * Other log messages will be printed out by the default log handler. */
182 this._set_handler (domain, LogLevelFlags.LEVEL_DEBUG,
183 (domain_arg, flags, message) => {});
187 * Create or return the singleton {@link Debug} class instance.
188 * If the instance doesn't exist already, it will be created with no debug
191 * This function is thread-safe.
193 * @return Singleton {@link Debug} instance
196 public static Debug dup ()
198 lock (Debug._instance)
200 Debug? _retval = Debug._instance;
205 /* use an intermediate variable to force a strong reference */
206 retval = new Debug ();
207 Debug._instance = retval;
211 retval = (!) _retval;
219 * Create or return the singleton {@link Debug} class instance.
220 * If the instance doesn't exist already, it will be created with the given
221 * set of debug domains enabled. Otherwise, the existing instance will have
222 * its set of enabled domains changed to the provided set.
224 * @param debug_flags A comma-separated list of debug domains to enable, or
225 * null to disable debug output
226 * @param colour_enabled Whether debug output should be coloured using
227 * terminal escape sequences
228 * @return Singleton {@link Debug} instance
231 public static Debug dup_with_flags (string? debug_flags,
234 var retval = Debug.dup ();
236 lock (retval._domains)
239 retval._domains = new HashSet<string> (str_hash, str_equal);
241 if (debug_flags != null && debug_flags != "")
243 var domains_split = ((!) debug_flags).split (",");
244 foreach (var domain in domains_split)
246 var domain_lower = domain.down ();
248 if (GLib.strcmp (domain_lower, "all") == 0)
251 retval._domains.add (domain_lower);
256 retval.colour_enabled = colour_enabled;
258 /* Unconditionally enable all G_MESSAGES_DEBUG domains, or GLib's default
259 * log handler will drop all our output. We don't spawn any subprocesses,
260 * so this shouldn't leak and cause problems elsewhere. */
261 Environment.set_variable ("G_MESSAGES_DEBUG", "all", true);
268 /* Private constructor for singleton */
274 this._domains_handled = new HashSet<string> ();
276 /* Install a log handler for log messages emitted as a result of
277 * Debug.print-status being emitted. */
278 this._set_handler (Debug.STATUS_LOG_DOMAIN, LogLevelFlags.LEVEL_MASK,
279 this._print_status_log_handler_cb);
284 /* Remove handlers so they don't get called after we're destroyed */
285 foreach (var domain in this._domains_handled)
286 this._remove_handler (domain, true);
287 this._domains_handled.clear ();
289 /* Manually clear the singleton _instance */
290 lock (Debug._instance)
292 Debug._instance = null;
296 private void _set_handler (
301 this._remove_handler (domain);
302 Log.set_handler (domain, flags, log_func);
303 this._domains_handled.add (domain);
306 private void _remove_handler (string domain, bool keep_in_map = false)
308 if (this._domains_handled.contains (domain))
310 Log.set_handler (domain,
311 (LogLevelFlags.LEVEL_MASK | LogLevelFlags.FLAG_RECURSION |
312 LogLevelFlags.FLAG_FATAL),
313 Log.default_handler);
316 this._domains_handled.remove (domain);
321 * Causes all significant objects in the library to print their current
322 * status to standard output, obeying the options set on this
323 * {@link Debug} instance for colouring and other formatting.
327 public void emit_print_status ()
329 print ("Dumping status information…\n");
330 this.print_status ();
334 * Increment the indentation level used when printing output through the
337 * This is intended to be used by backend libraries only.
341 public void indent ()
343 /* We indent in increments of two spaces */
345 this._indentation_string = string.nfill (this._indentation * 2, ' ');
349 * Decrement the indentation level used when printing output through the
352 * This is intended to be used by backend libraries only.
356 public void unindent ()
359 this._indentation_string = string.nfill (this._indentation * 2, ' ');
363 * Print a debug line with the current indentation level for the specified
366 * This is intended to be used by backend libraries only.
368 * @param domain The debug domain name
369 * @param level A set of log level flags for the message
370 * @param format A printf-style format string for the heading
371 * @param ... Arguments for the format string
375 public void print_line (string domain,
380 /* FIXME: store the va_list temporarily to work around bgo#638308 */
381 var valist = va_list ();
382 string output = format.vprintf (valist);
383 g_log (domain, level, "%s%s", this._indentation_string, output);
387 * Print a debug line as a heading. It will be coloured according to the
388 * current indentation level so that different levels of headings stand out.
390 * This is intended to be used by backend libraries only.
392 * @param domain The debug domain name
393 * @param level A set of log level flags for the message
394 * @param format A printf-style format string for the heading
395 * @param ... Arguments for the format string
399 public void print_heading (string domain,
404 /* Colour the heading according to the current indentation level.
405 * ANSI terminal colour codes. */
406 const int[] heading_colours =
413 var wrapper_format = "%s";
414 if (this.colour_enabled == true)
417 this._indentation.clamp (0, heading_colours.length - 1);
419 "\033[1;%im%%s\033[0m".printf (heading_colours[indentation]);
422 /* FIXME: store the va_list temporarily to work around bgo#638308 */
423 var valist = va_list ();
424 string output = format.vprintf (valist);
425 this.print_line (domain, level, wrapper_format, output);
429 * Format a potentially null string for printing; if the string is null,
430 * “(null)” will be outputted. If coloured output is enabled, this output
431 * will be coloured brown. */
432 private string _format_nullable_string (string? input)
434 if (this.colour_enabled == true && input == null)
436 return "\033[1;36m(null)\033[0m"; /* cyan */
438 else if (input == null)
453 * Print a set of key–value pairs in a table. The width of the key column is
454 * automatically set to the width of the longest key. The keys and values
455 * must be provided as a null-delimited list of alternating key–value varargs.
456 * Values may be null but keys may not.
458 * This is intended to be used by backend libraries only.
460 * The table will be printed at the current indentation level plus one.
462 * @param domain The debug domain name
463 * @param level A set of log level flags for the message
464 * @param ... Alternating keys and values, terminated with null
467 public void print_key_value_pairs (string domain,
471 var valist = va_list ();
472 KeyValuePair[] lines = {};
473 uint max_key_length = 0;
475 /* Read in the arguments and calculate the longest key for alignment
479 string? _key = valist.arg ();
486 string? val = valist.arg ();
488 /* Keep track of the longest key we've seen */
489 max_key_length = uint.max (key.length, max_key_length);
491 lines += KeyValuePair ()
500 /* Print out the lines */
501 foreach (var line in lines)
503 var padding = string.nfill (max_key_length - line.key.length, ' ');
504 this.print_line (domain, level, "%s: %s%s", line.key, padding,
505 this._format_nullable_string (line.val));