strftime_logger 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9329428c0cbb2bf7dc2d5589e420659a86d5ada4
4
+ data.tar.gz: 729cc3eacd69620002f9aadaff0cb138d288f22c
5
+ SHA512:
6
+ metadata.gz: c4e88ff564ef06cb8ebed2571958f70f326fbd3330e65ca4056fd92283e35ea72764d20b517abd896a310cd260de95b8cc429c15422115b3f5ddaafeaaa45f32
7
+ data.tar.gz: 1f0fadbd16716c2aeb5af9c7de6b5026359eafbeb0ad4128dc007f0525fd1b33fcef0004c5bc87e1702c95c1339a58df106ea594146abda40e86f56a812104e6
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ example/log
19
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.0.1 (2014/07/01)
2
+
3
+ First version
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rspec'
6
+ gem 'timecop'
7
+ gem 'parallel'
8
+ gem 'rake'
9
+ gem 'pry'
10
+ gem 'pry-nav'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Naotoshi Seo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # StrftimeLogger
2
+
3
+ A logger treats log rotation in strftime fashion.
4
+
5
+ ## What is this for?
6
+
7
+ This logger provides a feature to rotate logs in the basis of time although
8
+ the ruby's built-in logger has a feature to rotate logs in the basis of log size.
9
+
10
+ This logger allows to specify the log path with `strftime` format such as:
11
+
12
+ ```
13
+ logger = StrftimeLogger.new('/var/log/application.log.%Y%m%d')
14
+ ```
15
+
16
+ which enables to rotate logs in each specific time.
17
+
18
+ In facts, this logger does not rotate logs, but just outputs to the strftime formatted path directly,
19
+ which results in avoiding locking files in log rotation.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ gem 'strftime_logger'
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ ## How to use
32
+
33
+ ### Normal Usage
34
+
35
+ ```ruby
36
+ require 'strftime_logger'
37
+ logger = StrftimeLogger.new('/var/log/application.log.%Y%m%d')
38
+ logger.info("foo\nbar")
39
+ ```
40
+
41
+ which outputs logs to `/var/log/application.log.YYYYMMDD` with contents like
42
+
43
+ ```
44
+ 20140630T00:00:00+09:00 [INFO] foo\\nbar
45
+ ```
46
+
47
+ where the time is in ISO8601 format, and the line feed characters `\n` in log messages
48
+ are replaced with `\\n` so that the log message will be in one line.
49
+
50
+ ### Change the log level
51
+
52
+ ```
53
+ logger.level = StrftimeLogger::WARN
54
+ ```
55
+
56
+ ### Change the Formatter
57
+
58
+ It is possible to change the logger formmater as:
59
+
60
+ ```ruby
61
+ logger.formatter = SampleFormatter.new
62
+ ```
63
+
64
+ The interface which the costom formmatter must provide is only `#initialize(opts = {})` and `#call(sevirity, message = nil, &block)`. Following is a simple example:
65
+
66
+ ```ruby
67
+ class SampleFormatter
68
+ LEVEL_TEXT = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN)
69
+
70
+ def initialize(opts={})
71
+ end
72
+
73
+ # @param sevirity [int] log sevirity
74
+ def call(severity, message = nil, &block)
75
+ if message.nil?
76
+ if block_given?
77
+ message = yield
78
+ else
79
+ message = ""
80
+ end
81
+ end
82
+ "#{Time.now} #{LEVEL_TEXT[sevirity]} #{message}"
83
+ end
84
+ end
85
+ ```
86
+
87
+ ## ChangeLog
88
+
89
+ See [CHANGELOG.md](CHANGELOG.md) for details.
90
+
91
+ ## Contributing
92
+
93
+ 1. Fork it
94
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
95
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
96
+ 4. Push to the branch (`git push origin my-new-feature`)
97
+ 5. Create new [Pull Request](../../pull/new/master)
98
+
99
+ ## Copyright
100
+
101
+ See [LICENSE.txt](LICENSE.txt) for details.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+
5
+ task :test do
6
+ require 'rspec/core'
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new(:test) do |spec|
9
+ spec.pattern = FileList['spec/**/*_spec.rb']
10
+ end
11
+ end
12
+
13
+ desc 'Open an irb session preloaded with the gem library'
14
+ task :console do
15
+ sh 'irb -rubygems -I lib -r pfsys-logger'
16
+ end
17
+ task :c => :console
@@ -0,0 +1,77 @@
1
+ require 'monitor'
2
+
3
+ class StrftimeLogger
4
+ class Adapter
5
+ class File
6
+
7
+ class LogFileMutex
8
+ include MonitorMixin
9
+ end
10
+
11
+ def initialize(level, path)
12
+ @level = level
13
+ @path = path
14
+ @timestamp_path = Time.now.strftime(path)
15
+ @mutex = LogFileMutex.new
16
+ @log = open_logfile(@timestamp_path)
17
+ end
18
+
19
+ def write(msg)
20
+ begin
21
+ @mutex.synchronize do
22
+ if @log.nil? || !same_path?
23
+ begin
24
+ @timestamp_path = Time.now.strftime(@path)
25
+ @log.close rescue nil
26
+ @log = create_logfile(@timestamp_path)
27
+ rescue
28
+ warn("log shifting failed. #{$!}")
29
+ end
30
+ end
31
+
32
+ begin
33
+ @log.write msg
34
+ rescue
35
+ warn("log writing failed. #{$!}")
36
+ end
37
+ end
38
+ rescue Exception => ignored
39
+ warn("log writing failed. #{ignored}")
40
+ end
41
+ end
42
+
43
+ def close
44
+ if !@log.nil? && !@log.closed?
45
+ @log.close
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # return nil if file not found
52
+ def open_logfile(filename)
53
+ begin
54
+ f = ::File.open filename, (::File::WRONLY | ::File::APPEND)
55
+ f.sync = true
56
+ rescue Errno::ENOENT
57
+ return nil
58
+ end
59
+ f
60
+ end
61
+
62
+ def create_logfile(filename)
63
+ begin
64
+ f = ::File.open filename, (::File::WRONLY | ::File::APPEND | ::File::CREAT | ::File::EXCL)
65
+ f.sync = true
66
+ rescue Errno::EEXIST
67
+ f = open_logfile(filename)
68
+ end
69
+ f
70
+ end
71
+
72
+ def same_path?
73
+ @timestamp_path == Time.now.strftime(@path)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'adapter/file'
2
+
3
+ class StrftimeLogger
4
+ # A birdge to support logger's adapters.
5
+ # This interface enables for the logger to output not only into file, but aslo syslog, fluentd, and queue, etc.
6
+ #
7
+ # In addition, one or some adapters can be configured for **each** log level.
8
+ # If multiple adapters are specified, writing a log will output to the multiple destinations.
9
+ class Bridge
10
+
11
+ # @param [Symbol] level
12
+ def initialize(level, name, adapters = nil)
13
+ set_adapters(level, name, adapters)
14
+ end
15
+
16
+ def write(msg)
17
+ @adapters.each do |adapter|
18
+ adapter.write(msg)
19
+ end
20
+ end
21
+
22
+ def close
23
+ @adapters.each do |adapter|
24
+ adapter.close
25
+ end
26
+ end
27
+
28
+ private
29
+ def default_adapters
30
+ {
31
+ debug: [StrftimeLogger::Adapter::File],
32
+ info: [StrftimeLogger::Adapter::File],
33
+ warn: [StrftimeLogger::Adapter::File],
34
+ error: [StrftimeLogger::Adapter::File],
35
+ fatal: [StrftimeLogger::Adapter::File],
36
+ unknown: [StrftimeLogger::Adapter::File],
37
+ }
38
+ end
39
+
40
+ def set_adapters(level, name, adapters = nil)
41
+ @adapters = Array.new
42
+ (adapters || default_adapters[level]).each do |adapter|
43
+ case adapter
44
+ when Class
45
+ @adapters.push adapter.new(level, name)
46
+ else
47
+ @adapters.push adapter
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+
@@ -0,0 +1,41 @@
1
+ require 'time'
2
+
3
+ class StrftimeLogger
4
+ class Formatter
5
+ FORMAT = "%s [%s] %s\n"
6
+ LEVEL_TEXT = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN)
7
+
8
+ def initialize(opts={})
9
+ end
10
+
11
+ def call(severity, message = nil, &block)
12
+ if message.nil?
13
+ if block_given?
14
+ message = yield
15
+ else
16
+ message = ""
17
+ end
18
+ end
19
+ FORMAT % [format_datetime(Time.now), format_severity(severity), format_message(message)]
20
+ end
21
+
22
+ private
23
+ def format_datetime(time)
24
+ time.iso8601
25
+ end
26
+
27
+ def format_severity(severity)
28
+ LEVEL_TEXT[severity]
29
+ end
30
+
31
+ def format_message(message)
32
+ case message
33
+ when ::Exception
34
+ e = message
35
+ "#{e.class} #{e.message} #{e.backtrace.first}"
36
+ else
37
+ message.to_s.gsub(/\n/, "\\n")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ require 'time'
2
+
3
+ class StrftimeLogger
4
+ class LtsvFormatter
5
+ def initialize(opts={})
6
+ end
7
+
8
+ def call(severity, message = nil, &block)
9
+ if message.nil?
10
+ if block_given?
11
+ message = yield
12
+ else
13
+ message = ""
14
+ end
15
+ end
16
+ "#{format_message(message)}\n"
17
+ end
18
+
19
+ private
20
+
21
+ def format_message(message)
22
+ unless message.is_a?(Hash)
23
+ message = { message: message }
24
+ end
25
+ message.map {|k, v| "#{k}:#{v}" }.join("\t")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,164 @@
1
+ class StrftimeLogger
2
+ require 'strftime_logger/formatter'
3
+ require 'strftime_logger/ltsv_formatter'
4
+ require 'strftime_logger/bridge'
5
+ require 'strftime_logger/adapter/file'
6
+
7
+ SEV_LABEL = [:debug, :info, :warn, :error, :fatal, :unknown]
8
+
9
+ # Logging severity.
10
+ module Severity
11
+ # Low-level information, mostly for developers
12
+ DEBUG = 0
13
+ # generic, useful information about system operation
14
+ INFO = 1
15
+ # a warning
16
+ WARN = 2
17
+ # a handleable error condition
18
+ ERROR = 3
19
+ # an unhandleable error that results in a program crash
20
+ FATAL = 4
21
+ # an unknown message that should always be logged
22
+ UNKNOWN = 5
23
+ end
24
+ include Severity
25
+
26
+ # Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
27
+ attr_accessor :level
28
+ attr_accessor :formatter
29
+
30
+ # @param [Hash|IO|String] path
31
+ # @param [Hash] adapters
32
+ #
33
+ # Example:
34
+ #
35
+ # StrftimeLogger.new('/var/log/foo/webapp.log.%Y%m%d') # String
36
+ # StrftimeLogger.new('/var/log/foo/webapp.log.%Y%m%d_%H')
37
+ #
38
+ # Exampl2:
39
+ #
40
+ # # Hash
41
+ # StrftimeLogger.new({
42
+ # debug: '/var/log/foo/webapp.debug.log.%Y%m%d',
43
+ # info: '/var/log/foo/webapp.info.log.%Y%m%d',
44
+ # warn: '/var/log/foo/webapp.warn.log.%Y%m%d',
45
+ # error: '/var/log/foo/webapp.error.log.%Y%m%d',
46
+ # fatal: '/var/log/foo/webapp.fatal.log.%Y%m%d',
47
+ # unknown: '/var/log/foo/webapp.unknown.log.%Y%m%d'
48
+ # })
49
+ #
50
+ # Exampl3:
51
+ #
52
+ # # With Specified Adapter
53
+ # StrftimeLogger.new('/var/log/foo/webapp.log.%Y%m%d', [StrftimeLogger::Adapter::File])
54
+ def initialize(path, adapter = nil)
55
+ @level = DEBUG
56
+ @default_formatter = StrftimeLogger::Formatter.new
57
+ @formatter = nil
58
+
59
+ if path.is_a?(Hash)
60
+ @path = path
61
+ else
62
+ # make a hash
63
+ keys = SEV_LABEL
64
+ vals = [path] * keys.size
65
+ @path = Hash[*(keys.zip(vals).flatten(1))]
66
+ end
67
+
68
+ if adapter.nil?
69
+ @adapter = {}
70
+ elsif adapter.is_a?(Hash)
71
+ @adapter = adapter
72
+ else
73
+ # make a hash
74
+ keys = SEV_LABEL
75
+ vals = [adapter] * keys.size
76
+ @adapter = Hash[*(keys.zip(vals).flatten(1))]
77
+ end
78
+
79
+ @bridge = {}
80
+ SEV_LABEL.each do |level|
81
+ @bridge[level] = StrftimeLogger::Bridge.new(level, @path[level], @adapter[level])
82
+ end
83
+ end
84
+
85
+ # @param severity [Int] log severity
86
+ def add(severity, message = nil, &block)
87
+ severity ||= UNKNOWN
88
+
89
+ if @bridge.nil? or severity < @level
90
+ return true
91
+ end
92
+
93
+ log_level = SEV_LABEL[severity]
94
+ if @bridge[log_level].nil?
95
+ @bridge[log_level] = StrftimeLogger::Adapter.new(log_level, @path[log_level])
96
+ end
97
+
98
+ @bridge[log_level].write(format_message(severity, message, &block))
99
+ true
100
+ end
101
+ alias log add
102
+
103
+ def debug(msg, &block)
104
+ add(DEBUG, msg, &block)
105
+ end
106
+
107
+ def info(msg, &block)
108
+ add(INFO, msg, &block)
109
+ end
110
+
111
+ def warn(msg, &block)
112
+ add(WARN, msg, &block)
113
+ end
114
+
115
+ def error(msg, &block)
116
+ add(ERROR, msg, &block)
117
+ end
118
+
119
+ def fatal(msg, &block)
120
+ add(FATAL, msg, &block)
121
+ end
122
+
123
+ def unknown(msg, &block)
124
+ add(UNKNOWN, msg, &block)
125
+ end
126
+
127
+ def write(msg)
128
+ msg.chomp! if msg.respond_to?(:chomp!)
129
+ add(INFO, msg)
130
+ end
131
+
132
+ # Returns +true+ iff the current severity level allows for the printing of
133
+ # +DEBUG+ messages.
134
+ def debug?; @level <= DEBUG; end
135
+
136
+ # Returns +true+ iff the current severity level allows for the printing of
137
+ # +INFO+ messages.
138
+ def info?; @level <= INFO; end
139
+
140
+ # Returns +true+ iff the current severity level allows for the printing of
141
+ # +WARN+ messages.
142
+ def warn?; @level <= WARN; end
143
+
144
+ # Returns +true+ iff the current severity level allows for the printing of
145
+ # +ERROR+ messages.
146
+ def error?; @level <= ERROR; end
147
+
148
+ # Returns +true+ iff the current severity level allows for the printing of
149
+ # +FATAL+ messages.
150
+ def fatal?; @level <= FATAL; end
151
+
152
+ def close
153
+ SEV_LABEL.each do |level|
154
+ next if @bridge[level].nil?
155
+ @bridge[level].close
156
+ end
157
+ end
158
+
159
+ def format_message(severity, message = nil, &block)
160
+ (@formatter || @default_formatter).call(severity, message, &block)
161
+ end
162
+ end
163
+
164
+
@@ -0,0 +1,33 @@
1
+ require_relative 'spec_helper'
2
+ require 'strftime_logger'
3
+ require 'fileutils'
4
+
5
+ describe StrftimeLogger do
6
+ subject { StrftimeLogger.new("#{log_dir}/application.log.%Y%m%d") }
7
+ let(:log_dir) { "#{File.dirname(__FILE__)}/log" }
8
+ let(:today) { Time.now.strftime "%Y%m%d"}
9
+ let(:now) { Time.now.iso8601 }
10
+
11
+ before do
12
+ Dir.mkdir(log_dir)
13
+ Timecop.freeze(Time.now)
14
+ end
15
+
16
+ after do
17
+ FileUtils.rm_rf log_dir
18
+ Timecop.return
19
+ end
20
+
21
+ it :write do
22
+ subject.write("test")
23
+ begin
24
+ raise ArgumentError.new('test')
25
+ rescue => e
26
+ subject.write(e)
27
+ end
28
+ File.open("#{log_dir}/application.log.#{today}") do |f|
29
+ expect(f.gets).to eq "#{now} [INFO] test\n"
30
+ expect(f.gets).to match(/#{Regexp.escape(now)} \[INFO\] ArgumentError test/)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'spec_helper'
2
+ require 'strftime_logger'
3
+ require 'fileutils'
4
+
5
+ describe StrftimeLogger do
6
+ subject do
7
+ StrftimeLogger.new("#{log_dir}/application.log.%Y%m%d").tap {|logger|
8
+ logger.formatter = StrftimeLogger::LtsvFormatter.new
9
+ }
10
+ end
11
+ let(:log_dir) { "#{File.dirname(__FILE__)}/log" }
12
+ let(:today) { Time.now.strftime "%Y%m%d"}
13
+
14
+ before do
15
+ Dir.mkdir(log_dir)
16
+ Timecop.freeze(Time.now)
17
+ end
18
+
19
+ after do
20
+ FileUtils.rm_rf log_dir
21
+ Timecop.return
22
+ end
23
+
24
+ it :write do
25
+ subject.write("test")
26
+ subject.write({a:1, b:2})
27
+ File.open("#{log_dir}/application.log.#{today}") do |f|
28
+ expect(f.gets).to eq "message:test\n"
29
+ expect(f.gets).to eq "a:1\tb:2\n"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler.setup(:default, :test)
3
+ Bundler.require(:default, :test)
4
+
5
+ $TESTING=true
6
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
7
+ require 'strftime_logger'
@@ -0,0 +1,104 @@
1
+ require_relative 'spec_helper'
2
+ require 'strftime_logger'
3
+ require 'fileutils'
4
+
5
+ describe StrftimeLogger do
6
+ let(:log_dir) { "#{File.dirname(__FILE__)}/log" }
7
+
8
+ before do
9
+ Dir.mkdir(log_dir)
10
+ Timecop.freeze(Time.now)
11
+ end
12
+
13
+ after do
14
+ FileUtils.rm_rf log_dir
15
+ Timecop.return
16
+ end
17
+
18
+ context :write do
19
+ subject { StrftimeLogger.new("#{log_dir}/application.log.%Y%m%d") }
20
+ let(:today) { Time.now.strftime "%Y%m%d"}
21
+ let(:now) { Time.now.iso8601 }
22
+
23
+ it :write do
24
+ subject.write("test")
25
+ subject.write("test")
26
+ expect(File.read("#{log_dir}/application.log.#{today}")).to eq "#{now} [INFO] test\n"*2
27
+ end
28
+
29
+ LEVEL_TEXT = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN)
30
+ %w[debug info warn error fatal unknown].each_with_index do |level, severity|
31
+ it level do
32
+ subject.__send__(level, "test")
33
+ expect(File.read("#{log_dir}/application.log.#{today}")).to eq "#{now} [#{LEVEL_TEXT[severity]}] test\n"
34
+ end
35
+ end
36
+
37
+ it 'multiline' do
38
+ subject.info("foo\nbar")
39
+ expect(File.read("#{log_dir}/application.log.#{today}")).to eq "#{now} [INFO] foo\\nbar\n"
40
+ end
41
+
42
+ it 'rotate log' do
43
+ subject.info("test")
44
+ Timecop.freeze(Time.now + 24 * 60 * 60)
45
+ subject.info("test")
46
+ yesterday = (Time.now - 24 * 60 * 60).strftime "%Y%m%d"
47
+ one_day_ago = (Time.now - 24 * 60 * 60).iso8601
48
+ expect(File.read("#{log_dir}/application.log.#{yesterday}")).to eq "#{one_day_ago} [INFO] test\n"
49
+ expect(File.read("#{log_dir}/application.log.#{today}")).to eq "#{now} [INFO] test\n"
50
+ end
51
+ end
52
+
53
+ context :new do
54
+ let(:now) { Time.now.iso8601 }
55
+
56
+ it 'date format' do
57
+ logger = StrftimeLogger.new("#{log_dir}/application.log.%Y%m%d_%H")
58
+ current_hour = Time.now.strftime "%Y%m%d_%H"
59
+ logger.info("test")
60
+ expect(File.read("#{log_dir}/application.log.#{current_hour}")).to eq "#{now} [INFO] test\n"
61
+ end
62
+
63
+ it 'file per level' do
64
+ logger = StrftimeLogger.new({
65
+ debug: "#{log_dir}/application.debug.log",
66
+ info: "#{log_dir}/application.info.log",
67
+ warn: "#{log_dir}/application.warn.log",
68
+ error: "#{log_dir}/application.error.log",
69
+ fatal: "#{log_dir}/application.fatal.log",
70
+ unknown: "#{log_dir}/application.unknown.log"
71
+ })
72
+ logger.info("test")
73
+ expect(File.read("#{log_dir}/application.info.log")).to eq "#{now} [INFO] test\n"
74
+ end
75
+
76
+ it 'level=' do
77
+ logger = StrftimeLogger.new("#{log_dir}/application.log")
78
+ logger.level = StrftimeLogger::WARN
79
+ logger.info("test")
80
+ logger.warn("test")
81
+ expect(File.read("#{log_dir}/application.log")).to eq "#{now} [WARN] test\n"
82
+ end
83
+
84
+ class MockAdapter
85
+ def initialize(level, path)
86
+ end
87
+ def write(msg)
88
+ ::File.open("#{File.dirname(__FILE__)}/log/mock", 'a+') do |f|
89
+ f.write msg
90
+ f.flush
91
+ end
92
+ end
93
+ end
94
+
95
+ describe 'switch adapter' do
96
+ it 'class and instance' do
97
+ logger = StrftimeLogger.new("#{log_dir}/test", {debug: [MockAdapter, MockAdapter.new('mock', 'mock')]})
98
+ logger.debug('mock')
99
+ expect(File.read("#{log_dir}/mock")).to eq "#{now} [DEBUG] mock\n#{now} [DEBUG] mock\n"
100
+ end
101
+ end
102
+ end
103
+ end
104
+
@@ -0,0 +1,14 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'strftime_logger'
4
+ require 'parallel'
5
+
6
+ logger = StrftimeLogger.new("#{__dir__}/test.log")
7
+ Parallel.map(['a', 'b'], :in_processes => 2) do |letter|
8
+ 3000.times do
9
+ logger.info letter * 5000
10
+ end
11
+ end
12
+
13
+ # egrep -e 'ab' -e 'ba' test.log
14
+ # これはまざらない
@@ -0,0 +1,37 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'strftime_logger'
4
+ require 'parallel'
5
+ require 'timecop'
6
+ require 'test/unit'
7
+
8
+ Timecop.scale(24 * 60 * 60)
9
+
10
+ $proc_num = 2
11
+ $execute_num = 10000
12
+
13
+ logger = StrftimeLogger.new("#{__dir__}/test.log.%Y%m%d")
14
+ Parallel.map(['a', 'b'], :in_processes => $proc_num) do |letter|
15
+ $execute_num.times do
16
+ logger.info letter * 5000
17
+ end
18
+ end
19
+
20
+ $total_num = `LANG=C wc -l #{__dir__}/test.log.*`.split("\n").map(&:strip).grep(/\stotal\z/).first.split(' ').first.to_i
21
+ p "Actually total line num #{$total_num}"
22
+ p "Expected total line num #{$execute_num * $proc_num}"
23
+
24
+ class StrftimeLoggerTC < Test::Unit::TestCase
25
+ def test_logger
26
+ assert_equal($execute_num * $proc_num, $total_num)
27
+ end
28
+ def teardown
29
+ p 'rm -rf #{__dir__}/test.log.*'
30
+ end
31
+ end
32
+
33
+ =begin
34
+ % ruby example/rotate_process_safe_check.rb
35
+ no warn!!!
36
+ =end
37
+
@@ -0,0 +1,36 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'strftime_logger'
4
+ require 'parallel'
5
+ require 'timecop'
6
+ require 'test/unit'
7
+
8
+ Timecop.scale(24 * 60 * 60)
9
+
10
+ $proc_num = 2
11
+ $execute_num = 10000
12
+
13
+ logger = StrftimeLogger.new("#{__dir__}/test.log.%Y%m%d")
14
+ Parallel.map(['a', 'b'], :in_threads => $proc_num) do |letter|
15
+ $execute_num.times do
16
+ logger.info letter * 5000
17
+ end
18
+ end
19
+
20
+ $total_num = `LANG=C wc -l #{__dir__}/test.log.*`.split("\n").map(&:strip).grep(/\stotal\z/).first.split(' ').first.to_i
21
+ p "Actually total line num #{$total_num}"
22
+ p "Expected total line num #{$execute_num * $proc_num}"
23
+
24
+ class StrftimeLoggerTC < Test::Unit::TestCase
25
+ def test_logger
26
+ assert_equal($execute_num * $proc_num, $total_num)
27
+ end
28
+ def teardown
29
+ p 'rm -rf #{__dir__}/test.log.*'
30
+ end
31
+ end
32
+
33
+ =begin
34
+ % ruby example/rotate_thread_safe_check.rb
35
+ no warn!!!
36
+ =end
@@ -0,0 +1,14 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'strftime_logger'
4
+ require 'parallel'
5
+
6
+ logger = StrftimeLogger.new("#{__dir__}/test.log")
7
+ Parallel.map(['a', 'b'], :in_threads => 2) do |letter|
8
+ 3000.times do
9
+ logger.info letter * 5000
10
+ end
11
+ end
12
+
13
+ # egrep -e 'ab' -e 'ba' test.log
14
+ # これはまざらない
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "strftime_logger"
7
+ gem.version = "0.0.1"
8
+ gem.authors = ["Naotoshi Seo"]
9
+ gem.email = ["seo.naotoshi@dena.jp"]
10
+ gem.description = %q{A logger treats log rotation in strftime fashion}
11
+ gem.summary = %q{A logger treats log rotation in strftime fashion.}
12
+ gem.homepage = "https://github.com/sonots/strftime-logger"
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strftime_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Naotoshi Seo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A logger treats log rotation in strftime fashion
14
+ email:
15
+ - seo.naotoshi@dena.jp
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - CHANGELOG.md
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - lib/strftime_logger.rb
27
+ - lib/strftime_logger/adapter/file.rb
28
+ - lib/strftime_logger/bridge.rb
29
+ - lib/strftime_logger/formatter.rb
30
+ - lib/strftime_logger/ltsv_formatter.rb
31
+ - spec/formatter_spec.rb
32
+ - spec/ltsv_formatter_spec.rb
33
+ - spec/spec_helper.rb
34
+ - spec/strftime_logger_spec.rb
35
+ - spec/thread_safe/process_safe_check.rb
36
+ - spec/thread_safe/rotate_process_safe_check.rb
37
+ - spec/thread_safe/rotate_thread_safe_check.rb
38
+ - spec/thread_safe/thread_safe_check.rb
39
+ - strftime_logger.gemspec
40
+ homepage: https://github.com/sonots/strftime-logger
41
+ licenses: []
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 2.2.2
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: A logger treats log rotation in strftime fashion.
63
+ test_files:
64
+ - spec/formatter_spec.rb
65
+ - spec/ltsv_formatter_spec.rb
66
+ - spec/spec_helper.rb
67
+ - spec/strftime_logger_spec.rb
68
+ - spec/thread_safe/process_safe_check.rb
69
+ - spec/thread_safe/rotate_process_safe_check.rb
70
+ - spec/thread_safe/rotate_thread_safe_check.rb
71
+ - spec/thread_safe/thread_safe_check.rb