strftime_logger 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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