Post-release version bump
[platform/upstream/folks.git] / folks / debug.vala
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  *
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.
8  *
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.
13  *
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/>.
16  *
17  * Authors:
18  *       Philip Withnall <philip.withnall@collabora.co.uk>
19  */
20
21 using GLib;
22 using Gee;
23
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(). */
28 [PrintfFormat]
29 private extern void g_log (string? log_domain,
30     LogLevelFlags log_level,
31     string format,
32     ...);
33
34 /**
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}.
39  *
40  * @since 0.5.1
41  */
42 public class Folks.Debug : Object
43 {
44   private enum Domains {
45     /* Zero is used for "no debug spew" */
46     CORE = 1 << 0,
47     TELEPATHY_BACKEND = 1 << 1,
48     KEY_FILE_BACKEND = 1 << 2
49   }
50
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 */
55
56   /* The current indentation level, in spaces */
57   private uint _indentation = 0;
58   private string _indentation_string = "";
59
60   private bool _colour_enabled = true;
61   private HashSet<string> _domains_handled;
62
63   /*
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”.
67    *
68    * This property is thread-safe.
69    *
70    * @since 0.5.1
71    */
72   public bool colour_enabled
73     {
74       get
75         {
76           lock (this._colour_enabled)
77             {
78               return this._colour_enabled;
79             }
80         }
81
82       set
83         {
84           lock (this._colour_enabled)
85             {
86               this._colour_enabled = value;
87             }
88         }
89     }
90
91   private bool _debug_output_enabled = true;
92
93   /**
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
96    * by enabled domains.
97    *
98    * @since 0.5.1
99    */
100   public bool debug_output_enabled
101     {
102       get
103         {
104           lock (this._debug_output_enabled)
105             {
106               return this._debug_output_enabled;
107             }
108         }
109
110       set
111         {
112           lock (this._debug_output_enabled)
113             {
114               this._debug_output_enabled = value;
115             }
116         }
117     }
118
119   /**
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
123    * emitted.
124    *
125    * Client processes should emit this signal by calling
126    * {@link Debug.emit_print_status}.
127    *
128    * @since 0.5.1
129    */
130   public signal void print_status ();
131
132   /**
133    * Log domain for the status messages logged as a result of calling
134    * {@link Debug.emit_print_status}.
135    *
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.
138    *
139    * @since 0.5.1
140    */
141   public const string STATUS_LOG_DOMAIN = "folks-status";
142
143   private void _print_status_log_handler_cb (string? log_domain,
144       LogLevelFlags log_levels,
145       string message)
146     {
147       /* Print directly to stdout without any adornments */
148       GLib.stdout.printf ("%s\n", message);
149     }
150
151   private void _log_handler_cb (string? log_domain,
152       LogLevelFlags log_levels,
153       string message)
154     {
155       if (this.debug_output_enabled == false)
156         {
157           /* Don't output anything if debug output is disabled, even for
158            * enabled debug domains. */
159           return;
160         }
161
162       /* Otherwise, pass through to the default log handler */
163       Log.default_handler (log_domain, log_levels, message);
164     }
165
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)
169     {
170       lock (this._domains)
171         {
172           if (this._all || this._domains.contains (domain.down ()))
173             {
174               this._set_handler (domain, LogLevelFlags.LEVEL_MASK,
175                   this._log_handler_cb);
176               return;
177             }
178         }
179
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) => {});
184     }
185
186   /**
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
189    * domains enabled.
190    *
191    * This function is thread-safe.
192    *
193    * @return  Singleton {@link Debug} instance
194    * @since 0.5.1
195    */
196   public static Debug dup ()
197     {
198       lock (Debug._instance)
199         {
200           Debug? _retval = Debug._instance;
201           Debug retval;
202
203           if (_retval == null)
204             {
205               /* use an intermediate variable to force a strong reference */
206               retval = new Debug ();
207               Debug._instance = retval;
208             }
209           else
210             {
211               retval = (!) _retval;
212             }
213
214           return retval;
215         }
216     }
217
218   /**
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.
223    *
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
229    * @since 0.5.1
230    */
231   public static Debug dup_with_flags (string? debug_flags,
232       bool colour_enabled)
233     {
234       var retval = Debug.dup ();
235
236       lock (retval._domains)
237         {
238           retval._all = false;
239           retval._domains = new HashSet<string> (str_hash, str_equal);
240
241           if (debug_flags != null && debug_flags != "")
242             {
243               var domains_split = ((!) debug_flags).split (",");
244               foreach (var domain in domains_split)
245                 {
246                   var domain_lower = domain.down ();
247
248                   if (GLib.strcmp (domain_lower, "all") == 0)
249                     retval._all = true;
250                   else
251                     retval._domains.add (domain_lower);
252                 }
253             }
254         }
255
256       retval.colour_enabled = colour_enabled;
257
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);
262
263       return retval;
264     }
265
266   private Debug ()
267     {
268       /* Private constructor for singleton */
269       Object ();
270     }
271
272   construct
273     {
274       this._domains_handled = new HashSet<string> ();
275
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);
280     }
281
282   ~Debug ()
283     {
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 ();
288
289       /* Manually clear the singleton _instance */
290       lock (Debug._instance)
291         {
292           Debug._instance = null;
293         }
294     }
295
296   private void _set_handler (
297       string domain,
298       LogLevelFlags flags,
299       LogFunc log_func)
300     {
301       this._remove_handler (domain);
302       Log.set_handler (domain, flags, log_func);
303       this._domains_handled.add (domain);
304     }
305
306   private void _remove_handler (string domain, bool keep_in_map = false)
307     {
308       if (this._domains_handled.contains (domain))
309         {
310           Log.set_handler (domain,
311               (LogLevelFlags.LEVEL_MASK | LogLevelFlags.FLAG_RECURSION |
312                   LogLevelFlags.FLAG_FATAL),
313               Log.default_handler);
314
315           if (!keep_in_map)
316             this._domains_handled.remove (domain);
317         }
318     }
319
320   /**
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.
324    *
325    * @since 0.5.1
326    */
327   public void emit_print_status ()
328     {
329       print ("Dumping status information…\n");
330       this.print_status ();
331     }
332
333   /**
334    * Increment the indentation level used when printing output through the
335    * object.
336    *
337    * This is intended to be used by backend libraries only.
338    *
339    * @since 0.5.1
340    */
341   public void indent ()
342     {
343       /* We indent in increments of two spaces */
344       this._indentation++;
345       this._indentation_string = string.nfill (this._indentation * 2, ' ');
346     }
347
348   /**
349    * Decrement the indentation level used when printing output through the
350    * object.
351    *
352    * This is intended to be used by backend libraries only.
353    *
354    * @since 0.5.1
355    */
356   public void unindent ()
357     {
358       this._indentation--;
359       this._indentation_string = string.nfill (this._indentation * 2, ' ');
360     }
361
362   /**
363    * Print a debug line with the current indentation level for the specified
364    * debug domain.
365    *
366    * This is intended to be used by backend libraries only.
367    *
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
372    * @since 0.5.1
373    */
374   [PrintfFormat ()]
375   public void print_line (string domain,
376       LogLevelFlags level,
377       string format,
378       ...)
379     {
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);
384     }
385
386   /**
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.
389    *
390    * This is intended to be used by backend libraries only.
391    *
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
396    * @since 0.5.1
397    */
398   [PrintfFormat ()]
399   public void print_heading (string domain,
400       LogLevelFlags level,
401       string format,
402       ...)
403     {
404       /* Colour the heading according to the current indentation level.
405        * ANSI terminal colour codes. */
406       const int[] heading_colours =
407         {
408           31, /* red */
409           32, /* green */
410           34 /* blue */
411         };
412
413       var wrapper_format = "%s";
414       if (this.colour_enabled == true)
415         {
416           var indentation =
417               this._indentation.clamp (0, heading_colours.length - 1);
418           wrapper_format =
419               "\033[1;%im%%s\033[0m".printf (heading_colours[indentation]);
420         }
421
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);
426     }
427
428   /*
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)
433     {
434       if (this.colour_enabled == true && input == null)
435         {
436           return "\033[1;36m(null)\033[0m"; /* cyan */
437         }
438       else if (input == null)
439         {
440           return "(null)";
441         }
442
443       return (!) input;
444     }
445
446   struct KeyValuePair
447     {
448       string key;
449       string? val;
450     }
451
452   /**
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.
457    *
458    * This is intended to be used by backend libraries only.
459    *
460    * The table will be printed at the current indentation level plus one.
461    *
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
465    * @since 0.5.1
466    */
467   public void print_key_value_pairs (string domain,
468       LogLevelFlags level,
469       ...)
470     {
471       var valist = va_list ();
472       KeyValuePair[] lines = {};
473       uint max_key_length = 0;
474
475       /* Read in the arguments and calculate the longest key for alignment
476        * purposes */
477       while (true)
478         {
479           string? _key = valist.arg ();
480           if (_key == null)
481             {
482               break;
483             }
484           var key = (!) _key;
485
486           string? val = valist.arg ();
487
488           /* Keep track of the longest key we've seen */
489           max_key_length = uint.max (key.length, max_key_length);
490
491           lines += KeyValuePair ()
492             {
493               key = key,
494               val = val
495             };
496         }
497
498       this.indent ();
499
500       /* Print out the lines */
501       foreach (var line in lines)
502         {
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));
506         }
507
508       this.unindent ();
509     }
510 }