1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
19 // Logging facility collects all messages from the program itself and libraries
20 // (mainly GLib and libpurple). It filters them by severity and then outputs
21 // them to the Log window, a file or on the standard error output (stderr).
23 // Processing consists of three phases: initialization -> normal phase ->
24 // finalization, and is tightly tied to the CenterIM class. The program can exit
25 // in any of these phases.
27 // 1) Initialization: All messages are being buffered. If an error occurs
28 // during this phase, the recorded messages are printed on stderr before the
30 // 2) Normal phase: When processing reaches this phase (implying no critical
31 // error occurred during the initialization phase), messages recorded by
32 // the initialization phase are output to the Log window and optionally to a
33 // file. New messages coming from the normal processing of the program are
34 // then output in the same way. A limited number of the most recent messages
35 // is also being buffered by this phase. If the program exists from the main
36 // loop normally, the recorded messages are cleared.
37 // 3) Finalization: All messages are being buffered and when the program is
38 // about to exit, they will get printed on stderr together with any buffered
39 // messages from the previous stage.
41 // This mechanism ensures that if a non-recoverable error occurs during
42 // execution of the program and it must exit abnormally, the error will always
43 // get printed on stderr.
48 #include <cppconsui/HorizontalListBox.h>
49 #include <cppconsui/Spacer.h>
53 // Maximum number of lines in the Log window.
54 #define LOG_WINDOW_MAX_LINES 200
55 // Number of deleted lines when the line limit in the Log window is reached.
56 #define LOG_WINDOW_DELETE_COUNT 40
58 // Maximum number of buffered messages in the normal phase.
59 #define LOG_MAX_BUFFERED_MESSAGES 200
61 Log
*Log::my_instance_
= nullptr;
68 #define WRITE_METHOD(name, level) \
69 void Log::name(const char *fmt, ...) \
74 if (log_level_cim_ < level) \
75 return; /* Do not show this message. */ \
77 va_start(args, fmt); \
78 text = g_strdup_vprintf(fmt, args); \
81 write(TYPE_CIM, level, text); \
85 WRITE_METHOD(error
, LEVEL_ERROR
)
86 WRITE_METHOD(critical
, LEVEL_CRITICAL
)
87 WRITE_METHOD(warning
, LEVEL_WARNING
)
88 WRITE_METHOD(message
, LEVEL_MESSAGE
)
89 WRITE_METHOD(info
, LEVEL_INFO
)
90 WRITE_METHOD(debug
, LEVEL_DEBUG
)
94 void Log::clearAllBufferedMessages()
96 // Delete all buffered messages.
97 clearBufferedMessages(init_log_items_
);
98 clearBufferedMessages(log_items_
);
101 Log::LogWindow::LogWindow() : Window(0, 0, 80, 24, nullptr, TYPE_NON_FOCUSABLE
)
103 setColorScheme(CenterIM::SCHEME_LOG
);
105 auto lbox
= new CppConsUI::HorizontalListBox(AUTOSIZE
, AUTOSIZE
);
106 addWidget(*lbox
, 1, 1);
108 lbox
->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE
)));
109 textview_
= new CppConsUI::TextView(AUTOSIZE
, AUTOSIZE
, true);
110 lbox
->appendWidget(*textview_
);
111 lbox
->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE
)));
116 void Log::LogWindow::onScreenResized()
118 moveResizeRect(CENTERIM
->getScreenArea(CenterIM::LOG_AREA
));
121 void Log::LogWindow::append(const char *text
)
123 textview_
->append(text
);
125 // Shorten the window text.
126 std::size_t lines_num
= textview_
->getLinesNumber();
128 if (lines_num
> LOG_WINDOW_MAX_LINES
) {
129 // Remove some lines.
131 0, lines_num
- LOG_WINDOW_MAX_LINES
+ LOG_WINDOW_DELETE_COUNT
);
135 Log::LogBufferItem::LogBufferItem(Type type
, Level level
, const char *text
)
136 : type_(type
), level_(level
)
138 text_
= g_strdup(text
);
141 Log::LogBufferItem::~LogBufferItem()
147 : phase_(PHASE_INITIALIZATION
), log_window_(nullptr), logfile_(nullptr),
148 log_level_cim_(LEVEL_DEBUG
), log_level_glib_(LEVEL_DEBUG
),
149 log_level_purple_(LEVEL_DEBUG
)
151 #define REGISTER_G_LOG_HANDLER(name, handler) \
152 g_log_set_handler((name), (GLogLevelFlags)G_LOG_LEVEL_MASK, (handler), this)
154 // Register the glib log handlers.
155 default_handler_
= REGISTER_G_LOG_HANDLER(nullptr, default_log_handler_
);
156 glib_handler_
= REGISTER_G_LOG_HANDLER("GLib", glib_log_handler_
);
157 gmodule_handler_
= REGISTER_G_LOG_HANDLER("GModule", glib_log_handler_
);
158 glib_gobject_handler_
=
159 REGISTER_G_LOG_HANDLER("GLib-GObject", glib_log_handler_
);
160 gthread_handler_
= REGISTER_G_LOG_HANDLER("GThread", glib_log_handler_
);
165 g_log_remove_handler(nullptr, default_handler_
);
166 g_log_remove_handler("GLib", glib_handler_
);
167 g_log_remove_handler("GModule", gmodule_handler_
);
168 g_log_remove_handler("GLib-GObject", glib_gobject_handler_
);
169 g_log_remove_handler("GThread", gthread_handler_
);
171 outputAllBufferedMessages();
172 clearAllBufferedMessages();
177 g_assert(my_instance_
== nullptr);
179 my_instance_
= new Log
;
184 g_assert(my_instance_
!= nullptr);
187 my_instance_
= nullptr;
190 void Log::initNormalPhase()
192 // Normal phase is called after CppConsUI and libpurple has been initialized.
193 // Logging to file is part of the normal phase.
194 g_assert(phase_
== PHASE_INITIALIZATION
);
197 purple_prefs_add_none(CONF_PREFIX
"/log");
198 purple_prefs_add_bool(CONF_PREFIX
"/log/debug", false);
199 purple_prefs_add_string(CONF_PREFIX
"/log/filename", "debug.log");
200 purple_prefs_add_string(CONF_PREFIX
"/log/log_level_cim", "info");
201 purple_prefs_add_string(CONF_PREFIX
"/log/log_level_purple", "critical");
202 purple_prefs_add_string(CONF_PREFIX
"/log/log_level_glib", "warning");
204 updateCachedPreference(CONF_PREFIX
"/log/debug");
205 updateCachedPreference(CONF_PREFIX
"/log/log_level_cim");
206 updateCachedPreference(CONF_PREFIX
"/log/log_level_purple");
207 updateCachedPreference(CONF_PREFIX
"/log/log_level_glib");
209 // Connect callbacks.
210 purple_prefs_connect_callback(
211 this, CONF_PREFIX
"/log", log_pref_change_
, this);
213 // Create the log window.
214 g_assert(log_window_
== nullptr);
215 log_window_
= new LogWindow
;
218 // Normal phase is now active.
219 phase_
= PHASE_NORMAL
;
221 // Output buffered messages from the initialization phase.
222 for (LogBufferItem
*item
: init_log_items_
) {
223 // Determine if this message should be displayed.
224 Level loglevel
= getLogLevel(item
->getType());
226 if (loglevel
>= item
->getLevel())
227 write(item
->getType(), item
->getLevel(), item
->getText(), false);
231 init_log_items_
.clear();
234 void Log::finalizeNormalPhase()
236 // Normal phase finalization is done before CppConsUI and libpurple is
237 // finalized. Note that log levels are unchanged after the normal phase
239 g_assert(phase_
== PHASE_NORMAL
);
241 // Delete the log window.
242 g_assert(log_window_
!= nullptr);
244 log_window_
= nullptr;
246 purple_prefs_disconnect_by_handle(this);
248 // Close the log file (if it is opened).
249 if (logfile_
!= nullptr)
250 g_io_channel_unref(logfile_
);
252 // Done with the normal phase.
253 phase_
= PHASE_FINALIZATION
;
256 void Log::purple_print(
257 PurpleDebugLevel purplelevel
, const char *category
, const char *arg_s
)
259 Level level
= convertPurpleDebugLevel(purplelevel
);
260 if (log_level_purple_
< level
)
261 return; // Do not show this message.
263 if (category
== nullptr) {
265 warning(_("centerim/log: purple_print() parameter category was "
269 char *text
= g_strdup_printf("libpurple/%s: %s", category
, arg_s
);
270 write(TYPE_PURPLE
, level
, text
);
274 gboolean
Log::purple_is_enabled(
275 PurpleDebugLevel purplelevel
, const char * /*category*/)
277 Level level
= convertPurpleDebugLevel(purplelevel
);
279 if (log_level_purple_
< level
)
285 void Log::default_log_handler(
286 const char *domain
, GLogLevelFlags flags
, const char *msg
)
291 Level level
= convertGLibDebugLevel(flags
);
292 if (log_level_glib_
< level
)
293 return; // Do not show this message.
295 char *text
= g_strdup_printf("%s: %s", domain
? domain
: "g_log", msg
);
296 write(TYPE_GLIB
, level
, text
);
300 void Log::glib_log_handler(
301 const char *domain
, GLogLevelFlags flags
, const char *msg
)
306 Level level
= convertGLibDebugLevel(flags
);
307 if (log_level_glib_
< level
)
308 return; // Do not show this message.
310 char *text
= g_strdup_printf("%s: %s", domain
? domain
: "g_log", msg
);
311 write(TYPE_GLIB
, level
, text
);
315 void Log::log_pref_change(
316 const char *name
, PurplePrefType
/*type*/, gconstpointer
/*val*/)
318 // log/* preference changed.
319 updateCachedPreference(name
);
322 void Log::updateCachedPreference(const char *name
)
324 if (std::strcmp(name
, CONF_PREFIX
"/log/debug") == 0) {
325 bool logfile_enabled
= purple_prefs_get_bool(name
);
327 if (logfile_enabled
&& logfile_
== nullptr) {
328 char *filename
= g_build_filename(purple_user_dir(),
329 purple_prefs_get_string(CONF_PREFIX
"/log/filename"), nullptr);
330 GError
*err
= nullptr;
332 logfile_
= g_io_channel_new_file(filename
, "a", &err
);
333 if (logfile_
== nullptr) {
334 error(_("centerim/log: Error opening logfile '%s' (%s)."), filename
,
340 else if (!logfile_enabled
&& logfile_
!= nullptr) {
341 // Debug was disabled so close logfile if it is opened.
342 g_io_channel_unref(logfile_
);
346 else if (std::strcmp(name
, CONF_PREFIX
"/log/log_level_cim") == 0)
347 log_level_cim_
= stringToLevel(purple_prefs_get_string(name
));
348 else if (std::strcmp(name
, CONF_PREFIX
"/log/log_level_purple") == 0)
349 log_level_purple_
= stringToLevel(purple_prefs_get_string(name
));
350 else if (std::strcmp(name
, CONF_PREFIX
"/log/log_level_glib") == 0)
351 log_level_glib_
= stringToLevel(purple_prefs_get_string(name
));
354 void Log::write(Type type
, Level level
, const char *text
, bool buffer
)
357 bufferMessage(type
, level
, text
);
359 // If the normal phase is not active then only buffer the message.
360 if (phase_
!= PHASE_NORMAL
)
363 g_assert(log_window_
!= nullptr);
364 log_window_
->append(text
);
368 void Log::writeErrorToWindow(const char *fmt
, ...)
370 // Can be called only if the normal phase is active.
371 g_assert(phase_
== PHASE_NORMAL
);
372 g_assert(log_window_
!= nullptr);
376 if (log_level_cim_
< LEVEL_ERROR
)
377 return; // Do not show this message.
380 char *text
= g_strdup_vprintf(fmt
, args
);
383 log_window_
->append(text
);
387 void Log::writeToFile(const char *text
)
389 // Writing to a file is possible only in the normal phase.
390 g_assert(phase_
== PHASE_NORMAL
);
392 if (text
== nullptr || logfile_
== nullptr)
395 // Write text into logfile.
396 GError
*err
= nullptr;
398 if (g_io_channel_write_chars(logfile_
, text
, -1, nullptr, &err
) !=
399 G_IO_STATUS_NORMAL
) {
401 _("centerim/log: Error writing to logfile (%s)."), err
->message
);
405 // If necessary write missing EOL character.
406 std::size_t len
= std::strlen(text
);
407 if (len
> 0 && text
[len
- 1] != '\n') {
408 // Ignore all errors.
409 g_io_channel_write_chars(logfile_
, "\n", -1, nullptr, nullptr);
413 if (g_io_channel_flush(logfile_
, &err
) != G_IO_STATUS_NORMAL
) {
415 _("centerim/log: Error flushing logfile (%s)."), err
->message
);
420 void Log::bufferMessage(Type type
, Level level
, const char *text
)
422 auto item
= new LogBufferItem(type
, level
, text
);
423 if (phase_
== PHASE_INITIALIZATION
)
424 init_log_items_
.push_back(item
);
426 log_items_
.push_back(item
);
428 if (phase_
!= PHASE_NORMAL
)
431 // Reduce a number of buffered messages if the normal phase is active.
432 std::size_t size
= log_items_
.size();
433 if (size
<= LOG_MAX_BUFFERED_MESSAGES
)
436 // There can never be more than one message to remove.
437 g_assert(size
== LOG_MAX_BUFFERED_MESSAGES
+ 1);
438 delete log_items_
.front();
439 log_items_
.pop_front();
442 void Log::clearBufferedMessages(LogBufferItems
&items
)
444 for (LogBufferItem
*item
: items
)
449 void Log::outputBufferedMessages(LogBufferItems
&items
)
451 for (LogBufferItem
*item
: items
) {
452 // Determine if this message should be displayed.
453 Level loglevel
= getLogLevel(item
->getType());
455 if (loglevel
>= item
->getLevel()) {
456 const char *text
= item
->getText();
457 g_assert(text
!= nullptr);
459 std::fprintf(stderr
, "%s", text
);
461 // If necessary write missing EOL character.
462 std::size_t len
= std::strlen(text
);
463 if (len
> 0 && text
[len
- 1] != '\n')
464 std::fprintf(stderr
, "\n");
469 void Log::outputAllBufferedMessages()
471 // Output all buffered messages on stderr.
472 outputBufferedMessages(init_log_items_
);
473 outputBufferedMessages(log_items_
);
478 Log::Level
Log::convertPurpleDebugLevel(PurpleDebugLevel purplelevel
)
480 switch (purplelevel
) {
481 case PURPLE_DEBUG_MISC
:
483 case PURPLE_DEBUG_INFO
:
485 case PURPLE_DEBUG_WARNING
:
486 return LEVEL_WARNING
;
487 case PURPLE_DEBUG_ERROR
:
488 return LEVEL_CRITICAL
;
489 case PURPLE_DEBUG_FATAL
:
491 case PURPLE_DEBUG_ALL
:
492 return LEVEL_ERROR
; // Use error level so this message is always printed.
496 _("centerim/log: Unknown libpurple logging level '%d'."), purplelevel
);
500 Log::Level
Log::convertGLibDebugLevel(GLogLevelFlags gliblevel
)
502 if (gliblevel
& G_LOG_LEVEL_DEBUG
)
504 if (gliblevel
& G_LOG_LEVEL_INFO
)
506 if (gliblevel
& G_LOG_LEVEL_MESSAGE
)
507 return LEVEL_MESSAGE
;
508 if (gliblevel
& G_LOG_LEVEL_WARNING
)
509 return LEVEL_WARNING
;
510 if (gliblevel
& G_LOG_LEVEL_CRITICAL
)
511 return LEVEL_CRITICAL
;
512 if (gliblevel
& G_LOG_LEVEL_ERROR
)
515 warning(_("centerim/log: Unknown GLib logging level '%d'."), gliblevel
);
519 Log::Level
Log::stringToLevel(const char *slevel
)
521 if (std::strcmp(slevel
, "none") == 0)
523 else if (std::strcmp(slevel
, "debug") == 0)
525 else if (std::strcmp(slevel
, "info") == 0)
527 else if (std::strcmp(slevel
, "message") == 0)
528 return LEVEL_MESSAGE
;
529 else if (std::strcmp(slevel
, "warning") == 0)
530 return LEVEL_WARNING
;
531 else if (std::strcmp(slevel
, "critical") == 0)
532 return LEVEL_CRITICAL
;
533 else if (std::strcmp(slevel
, "error") == 0)
538 Log::Level
Log::getLogLevel(Type type
)
542 return log_level_cim_
;
544 return log_level_glib_
;
546 return log_level_purple_
;
548 g_assert_not_reached();
552 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: