ApacheのログをTokyoCabinetに吐く(いい加減な実装)

そろそろKVSを本格導入したいなーと思ったので、TokyoCabinetを少しさわってみた。
とりあえず、DSASの中の人が公開しているmod_syslogを改変して、テーブルデータベースにログを出力するモジュールを、ものすごくいい加減に実装。

#include "apr_hooks.h"
#include "ap_config.h"
#include "mod_log_config.h"
#include "http_config.h"

#include <tcutil.h>
#include <tctdb.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>

module AP_MODULE_DECLARE_DATA tctlog_module;

static const char syslog_filter_name[] = "LOG_TCT";

static APR_OPTIONAL_FN_TYPE(ap_log_set_writer_init) *set_writer_init;
static APR_OPTIONAL_FN_TYPE(ap_log_set_writer)      *set_writer;

static ap_log_writer_init *prev_log_writer_init = NULL;
static ap_log_writer      *prev_log_writer      = NULL;

typedef struct tctlog_config_t {
} tctlog_config_t;

char dummy[16];

#define PREFIX_TCTLOG        "tct:"
#define PREFIX_TCTLOG_LENGTH 4

static void *ap_tctlog_writer_init(apr_pool_t *p, server_rec *s, const char* name) {
  if (strncasecmp(PREFIX_TCTLOG, name, PREFIX_TCTLOG_LENGTH) == 0) {
    return &dummy[0];
  }

  if (prev_log_writer_init) {
    return prev_log_writer_init(p, s, name);
  }

  return NULL;
}

static apr_status_t ap_tctlog_writer(request_rec *r, void *handle, const char **strs, int *strl, int nelts, apr_size_t len) {
  if (handle == dummy) {
    char *str, *s;
    int i;

    TCTDB *tdb;
    TCMAP *cols;

    str = apr_palloc(r->pool, len + 1);

    for (i = 0, s = str; i < nelts; i++) {
      memcpy(s, strs[i], strl[i]);
      s += strl[i];
    }

    str[len] = '\0';

    tdb = tctdbnew();
    tctdbsetmutex(tdb);

    if(!tctdbopen(tdb, "/tmp/access.tdb", TDBOWRITER | TDBOCREAT)) {
      return OK;
    }

    for (i = 0, s = str; i < len; i++, s++) {
      if (*s == ' ') {
        *s = '\0';
        s++;
        break;
      }
    }

    tctdbput3(tdb, str, s);

    tctdbclose(tdb);
    tctdbdel(tdb);

    return OK;
  }

  if (prev_log_writer) {
    return prev_log_writer(r, handle, strs, strl, nelts, len);
  }

  return OK;
}

static int tctlog_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) {
  if (!set_writer_init) {
    set_writer_init = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer_init);
    set_writer      = APR_RETRIEVE_OPTIONAL_FN(ap_log_set_writer);
  }

  if (!prev_log_writer_init) {
    void *f;

    f = set_writer_init(ap_tctlog_writer_init);

    if (f != ap_tctlog_writer_init) {
      prev_log_writer_init = f;
    }

    f = set_writer(ap_tctlog_writer);

    if (f != ap_tctlog_writer) {
      prev_log_writer = f;
    }
  }

  return OK;
}

static void tctlog_register_hooks(apr_pool_t *p) {
  static const char *pre[] = { "mod_log_config.c", NULL };
  ap_hook_pre_config(tctlog_pre_config, pre, NULL, APR_HOOK_REALLY_LAST);
}

module AP_MODULE_DECLARE_DATA tctlog_module = {
  STANDARD20_MODULE_STUFF, 
  NULL,                  /* create per-dir    config structures */
  NULL,                  /* merge  per-dir    config structures */
  NULL,                  /* create per-server config structures */
  NULL,                  /* merge  per-server config structures */
  NULL,                  /* table of config file commands       */
  tctlog_register_hooks  /* register hooks                      */
};
LogFormat "%{%s}t status\t%>s\tmsec\t%D\turl\t%U" tct
CustomLog tct:foo tct

ログはこんな感じ。


[root@andLinux tctlog]# tctmgr list -pv /tmp/access.tdb
1258980571 status 200 msec 0 url /index.html
1258980573 status 304 msec 0 url /index.html
1258980576 status 304 msec 0 url /index.html
1258980579 status 404 msec 0 url /xxx
[root@andLinux tctlog]# tctmgr search -pv /tmp/access.tdb status STREQ 304
1258980573 status 304 msec 0 url /index.html
1258980576 status 304 msec 0 url /index.html
[root@andLinux tctlog]# tctmgr search -pv /tmp/access.tdb url STRINC index
1258980571 status 200 msec 0 url /index.html
1258980573 status 304 msec 0 url /index.html
1258980576 status 304 msec 0 url /index.html

ガバイト単位のファイルをgrepしなくていいなら、本格的に作り込んでもいいかも。
圧縮もできるし。


あと所感。

  • リクエスト単位でopen/closeは良くなと思うけど、どうなんだろう?
    • データベース開きっぱなしにして別プロセスからアクセスしたら、ログが見れなかった
      • flushが必要なのかも
    • Apache終了をフックするための関数が見つからないよー
  • strsって、ログって分割されてる?
  • こーゆー実装の場合でもtctdbsetmutex()を呼んだ方がいいんだろうか?
  • プライマリーキーを自動で付けてくれると便利なのになー