試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

GLibのメッセージ記録システムを使用する

GTK+のアプリケーションなどで

** (appname:1234): WARNING **: foo
** (appname:5678): CRITICAL **: bar

のようなメッセージが端末に出るのを見ることがあるのだが、これはGLibのメッセージ記録システムを利用している。

メッセージ出力部

重要度によって

  • g_debug()
  • g_message()
  • g_warning()
  • g_critical()
  • g_error()

といったマクロ*1が用意されているので、使用する場面に合ったものを使用する。引数はprintf()系関数と同様。

ログハンドラ

上に挙げたマクロを呼び出すと、本記事の一番上に貼り付けたような形式でメッセージが端末に出力される。しかし、これ以外に、自分で定義した関数をカスタムログハンドラとして呼び出すようにすることもできる。その場合の設定はg_log_set_handler()関数で行う。
この関数の2番目の引数ではカスタムログハンドラを使用するログレベルを指定する。この指定により、特定のログレベルのみGUIのダイアログで通知するといったことも可能になる。
例えば

g_log_set_handler (NULL,
                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
                   (GLogFunc) my_log_handler,
                   NULL);

のようにした場合は全てのログレベルがカスタムログハンドラ(この場合はmy_log_handler())で処理され、

g_log_set_handler (NULL,
                   G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
                   (GLogFunc) my_log_handler,
                   NULL);

のようにした場合はg_warning()とg_critical()のメッセージのみカスタムログハンドラで処理される。

この例ではGTK+を使用しているため、GTK+の開発パッケージも必要。
[任意]ファイル名: gliblogtest.c

#include <stdlib.h>
#include <gtk/gtk.h>
#include <glib.h>

/*
 * 既定のメッセージハンドラを使用(端末に表示):
 * gcc -Wall -Wextra gliblogtest.c -o gliblogtest $(pkg-config --cflags --libs gtk+-2.0)
 * カスタムメッセージハンドラを使用(ダイアログに表示):
 * gcc -DUSE_CUSTOM_LOG_HANDLER -Wall -Wextra gliblogtest.c -o gliblogtest_customloghandler $(pkg-config --cflags --libs gtk+-2.0)
 */

static GtkAccelGroup *accelgroup;
static GtkWidget *win, *item_quit, *menu_file, *item_file, *menubar, *vbox, *entry, *btn_debug, *btn_message, *btn_warning, *btn_critical, *btn_error;

static void
on_button_clicked (gpointer data)
{
  gint loglevel = GPOINTER_TO_INT (data);
  const gchar *msg = gtk_entry_get_text (GTK_ENTRY (entry));
  /* 対象のログレベル別に用意されているマクロを呼ぶ */
  switch (loglevel)
  {
  case G_LOG_LEVEL_DEBUG:
    g_print ("g_debug(\"%s\")\n", msg);
    g_debug (msg, NULL);
    break;
  case G_LOG_LEVEL_MESSAGE:
    g_print ("g_message(\"%s\")\n", msg);
    g_message (msg, NULL);
    break;
  case G_LOG_LEVEL_WARNING:
    g_print ("g_warning(\"%s\")\n", msg);
    g_warning (msg, NULL);
    break;
  case G_LOG_LEVEL_CRITICAL:
    g_print ("g_critical(\"%s\")\n", msg);
    g_critical (msg, NULL);
    break;
  case G_LOG_LEVEL_ERROR:
    g_print ("g_error(\"%s\")\n", msg);
    g_error (msg, NULL);  /* abort()により強制終了 */
    break;
  }
}

/* メインウィンドウの初期化 */
void
mainwindow_init ()
{
  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  entry = gtk_entry_new ();
  btn_debug = gtk_button_new_with_mnemonic ("_Debug");
  btn_message = gtk_button_new_with_mnemonic ("_Message");
  btn_warning = gtk_button_new_with_mnemonic ("_Warning");
  btn_critical = gtk_button_new_with_mnemonic ("_Critical");
  btn_error = gtk_button_new_with_mnemonic ("_Error");
  vbox = gtk_vbox_new (FALSE, 0);
  accelgroup = gtk_accel_group_new ();
  gtk_window_add_accel_group (GTK_WINDOW (win), accelgroup);
  item_quit = gtk_image_menu_item_new_from_stock (GTK_STOCK_QUIT, accelgroup);
  menu_file = gtk_menu_new ();
  gtk_menu_shell_append (GTK_MENU_SHELL (menu_file), item_quit);
  item_file = gtk_menu_item_new_with_mnemonic ("_File");
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item_file), menu_file);
  menubar = gtk_menu_bar_new ();
  gtk_menu_shell_append (GTK_MENU_SHELL (menubar), item_file);
  gtk_window_set_title (GTK_WINDOW (win), "GLib log test");
  gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), btn_debug, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), btn_message, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), btn_warning, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), btn_critical, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), btn_error, TRUE, TRUE, 0);
  gtk_container_add (GTK_CONTAINER (win), vbox);
  gtk_widget_show_all (win);
  g_signal_connect_swapped (G_OBJECT (btn_debug), "clicked", G_CALLBACK (on_button_clicked), GINT_TO_POINTER(G_LOG_LEVEL_DEBUG));
  g_signal_connect_swapped (G_OBJECT (btn_message), "clicked", G_CALLBACK (on_button_clicked), GINT_TO_POINTER(G_LOG_LEVEL_MESSAGE));
  g_signal_connect_swapped (G_OBJECT (btn_warning), "clicked", G_CALLBACK (on_button_clicked), GINT_TO_POINTER(G_LOG_LEVEL_WARNING));
  g_signal_connect_swapped (G_OBJECT (btn_critical), "clicked", G_CALLBACK (on_button_clicked), GINT_TO_POINTER(G_LOG_LEVEL_CRITICAL));
  g_signal_connect_swapped (G_OBJECT (btn_error), "clicked", G_CALLBACK (on_button_clicked), GINT_TO_POINTER(G_LOG_LEVEL_ERROR));
  g_signal_connect (G_OBJECT (win), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
  g_signal_connect (G_OBJECT (item_quit), "activate", G_CALLBACK (gtk_main_quit), NULL);
}

#ifdef USE_CUSTOM_LOG_HANDLER
/* カスタムログハンドラ */
static void
my_log_handler (const gchar *log_domain,
                GLogLevelFlags log_level,
                const gchar *message)
{
  GtkMessageType dialog_type;
  GtkWidget *dlg;
  gchar *title;
  /* ログレベルに応じて出力を変える */
  switch (log_level - (log_level & G_LOG_FLAG_RECURSION) - (log_level & G_LOG_FLAG_FATAL))
  {
  case G_LOG_LEVEL_ERROR:
    dialog_type = GTK_MESSAGE_ERROR;
    title = g_strdup_printf ("ERROR - %s", log_domain);
    break;
  case G_LOG_LEVEL_CRITICAL:
    dialog_type = GTK_MESSAGE_WARNING;
    title = g_strdup_printf ("CRITICAL - %s", log_domain);
    break;
  case G_LOG_LEVEL_WARNING:
    dialog_type = GTK_MESSAGE_WARNING;
    title = g_strdup_printf ("WARNING - %s", log_domain);
    break;
  case G_LOG_LEVEL_MESSAGE:
    dialog_type = GTK_MESSAGE_INFO;
    title = g_strdup_printf ("MESSAGE - %s", log_domain);
    break;
  case G_LOG_LEVEL_INFO:
    dialog_type = GTK_MESSAGE_INFO;
    title = g_strdup_printf ("INFO - %s", log_domain);
    break;
  case G_LOG_LEVEL_DEBUG:
    dialog_type = GTK_MESSAGE_INFO;
    title = g_strdup_printf ("DEBUG - %s", log_domain);
    break;
  }
  dlg = gtk_message_dialog_new (GTK_WINDOW (win), 0, dialog_type, GTK_BUTTONS_CLOSE, message);
  gtk_window_set_title (GTK_WINDOW (dlg), title);
  gtk_dialog_run (GTK_DIALOG (dlg));
  gtk_widget_destroy (dlg);
  g_free (title);
}
#endif

int
main (int argc, char *argv[])
{
  gtk_set_locale ();
  gtk_init (&argc, &argv);
  mainwindow_init ();
#ifdef USE_CUSTOM_LOG_HANDLER
  /*
   * G_LOG_LEVEL_MASKの部分を個別のログレベルマスクにすると
   * カスタムハンドラに渡す対象のログレベルを指定できる
   * (例: G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION)
   */
  g_log_set_handler (NULL,
                     G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
                     (GLogFunc) my_log_handler,
                     NULL);
#endif
  gtk_main ();
  return EXIT_SUCCESS;
}


適当な文字列を入力して好きなボタンを押すことでログメッセージが表示される。
普通にビルドした場合

(Debugボタンを押す)
g_debug("foobar")
** (gliblogtest:[プロセスID]): DEBUG: foobar

(Messageボタンを押す)
g_message("foobar")
** Message: foobar

(Warningボタンを押す)
g_warning("foobar")

** (gliblogtest:[プロセスID]): WARNING **: foobar

(Criticalボタンを押す)
g_critical("foobar")

** (gliblogtest:[プロセスID]): CRITICAL **: foobar

(Errorボタンを押す)
g_error("foobar")

** ERROR **: foobar
aborting...
(終了)

上のように押したボタンに応じたメッセージが端末に表示され(「Error」では終了もする)、-DUSE_CUSTOM_LOG_HANDLERオプションを付けてビルドした場合はGTK+のメッセージダイアログに表示される。そのままでは全てのレベルでダイアログにメッセージが出るが、上にも書いたように、g_log_set_handler()の2番目の引数を編集することにより、特定のレベルのみダイアログで表示するようにもできる。

GTK+を用いない場合のプログラム名の表示について

(2009/7/24)GTK+アプリケーションでは表示の中にプログラム名が入っているが、GTK+を使用しない場合には

** (process:[プロセスID]): DEBUG: [メッセージ]

のように「process」という名前が使われる。
これはg_set_prgname()を呼ぶことにより変更できる(呼ぶのは一度だけにする)。

#include <stdlib.h>
#include <glib.h>

int
main ()
{
  g_set_prgname ("foobar");
  g_debug ("test");
  return EXIT_SUCCESS;
}

これで

** (foobar:[プロセスID]): DEBUG: [メッセージ]

のように表示される。

参考URL:

使用したバージョン:

  • GLib 2.20.1
  • GTK+ 2.16.1

*1:g_log()関数を重要度ごとに簡単に使用するために用意されている