strftime_logger 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +17 -0
- data/lib/strftime_logger/adapter/file.rb +77 -0
- data/lib/strftime_logger/bridge.rb +54 -0
- data/lib/strftime_logger/formatter.rb +41 -0
- data/lib/strftime_logger/ltsv_formatter.rb +28 -0
- data/lib/strftime_logger.rb +164 -0
- data/spec/formatter_spec.rb +33 -0
- data/spec/ltsv_formatter_spec.rb +32 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/strftime_logger_spec.rb +104 -0
- data/spec/thread_safe/process_safe_check.rb +14 -0
- data/spec/thread_safe/rotate_process_safe_check.rb +37 -0
- data/spec/thread_safe/rotate_thread_safe_check.rb +36 -0
- data/spec/thread_safe/thread_safe_check.rb +14 -0
- data/strftime_logger.gemspec +18 -0
- metadata +71 -0
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
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|