Rails + Unicorn の logrotate

調べたこと

Railsで作成したアプリケーションをUnicornから起動させたときの、ログローテーションのやり方を調べてみた。


仕掛け

Unicornには、USR1シグナルを送ると、ログファイルを開きなおしてくれる機能が備わっている。そのため、現在のログをリネームした後、USR1シグナルを送信すれば、ログをローテートできるようだ。


Unicornのlogrotate設定

上記を踏まえ、logrotateの設定ファイルはこんな感じにしてみた。

/home/sample/rails_app/log/*.log {
  daily
  missingok
  rotate 60
  dateext

  # 圧縮設定 (圧縮は次回のローテートまで遅らせる)
  compress
  delaycompress

  # unicorn masterプロセスに、USR1シグナルを送る
  lastaction
    pid=/home/sample/rails_app/tmp/pids/unicorn.pid
    test -s $pid && kill -USR1 "$(cat $pid)"
  endscript
}

これを /etc/logrotate.d/rails_app に配置して設定は完了。


補足:ログローテーションの動作確認方法

logrotateの設定ファイルをテストするには、-d オプションをつける。

# logrotate -d /etc/logrotate.d/rails_app

このコマンドでは、実際にローテーションは行われずに、設定に問題がないかを確認できる。


また、-dオプションを付けずに、ログローテーションを実行すると、/var/lib/logrotate.status に、日付が記録される。
この日付を編集して、ローテートの動作を確認することができる。

Mac OS X 10.6 に Ruby 1.9.2 をインストール

目的

1. rvmで複数バージョンのrubyを簡単に使用できるようにする
2. irbでの日本語入力が文字化けしないようにする
3. Rails の test:benchmark でメモリ使用量等を表示する


文字化け対策に必要なreadlineをインストール

$ sudo port install readline

( macに標準で入っているreadlineを使うと文字化けする )


rvmのインストール

$ git clone -depth 1 git://github.com/wayneeseguin/rvm.git
$ cd rvm
$ ./install
$ vi ~/.bashrc (以下を追加)
source ~/.rvm/scripts/rvm


Ruby 1.9.2のインストール

Railsの性能テストで、メモリ使用量等を表示するために、gcdataパッチをあててインストールする。また、文字化け対策としてportでインストールしたreadlineを使用する。

$ rvm install 1.9.2 --patch ~/.rvm/patches/ruby/1.9.2/gcdata.patch -C --with-readline-dir=/opt/local --with--enable-readline-v6

確認 (Railsのインストール方法は省略)

$ rvm 1.9.2
$ ruby --version
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.6.0]

(Railsプロジェクト内で)
$ rake test:benchmark                       
BrowsingTest#test_homepage (61 ms warmup)
           wall_time: 9 ms
              memory: 155.58 KB
             objects: 2139
             gc_runs: 0
             gc_time: 0.00 ms


補足: Rails 3.0.5 の test:benchmarkでのエラー

rake test:benchmarkで以下のエラーが出る場合、

uninitialized constant BrowsingTest::STARTED (NameError)

Gemfileにtest-unitを記載して、bundle install すると解決する(かも)。

group :test do
  gem 'ruby-prof'
  gem 'test-unit'
end

Javascriptで祝日判定

ある日付が祝日がどうかを判定するJavascriptです。

// 日付が指定されている祝日
var DateHoliday = function( month, day ){
  this.month = month;
  this.day = day;
};
DateHoliday.prototype = {
  getHoliday:  function(year){
    return this.day;
  }
};

// ハッピーマンデー
var MondayHoliday = function( month, week ){
  this.month = month;
  this.week = week;
  this.wday = 1;
};
MondayHoliday.prototype = {
  getHoliday: function(year){
    var firstWday = new Date(year,this.month-1,1).getDay();
    return 7*(this.week - ( (firstWday <= this.wday) ? 1 : 0 )) + ( this.wday - firstWday ) + 1; // 第this.week this.wday曜日
  }
};

// 春分・秋分の日
var EquinoxHoliday = function( month ){
  this.month = month;
  if( this.month == 3 )
    this.offset = 20.8431;
  else if ( this.month == 9 )
    this.offset = 23.2488;
  else
    throw 'Not exists equinox day in '+month;
};
EquinoxHoliday.prototype = {
  getHoliday: function(year){
    return Math.floor(this.offset+0.242194*(year-1980)-Math.floor((year-1980)/4)); // 1980-2099に対応?
  }
};

var HolidayHelper = {
  holidayMap: {
    1: [new DateHoliday( 1, 1 ), new MondayHoliday( 1, 2 )],
    2: [new DateHoliday( 2, 11 )],
    3: [new EquinoxHoliday( 3 )],
    4: [new DateHoliday( 4, 29 )],
    5: [new DateHoliday( 5, 3 ), new DateHoliday( 5, 4 ), new DateHoliday( 5, 5 )],
    7: [new MondayHoliday( 7, 3 )], 
    9: [new MondayHoliday( 9, 3 ), new EquinoxHoliday( 9 )],
    10: [new MondayHoliday( 10, 2 )],
    11: [new DateHoliday( 11, 3 ), new DateHoliday( 11, 23 )],
    12: [new DateHoliday( 12, 23 )]
  },
  // 月をまたがる振替休日や国民の休日(昨日と翌日が国民の祝日である日)が存在しないことを前提とした処理
  getHolidays: function( year, month ){
    var holidays = this.holidayMap[month];
    if( !holidays )
      return {};
    var dayHash= {}
    var dateArray = []
    for( var i=0, len=holidays.length; i<len; i++ ){
      var day = holidays[i].getHoliday(year);
      dayHash[ day ] = true;
      dateArray.push( new Date(year,month-1,day) );
    }

    for( var i=0, len=dateArray.length; i<len; i++ ){
      var date = dateArray[i];
      var day = date.getDate();

      if( date.getDay() == 0 ){
        var cday = day+1;
        while( dayHash[cday] )  // 振替休日が祝日の場合、翌日へ
          cday++;
        dayHash[ cday ] = true;
      }
      // 国民の休日判定には、振替休日を考慮しない
      if( dayHash[day+2] && !dayHash[day+1] )
        dayHash[ day+1 ] = true;
    }
    return dayHash;
  },
  isHoliday : function( dateOrYear, month, day ){
    var year = day ? dateOrYear : dateOrYear.getFullYear();
    var month = day ? month : dateOrYear.getMonth()+1;
    var day = day || dateOrYear.getDate();

    return !! this.getHolidays( year, month )[ day ];
  }
};

判定方法

// Dateで判定
HolidayHelper.isHoliday( new Date(2010,0,1) );
  --> true

// 数値から判定
HolidayHelper.isHoliday( 2010,1,1 );
  --> true

既にあるとは思いつつ、つい作ってみたシロモノです。
2010年8月時点の法律に基づく判定になりますので、現時点の法律が施行される前の祝日は対応していません。
また、rubydate2ライブラリの祝日判定結果と、2007年から2030年の範囲で一致することを確認しています。

ご利用は自己責任でお願いします。。

Android 背景画像の繰り返し表示

Androidで背景画像を繰り返し表示する方法のメモ。


背景画像を配置する。

res/drawable/back_img.png


xmlファイルを作成し、繰り返しを設定する。

res/drawable/back_img_repeat.xml
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
   android:src="@drawable/back_img" 
   android:tileMode="repeat" />


レイアウトに背景を指定する。

res/layout/xxx_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="@drawable/back_img_repeat">

  <!-- ..(snip).. -->
</LinerLayout>

で、できるかも。Android 1.6 APIで確認しました。


参考にしたサイト

rails date_select のプルダウン間の区切り文字

rails の date_select を使って、プルダウンの間に、"年" や "月" 等の日本語の文字を
表示できないかと悩んだ。
date_separatorを%sにして、sprintfで置換するのがお手軽かな?

    <% form_for @my_model, :url => { :action => 'create' } do |f| %>
        <%= sprintf( 
              f.date_select( :nengetsu,
                :order => [:year, :month, :day], 
                :discard_day => true, 
                :use_month_numbers => true, 
                :date_separator => '%s'
              ), '', '', ''
            )
        %>
    <% end %>

上記はこんな感じで出力される。

Ubuntu 8.04 にアップグレード・・・これは快適

先日、Ubuntu 7.10 から 8.04 にアップグレードした。


アップグレード自体は、ボタンぽっちと押してほぼ終了。
これは手間がかからない。
と思いきや、再起動後ログインするとX関連と思われるエラーが起きた。

 Error activating XKB configuration


このエラーに関しては、リリースノートに情報が載っていた。

リンクの情報を参考に、/etc/X11/xorg.conf の設定を以下の様に変更したら解決した。

Section "InputDevice"
        Identifier      "Generic Keyboard"
        Driver          "kbd"
        Option          "XkbRules"      "xorg"
        Option          "XkbModel"      "jp106"
        Option          "XkbLayout"     "jp,jp"
        Option          "XkbVariant"    "106"
EndSection


その他にはこれといった問題は起きず、アップグレード完了。
これで、Firefoxもバージョン3へアップグレードされた。

Firefox3は非常に動作が軽いし、今のところ安定している。Ubuntu 7.10 のときはFirefoxがフリーズしたり、落ちたりと色々あっただけに、快適になった。


あと日本語変換が賢くなった気がする。


これは素晴らしい。

ScalaのParser Combinator を勉強中

Scala の Parser Combinator を理解したい。

StandardTokenParsers を利用するのが簡単そうなのでまずは見よう見まねで使ってみた。


お題は、文字列を解析して、時刻を表示するプログラム。
"3 days ago" は3日前の時刻、"2 days later" は2日後の時刻を表示する。

import java.util._
import scala.util.parsing.combinator.syntactical._

object DateTranslator extends StandardTokenParsers {

  lexical.reserved += ("days", "months", "later", "ago")

  def sinceNow : Parser[Calendar] = {
    num ~ unit ~ timeAdj ^^ { 
      case n ~ u ~ s => {
        val cal = Calendar.getInstance()
        cal.add(u,s*n)
        cal;
      }
    }
  }

  def num : Parser[Int] = numericLit ^^ { case n => n.toInt }

  def unit : Parser[Int] = 
    ( "days" | "months" ) ^^ {
      case "days" => Calendar.DATE
      case "months" => Calendar.MONTH
    }

  def timeAdj : Parser[Int] =
    ( "later" | "ago" ) ^^ {
      case "later" => +1
      case "ago" => -1
    }

  def main(args:Array[String]):Unit = {
    sinceNow( new lexical.Scanner("3 days ago") ) match {
      case Success(cal, _) => println(cal.getTime())
      case Failure(msg, _) => println(msg)
      case Error(msg, _) => println(msg)
    }
  }
}
実行してみる。
% fsc DateTranslator.scala
% scala DateTranslator
Sun Jun 08 13:40:50 GMT 2008


おお、意外と簡単にできた。

でも仕組みの理解はまだあやふや。
今のところ以下の様な理解。

StandardTokenParsers を継承。

キーワード、識別子、数値リテラル、文字列、区切り文字 をパースする標準レキシカルパーサ(lexical)を持つ。
lexical.reserved (mutable.HashSet) に予約語をセットしておく。

Parserの結合子

~ [逐次実行]

レシーバ(Parserクラス)と引数(Parserクラス)それぞれの処理を逐次実行する。
parser1 ~ parser2 とした場合、parser1の処理が成功した場合、次の parser2 を実行する。
モナドの枠組みが適用されている。
( モナドをよく理解できていないが、順序処理を行うことや副作用をなくすという目的よりも、関数への引数の型と戻り値の型を同一にすることに意味があるような気がする。)

| [2者択一]

parser1 | parser2 とした場合、どちらかの処理が成功すれば成功とする。
parser1の処理が成功した場合、次の parser2 の処理は実行されない。

^^ [関数適用]

関数適用のために用いる。
parser ^^ function とすると、parserの処理に成功したらその結果がfunctionの引数に渡されて、
functionが実行される。



ScalaのParsersクラスをちょっと眺めてみたら、理解できない部分も多々あるけど結構おもしろい。
関数の中身が短い。抽象化度が高くてよくわからない。


こういうコードを書くには、関数型言語の概念などの知識や実践経験が要るのだろうか。
とりあえずHaskell本を発注しました。