Update AUTHORS file
[centerim5.git] / src / Log.cpp
blob1056fcd42a0c3ef7a646af3e3d6386095f882ace
1 /*
2 * Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
3 * Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
5 * This file is part of CenterIM.
7 * CenterIM is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * CenterIM is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "Log.h"
24 #include <cppconsui/HorizontalListBox.h>
25 #include <cppconsui/Spacer.h>
26 #include <cstdio>
27 #include "gettext.h"
29 Log *Log::my_instance = NULL;
31 Log *Log::instance()
33 return my_instance;
36 #define WRITE_METHOD(name, level) \
37 void Log::name(const char *fmt, ...) \
38 { \
39 va_list args; \
40 char *text; \
42 if (log_level_cim < level) \
43 return; /* we don't want to see this log message */ \
45 va_start(args, fmt); \
46 text = g_strdup_vprintf(fmt, args); \
47 va_end(args); \
49 write(TYPE_CIM, level, text); \
50 g_free(text); \
53 WRITE_METHOD(error, LEVEL_ERROR)
54 WRITE_METHOD(critical, LEVEL_CRITICAL)
55 WRITE_METHOD(warning, LEVEL_WARNING)
56 WRITE_METHOD(message, LEVEL_MESSAGE)
57 WRITE_METHOD(info, LEVEL_INFO)
58 WRITE_METHOD(debug, LEVEL_DEBUG)
60 #undef WRITE_METHOD
62 Log::LogWindow::LogWindow() : Window(0, 0, 80, 24, NULL, TYPE_NON_FOCUSABLE)
64 setColorScheme("log");
66 CppConsUI::HorizontalListBox *lbox =
67 new CppConsUI::HorizontalListBox(AUTOSIZE, AUTOSIZE);
68 addWidget(*lbox, 1, 1);
70 lbox->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE)));
71 textview = new CppConsUI::TextView(AUTOSIZE, AUTOSIZE, true);
72 lbox->appendWidget(*textview);
73 lbox->appendWidget(*(new CppConsUI::Spacer(1, AUTOSIZE)));
75 onScreenResized();
78 void Log::LogWindow::onScreenResized()
80 moveResizeRect(CENTERIM->getScreenArea(CenterIM::LOG_AREA));
83 void Log::LogWindow::append(const char *text)
85 textview->append(text);
87 // shorten the window text
88 size_t lines_num = textview->getLinesNumber();
90 if (lines_num > 200) {
91 // remove 40 extra lines
92 textview->erase(0, lines_num - 200 + 40);
96 Log::LogBufferItem::LogBufferItem(Type type_, Level level_, const char *text_)
97 : type(type_), level(level_)
99 text = g_strdup(text_);
102 Log::LogBufferItem::~LogBufferItem()
104 g_free(text);
107 Log::Log()
108 : log_window(NULL), phase2_active(false), logfile(NULL),
109 log_level_cim(LEVEL_DEBUG), log_level_glib(LEVEL_DEBUG),
110 log_level_purple(LEVEL_DEBUG)
112 #define REGISTER_G_LOG_HANDLER(name, handler) \
113 g_log_set_handler((name), (GLogLevelFlags)G_LOG_LEVEL_MASK, (handler), this)
115 // register the glib log handlers
116 default_handler = REGISTER_G_LOG_HANDLER(NULL, default_log_handler_);
117 glib_handler = REGISTER_G_LOG_HANDLER("GLib", glib_log_handler_);
118 gmodule_handler = REGISTER_G_LOG_HANDLER("GModule", glib_log_handler_);
119 glib_gobject_handler =
120 REGISTER_G_LOG_HANDLER("GLib-GObject", glib_log_handler_);
121 gthread_handler = REGISTER_G_LOG_HANDLER("GThread", glib_log_handler_);
124 Log::~Log()
126 g_log_remove_handler(NULL, default_handler);
127 g_log_remove_handler("GLib", glib_handler);
128 g_log_remove_handler("GModule", gmodule_handler);
129 g_log_remove_handler("GLib-GObject", glib_gobject_handler);
130 g_log_remove_handler("GThread", gthread_handler);
132 outputBufferMessages();
135 void Log::init()
137 g_assert(!my_instance);
139 my_instance = new Log;
142 void Log::finalize()
144 g_assert(my_instance);
146 delete my_instance;
147 my_instance = NULL;
150 void Log::initPhase2()
152 /* Phase 2 is called after CppConsUI and libpurple has been initialized.
153 * Logging to file is part of the phase 2. */
154 g_assert(!phase2_active);
156 // init prefs
157 purple_prefs_add_none(CONF_PREFIX "/log");
158 purple_prefs_add_bool(CONF_PREFIX "/log/debug", false);
159 purple_prefs_add_string(CONF_PREFIX "/log/filename", "debug.log");
160 purple_prefs_add_string(CONF_PREFIX "/log/log_level_cim", "info");
161 purple_prefs_add_string(CONF_PREFIX "/log/log_level_purple", "critical");
162 purple_prefs_add_string(CONF_PREFIX "/log/log_level_glib", "warning");
164 updateCachedPreference(CONF_PREFIX "/log/debug");
165 updateCachedPreference(CONF_PREFIX "/log/log_level_cim");
166 updateCachedPreference(CONF_PREFIX "/log/log_level_purple");
167 updateCachedPreference(CONF_PREFIX "/log/log_level_glib");
169 // connect callbacks
170 purple_prefs_connect_callback(
171 this, CONF_PREFIX "/log", log_pref_change_, this);
173 // create the log window
174 g_assert(!log_window);
175 log_window = new LogWindow;
176 log_window->show();
178 // phase 2 is now active
179 phase2_active = true;
181 // output buffered messages
182 for (LogBufferItems::iterator i = log_items.begin(); i != log_items.end();
183 i++) {
184 LogBufferItem *item = *i;
186 // determine if this message should be displayed
187 Level loglevel = getLogLevel(item->getType());
189 if (loglevel >= item->getLevel())
190 write(item->getType(), item->getLevel(), item->getText());
192 delete item;
194 log_items.clear();
197 void Log::finalizePhase2()
199 /* Phase 2 finalization is done before CppConsUI and libpurple is finalized.
200 * Note that log levels are unchanged after phase 2 finishes. */
201 g_assert(phase2_active);
203 // delete the log window
204 g_assert(log_window);
205 delete log_window;
206 log_window = NULL;
208 purple_prefs_disconnect_by_handle(this);
210 // close the log file (if it is opened)
211 if (logfile)
212 g_io_channel_unref(logfile);
214 // done with phase 2
215 phase2_active = false;
218 void Log::purple_print(
219 PurpleDebugLevel purplelevel, const char *category, const char *arg_s)
221 Level level = convertPurpleDebugLevel(purplelevel);
222 if (log_level_purple < level)
223 return; // we don't want to see this log message
225 if (!category) {
226 category = "misc";
227 warning(_("centerim/log: purple_print() parameter category was "
228 "not defined."));
231 char *text = g_strdup_printf("libpurple/%s: %s", category, arg_s);
232 write(TYPE_PURPLE, level, text);
233 g_free(text);
236 gboolean Log::purple_is_enabled(
237 PurpleDebugLevel purplelevel, const char * /*category*/)
239 Level level = convertPurpleDebugLevel(purplelevel);
241 if (log_level_purple < level)
242 return FALSE;
244 return TRUE;
247 void Log::default_log_handler(
248 const char *domain, GLogLevelFlags flags, const char *msg)
250 if (!msg)
251 return;
253 Level level = convertGLibDebugLevel(flags);
254 if (log_level_glib < level)
255 return; // we don't want to see this log message
257 char *text = g_strdup_printf("%s: %s", domain ? domain : "g_log", msg);
258 write(TYPE_GLIB, level, text);
259 g_free(text);
262 void Log::glib_log_handler(
263 const char *domain, GLogLevelFlags flags, const char *msg)
265 if (!msg)
266 return;
268 Level level = convertGLibDebugLevel(flags);
269 if (log_level_glib < level)
270 return; // we don't want to see this log message
272 char *text = g_strdup_printf("%s: %s", domain ? domain : "g_log", msg);
273 write(TYPE_GLIB, level, text);
274 g_free(text);
277 void Log::log_pref_change(
278 const char *name, PurplePrefType /*type*/, gconstpointer /*val*/)
280 // log/* preference changed
281 updateCachedPreference(name);
284 void Log::updateCachedPreference(const char *name)
286 if (!strcmp(name, CONF_PREFIX "/log/debug")) {
287 bool logfile_enabled = purple_prefs_get_bool(name);
289 if (logfile_enabled && !logfile) {
290 char *filename = g_build_filename(purple_user_dir(),
291 purple_prefs_get_string(CONF_PREFIX "/log/filename"), NULL);
292 GError *err = NULL;
294 if (!(logfile = g_io_channel_new_file(filename, "a", &err))) {
295 error(_("centerim/log: Error opening logfile '%s' (%s)."), filename,
296 err->message);
297 g_clear_error(&err);
299 g_free(filename);
301 else if (!logfile_enabled && logfile) {
302 // debug was disabled so close logfile if it's opened
303 g_io_channel_unref(logfile);
304 logfile = NULL;
307 else if (!strcmp(name, CONF_PREFIX "/log/log_level_cim"))
308 log_level_cim = stringToLevel(purple_prefs_get_string(name));
309 else if (!strcmp(name, CONF_PREFIX "/log/log_level_purple"))
310 log_level_purple = stringToLevel(purple_prefs_get_string(name));
311 else if (!strcmp(name, CONF_PREFIX "/log/log_level_glib"))
312 log_level_glib = stringToLevel(purple_prefs_get_string(name));
315 void Log::write(Type type, Level level, const char *text)
317 // if phase 2 is not active buffer the message
318 if (!phase2_active) {
319 LogBufferItem *item = new LogBufferItem(type, level, text);
320 log_items.push_back(item);
321 return;
324 g_assert(log_window);
325 log_window->append(text);
326 writeToFile(text);
329 void Log::writeErrorToWindow(const char *fmt, ...)
331 // can be called only if phase 2 is active
332 g_assert(phase2_active);
333 g_assert(log_window);
335 va_list args;
337 if (log_level_cim < LEVEL_ERROR)
338 return; // we don't want to see this log message
340 va_start(args, fmt);
341 char *text = g_strdup_vprintf(fmt, args);
342 va_end(args);
344 log_window->append(text);
345 g_free(text);
348 void Log::writeToFile(const char *text)
350 // writing to file is enabled only in phase 2
351 if (!phase2_active)
352 return;
354 if (!text)
355 return;
357 GError *err = NULL;
359 // write text into logfile
360 if (logfile) {
361 if (g_io_channel_write_chars(logfile, text, -1, NULL, &err) !=
362 G_IO_STATUS_NORMAL) {
363 writeErrorToWindow(
364 _("centerim/log: Error writing to logfile (%s)."), err->message);
365 g_clear_error(&err);
367 else {
368 // if necessary write missing EOL character
369 size_t len = strlen(text);
370 if (len && text[len - 1] != '\n') {
371 // ignore all errors
372 g_io_channel_write_chars(logfile, "\n", -1, NULL, NULL);
376 if (g_io_channel_flush(logfile, &err) != G_IO_STATUS_NORMAL) {
377 writeErrorToWindow(
378 _("centerim/log: Error flushing logfile (%s)."), err->message);
379 g_clear_error(&err);
384 void Log::outputBufferMessages()
386 // output all buffered messages to stderr
387 for (LogBufferItems::iterator i = log_items.begin(); i != log_items.end();
388 i++) {
389 LogBufferItem *item = *i;
391 // determine if this message should be displayed
392 Level loglevel = getLogLevel(item->getType());
394 if (loglevel >= item->getLevel()) {
395 const char *text = item->getText();
396 g_assert(text);
398 std::fprintf(stderr, text);
400 // if necessary write missing EOL character
401 if (text[strlen(text) - 1] != '\n')
402 std::fprintf(stderr, "\n");
405 delete item;
407 log_items.clear();
408 std::fflush(stderr);
411 Log::Level Log::convertPurpleDebugLevel(PurpleDebugLevel purplelevel)
413 switch (purplelevel) {
414 case PURPLE_DEBUG_MISC:
415 return LEVEL_DEBUG;
416 case PURPLE_DEBUG_INFO:
417 return LEVEL_INFO;
418 case PURPLE_DEBUG_WARNING:
419 return LEVEL_WARNING;
420 case PURPLE_DEBUG_ERROR:
421 return LEVEL_CRITICAL;
422 case PURPLE_DEBUG_FATAL:
423 return LEVEL_ERROR;
424 case PURPLE_DEBUG_ALL:
425 return LEVEL_ERROR; // use error level so this message is always printed
428 warning(
429 _("centerim/log: Unknown libpurple logging level '%d'."), purplelevel);
430 return LEVEL_DEBUG;
433 Log::Level Log::convertGLibDebugLevel(GLogLevelFlags gliblevel)
435 if (gliblevel & G_LOG_LEVEL_DEBUG)
436 return LEVEL_DEBUG;
437 if (gliblevel & G_LOG_LEVEL_INFO)
438 return LEVEL_INFO;
439 if (gliblevel & G_LOG_LEVEL_MESSAGE)
440 return LEVEL_MESSAGE;
441 if (gliblevel & G_LOG_LEVEL_WARNING)
442 return LEVEL_WARNING;
443 if (gliblevel & G_LOG_LEVEL_CRITICAL)
444 return LEVEL_CRITICAL;
445 if (gliblevel & G_LOG_LEVEL_ERROR)
446 return LEVEL_ERROR;
448 warning(_("centerim/log: Unknown GLib logging level '%d'."), gliblevel);
449 return LEVEL_DEBUG;
452 Log::Level Log::stringToLevel(const char *slevel)
454 if (!g_ascii_strcasecmp(slevel, "none"))
455 return LEVEL_NONE;
456 else if (!g_ascii_strcasecmp(slevel, "debug"))
457 return LEVEL_DEBUG;
458 else if (!g_ascii_strcasecmp(slevel, "info"))
459 return LEVEL_INFO;
460 else if (!g_ascii_strcasecmp(slevel, "message"))
461 return LEVEL_MESSAGE;
462 else if (!g_ascii_strcasecmp(slevel, "warning"))
463 return LEVEL_WARNING;
464 else if (!g_ascii_strcasecmp(slevel, "critical"))
465 return LEVEL_CRITICAL;
466 else if (!g_ascii_strcasecmp(slevel, "error"))
467 return LEVEL_ERROR;
468 return LEVEL_NONE;
471 Log::Level Log::getLogLevel(Type type)
473 switch (type) {
474 case TYPE_CIM:
475 return log_level_cim;
476 case TYPE_GLIB:
477 return log_level_glib;
478 case TYPE_PURPLE:
479 return log_level_purple;
480 default:
481 g_assert_not_reached();
485 /* vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab : */