Capistranoでログを標準出力とファイル出力の両方に出力する方法

もっとシンプルにできるやり方があれば教えてくださいませ!

chefを動かしているCapistranoのログをよしなにする必要があり
標準出力とファイル出力の両方に出力することはできないかなと悪戦苦闘した記録になります。

- 参考
capturing output to log file


require 'capistrano'
require 'capistrano/logger'

output = '/var/log/capistrano.log'
custom_logger = Capistrano::Logger.new(:output => output)
custom_logger.level = Capistrano::Logger::TRACE
self.logger = custom_logger

まず、こちらの設定で標準出力は行わず、ファイル出力されることを確認。

では合わせて標準出力に出すにはoutputをもうひとつ指定したらよしなにしてくれるだろうと。

custom_logger = Capistrano::Logger.new(:output => output)

↓

custom_logger = Capistrano::Logger.new(:output => output, :output => STDOUT)


1つ目のoutput引数が2つ目のoutput引数に上書きされて終わり。
そう簡単にはいかないですよね。

ということでCapistranoの中身を確認するとlog関係は
/lib/capistrano/logger.rbここにありそうだなと。

    def initialize(options={})
output = options[:output] || $stderr
if output.respond_to?(:puts)
@device = output
else
@device = File.open(output.to_str, "a")
@needs_close = true
end

@options = options
@level = 0
end

def close
device.close if @needs_close
end

def log(level, message, line_prefix=nil)
if level <= self.level
indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
message.each do |line|
if line_prefix
device.puts "#{indent} [#{line_prefix}] #{line.strip}\n"
else
device.puts "#{indent} #{line.strip}\n"
end
end
end
end

putsってメソッドがあればdeviceってのに格納することがわかったので
それを偽装してやればいいんだ。ということがわかります。

config/mydevice.rb

class MyDevice

attr_accessor :buffer
def puts(msg)
STDOUT.puts(msg)
@buffer = @buffer + msg
end
def tty?
return false
end
def initialize
@buffer = ""
end
def clone
end
end

こんな風にSTDOUT(標準出力)と@buffer(ファイル出力のための変数)に出力するようにします。

deploy.rb

require 'capistrano'
require 'capistrano/logger'
load 'config/mydevice'

self.logger.device = MyDevice.new

そしてdeploy.rbに上記記述を書くことで、設定が上書きされます。

  task :message do
open("/tmp/capistrano.log", "w") {|f| f.write self.logger.device.buffer}
end

こんな感じでmessageを出力するtaskを記載することで
/tmp/capistrano.logへ上書きで出力してくれます。

Perlのツールでもこんなにコードを読んだことはなかったので
どうやって読んでいったらいいかのきっかけになったのと、クラスとかそういうものが
ぼんやりとわかってきたような気がします。

@fujiwara++