ウェブサービスのログ設計についてメモ

ログの設計について、ググっても断片的にしか見つからなかったのでメモ。

ログの目的

  1. 不具合の原因を特定するため
  2. 不正利用の検出
  3. 利用統計の取得

ログレベル

  • fatal

サービス全体の提供が不可能な状態になる状態。DBに接続できないとか、syntaxエラーがある場合など。このレベルのログが出るとインフラ担当の人達にメールが飛んだりする。

  • error

全体が止まるほどではないが、ユーザーの一部の処理が完了しないような場合が該当する。エラーページが表示されるような場合はこのレベル。

  • warn

言語自体のwarningや処理フロー上来ることがない値がかえって来た場合などはこのレベル。(例えばユーザーが不正にAPIを叩いてブロックされた時とか) 状況によってはerror相当なので場合によりけり。

  • info

このレベルから下は開発者の胃を傷めないログレベル。アクセスログなど。hadoopでコネコネしたりして企画担当が利用したりする。

  • debug

これ以下は開発時に欲しい情報。本番環境では出力しない。

  • trace

println("ここまで処理が来た")

何をログとして残すか

例えばユーザーの行動についてなら

  • [いつ]アクセス日時
  • [誰が]ユーザ情報(ID)/ホスト/ポート/ユーザーエージェント
  • [何をした] ログファイルを切り替えてファイル名で表現する(例)login_failure.log

異なるファイルにログを出力するのは重要。特定のログに素早くアクセスでき、かつログの種類によって保存期間や閲覧権限を変えたりできる。機能とユーザー数が増えるほどその恩恵にあずかれる。

ログをどれだけの期間保存するか。

ログの重要度と書き込み頻度によって決める。
(容量肥大対策の一例)
ログを一日ごとのログローテーションで記録し、30日間保存する場合、前日のログはすぐアクセスできるようにファイル圧縮して同じディレクトリにおき、それ以前はバックアップサーバーに送って30日前まで保存するようにするなど。

ログのドキュメントに何を書くか。

ドキュメントを整備・保守しつづけないと、「どこで使用されているかわからないので消せないログ出力」が生まれてコードのエントロピーが増大する。

ログに何を書いてはいけないか

メールアドレスなどの特に重要な個人情報をログに残さないようにする。

ログに書くこと
  • 作成者
  • 出力条件

(例)認証キー発行APIを実行した時

  • 利用目的

(例)不正利用がないか記録を残します

  • フォーマット
  • ログの例

ログ設計について何かよい資料があったら誰か教えてください。

play-slickを試した

play framework + Java + ebeanで色々やったので、今度はscalaでDB操作をしたかった。
調べるとSquerylとslickとで人気を二分しているらしい。今回はscalaの勉強であることとtypesafe stackに入っていることも考えてslickを試してみた。

play frameworkとslickを使うサンプルがすでにgithub上にあるが、どうもうまく統合できていないように感じたので調べたところplay-slickというプラグインがあるらしいので動かすところまでメモ。

環境

  • play framework 2.1.1
  • slick 2.10.0-RC5
  • play-slick 0.3.2

howto
いつもどおりplay new

play new mySlickSample

project/Build.scalaに追加

val appDependencies = Seq(
    //...他の依存ライブラリ
    "com.typesafe" % "slick_2.10.0-RC5" % "0.11.2",
    "com.typesafe.play" %% "play-slick" % "0.3.2" 
  )

application.confにDBの設定

 db.default.driver=org.h2.Driver
 db.default.url="jdbc:h2:mem:play"

play-slickを有効にする設定をapplication.confに追加。
このへんはebeanと同じですね。

# slick
# ~~~~~
# It is possible to specify individual objects like:
# slick.default="models.Users,models.Settings"
slick.default="models.*"

modelを作成

package models

import play.api.db.slick.Config.driver.simple._

case class User(
  id: Option[Long],//privary keyの場合はOption
  name: String)

object Users extends Table[User]("USERS") {

  def id = column[Long]("ID", O.PrimaryKey, O AutoInc) // This is the primary key column
  def name = column[String]("COF_NAME")
  def * = id.? ~ name <> (User.apply _, User.unapply _)

  //とりあえずfindAllだけ
  def findAll(filter: String = "%") = {
    //scalaのコレクションっぽく扱えるのが素敵
    for {
      u <- Users
      if (u.name like ("%" + filter))
    } yield u
  }
}

controllerも

package controllers

import models.Users
import play.api._
import play.api.Play.current
import play.api.db.slick.Config.driver.simple._
import play.api.mvc._

object Application extends Controller {

  def index = Action {
    //controllerでwithSessionを使うのが気持ち悪い気がするのだが、これでいいのだろうか?
    play.api.db.slick.DB.withSession { implicit session =>
      val users = Users.findAll().list
      Ok(views.html.index(users))
    }
  }
}

view

@(users: List[User])

@main("slick test") {
	<ul>
	@for(u <- users){
            <li>@u.toString()</li>
	}
	</ul>
}

あとappのdefault packageにGlobal.scalaとか作っておいて、

import play.api.Application
import play.api.GlobalSettings
import play.api.Play.current
import play.api.db.slick.Config.driver.simple._
import models.Users
import models.User

object Global extends GlobalSettings {

  override def onStart(app: Application) {

    play.api.db.slick.DB withSession {implicit session =>
      Users.insertAll(
        User(None, "jhon"),
        User(None, "bob"))
    }
  }
}

とやればテスト用データを入れておける。

localhost:9000にアクセスすれば表示できる。
f:id:ganr:20130609132711p:plain


次はtodoアプリを作ろう。

参考
Slick ガイド - tototoshiの日記
Slick

追記(2013/6/25)
今後play frameworkではAnormからslickに移行していくようだ。
play-slickがplay framework本体にプルリクされているようで
https://github.com/playframework/Play20/pull/1230
wktkが止まらないですね。

MS932で指定した方がよかったメモ

Scalaスクレイピングしてたところ、

charset=Shift_JIS

なのに

Source.fromURL(vipSubjectsUrl, "Shift_JIS")

で「たまに」失敗することがあって困った。他言語でよく指定する"CP932"がないので30分くらい詰まった。 正解として "CP932"ではなく"MS932"を指定するらしい。

参考: http://blue-red.ddo.jp/~ao/wiki/wiki.cgi?page=Java+%A4%CE+MS932%2C+Cp943C%2C+SJIS+%A4%CE%B0%E3%A4%A4

play framework + Java + Ebean + eclipseでJUnitテストが起動に失敗するときの対処法

現象:
play.db.ebean.Modelを継承したModelをテストする際に以下のメッセージがコンソールに出てエラーになる。

c.a.e.s.d.BeanDescriptorManager - Error in deployment

環境:

  • play framework 2.1.1
  • eclipse juno(4.2 RC2)

対処法:
VM引数にjavaagentを設定する.

  1. ebeanを公式(http://www.avaje.org/download.html)から適当な場所にダウンロード&&解凍
  2. テストのVM引数に-javaagent:/path/to/ebean-2.7.0-agent.jarを設定

f:id:ganr:20130517220902p:plain

3.実行すれば問題が解消されるはず。

参考:
http://blog.matthieuguillermin.fr/2012/03/unit-testing-tricks-for-play-2-0-and-ebean/

残る疑問

  1. -javaagent引数は何をやっているのか?クラスをロードする時にバイトコードを書き換えさせるプログラムの設定をやっているみたいだが、よくわかってない。
  2. 今はテストファイルごとにVM引数を設定しているが、JUnit関連だけ一括して設定する方法があるのかを調べる。