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 +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
|