diff --git a/lib/ougai/formatters/base.rb b/lib/ougai/formatters/base.rb index 633f483..1672b8f 100644 --- a/lib/ougai/formatters/base.rb +++ b/lib/ougai/formatters/base.rb @@ -1,4 +1,3 @@ -require 'logger' require 'time' require 'socket' @@ -13,6 +12,11 @@ def initialize(app_name = nil, hostname = nil) @hostname = hostname || Socket.gethostname.force_encoding('UTF-8') @trace_indent = 2 @trace_max_lines = 100 + self.datetime_format = nil + end + + def datetime_format=(value) + @datetime_format = value || default_datetime_format end def serialize_exc(ex) @@ -30,6 +34,19 @@ def serialize_trace(trace) sp = "\n" + ' ' * @trace_indent trace.slice(0, @trace_max_lines).join(sp) end + + private + + def format_datetime(time) + time.strftime(@datetime_format) + end + + def default_datetime_format + t = Time.new + f = '%FT%T.%3N' + f << (t.utc? ? 'Z' : '%:z') + f.freeze + end end end end diff --git a/lib/ougai/formatters/bunyan.rb b/lib/ougai/formatters/bunyan.rb index 7661478..8de5422 100644 --- a/lib/ougai/formatters/bunyan.rb +++ b/lib/ougai/formatters/bunyan.rb @@ -49,7 +49,7 @@ def to_level(severity) def dump(data) return data unless @jsonize - data[:time] = data[:time].iso8601(3) + data[:time] = format_datetime(data[:time]) str = JSON.generate(data) str << "\n" if @with_newline str diff --git a/lib/ougai/formatters/readable.rb b/lib/ougai/formatters/readable.rb index 4628ee6..2f99ed0 100644 --- a/lib/ougai/formatters/readable.rb +++ b/lib/ougai/formatters/readable.rb @@ -20,7 +20,7 @@ def initialize(opts = {}) def call(severity, time, progname, data) msg = data.delete(:msg) level = @plain ? severity : colored_level(severity) - strs = ["[#{time.iso8601(3)}] #{level}: #{msg}"] + strs = ["[#{format_datetime(time)}] #{level}: #{msg}"] if err_str = create_err_str(data) strs.push(err_str) end diff --git a/lib/ougai/version.rb b/lib/ougai/version.rb index e5b4815..58d2d69 100644 --- a/lib/ougai/version.rb +++ b/lib/ougai/version.rb @@ -1,3 +1,3 @@ module Ougai - VERSION = "1.5.1" + VERSION = "1.5.2" end diff --git a/spec/formatters/base_spec.rb b/spec/formatters/base_spec.rb index 3241f77..3685ae4 100644 --- a/spec/formatters/base_spec.rb +++ b/spec/formatters/base_spec.rb @@ -3,6 +3,24 @@ describe Ougai::Formatters::Base do subject { described_class.new(app_name, hostname) } + context 'default' do + let (:app_name) { nil } + let (:hostname) { nil } + + it 'has datetime format default ISO8601' do + expect(subject.datetime_format).to match(/^\%FT\%T\.\%3N(Z|\%\:z)$/) + end + + it 'has datetime_format accessor' do + subject.datetime_format = '%I:%M:%S %p' + expect(subject.datetime_format).to eq('%I:%M:%S %p') + + # revert default format by to set nil + subject.datetime_format = nil + expect(subject.datetime_format).to match(/^\%FT\%T\.\%3N(Z|\%\:z)$/) + end + end + context 'without arguments and hostname contains a UTF-8 char' do let (:app_name) { nil } let (:hostname) { nil } diff --git a/spec/formatters/bunyan_spec.rb b/spec/formatters/bunyan_spec.rb index 81620c6..4c4e109 100644 --- a/spec/formatters/bunyan_spec.rb +++ b/spec/formatters/bunyan_spec.rb @@ -114,4 +114,20 @@ end end + describe '#datetime_format' do + subject do + formatter.call('DEBUG', Time.now, nil, data) + end + + context 'is time AM/PM format' do + before do + formatter.datetime_format = '%I:%M:%S %p' + end + + it 'applys output' do + result = JSON.parse(subject, symbolize_names: true) + expect(result[:time]).to match(/^\d{2}:\d{2}:\d{2} [AP]M$/) + end + end + end end diff --git a/spec/formatters/readable_spec.rb b/spec/formatters/readable_spec.rb index b4d7696..ae15e81 100644 --- a/spec/formatters/readable_spec.rb +++ b/spec/formatters/readable_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ougai::Formatters::Readable do + let!(:re_start_with_datetime) { /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[\+\-\:0-9]{4,6})]/ } + let(:data) do { msg: 'Log Message!', @@ -18,62 +20,73 @@ } end + let(:formatter) { described_class.new } + context 'when severity is TRACE' do - subject { described_class.new.call('TRACE', Time.now, nil, data) } + subject { formatter.call('TRACE', Time.now, nil, data) } it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;34mTRACE\e[0m: Log Message!") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include(':status => 200') end end context 'when severity is DEBUG' do - subject { described_class.new.call('DEBUG', Time.now, nil, data) } + subject { formatter.call('DEBUG', Time.now, nil, data) } it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;37mDEBUG\e[0m: Log Message!") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include(':status => 200') end end context 'when severity is INFO' do - subject { described_class.new.call('INFO', Time.now, nil, data) } + subject { formatter.call('INFO', Time.now, nil, data) } it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;36mINFO\e[0m: Log Message!") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include(':method => "GET"') end end context 'when severity is WARN' do - subject { described_class.new.call('WARN', Time.now, nil, data) } + subject { formatter.call('WARN', Time.now, nil, data) } it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;33mWARN\e[0m: Log Message!") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include(':path => "/"') end end context 'when severity is ERROR' do - subject { described_class.new.call('ERROR', Time.now, nil, data.merge({ err: err })) } + subject { formatter.call('ERROR', Time.now, nil, data.merge({ err: err })) } it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;31mERROR\e[0m: Log Message!") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include('DummyError (it is dummy.):') end end context 'when severity is FATAL' do - subject { described_class.new.call('FATAL', Time.now, nil, { msg: 'TheEnd', err: err }) } + subject { formatter.call('FATAL', Time.now, nil, { msg: 'TheEnd', err: err }) } + it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;35mFATAL\e[0m: TheEnd") expect(subject.gsub(/\e\[([;\d]+)?m/, '')).to include("error1.rb\n error2.rb") end end context 'when severity is UNKNOWN' do - subject { described_class.new.call('ANY', Time.now, nil, { msg: 'unknown msg' }) } + subject { formatter.call('ANY', Time.now, nil, { msg: 'unknown msg' }) } + it 'includes valid strings' do + expect(subject).to match(re_start_with_datetime) expect(subject).to include("\e[0;32mANY\e[0m: unknown msg") end end @@ -91,4 +104,20 @@ expect(plain_subject).not_to include(':method => "GET"') end end + + describe '#datetime_format' do + subject do + formatter.call('DEBUG', Time.now, nil, data) + end + + context 'is time AM/PM format' do + before do + formatter.datetime_format = '%I:%M:%S %p' + end + + it 'applys output' do + expect(subject).to match(/^\[\d{2}:\d{2}:\d{2} [AP]M\]/) + end + end + end end