logbert 0.6.19 → 1.0.0
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 +13 -5
- data/lib/logbert/consumers.rb +1 -0
- data/lib/logbert/consumers/redis_consumer.rb +32 -0
- data/lib/logbert/handlers.rb +3 -2
- data/lib/logbert/handlers/log_rotator.rb +173 -0
- data/lib/logbert/handlers/redis_handler.rb +23 -0
- data/lib/logbert/logger.rb +14 -14
- data/lib/logbert/message.rb +36 -9
- data/lib/logbert/naming.rb +1 -3
- metadata +37 -6
- data/lib/logbert/handlers/time_rotator.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWZhNDk3MmU1ZjgxMDViZjhhZjE5NTZhNDVhYjJlY2RmZjljOTdkYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmQwMjJiYjMxNjk4OGYxMTg3OGIzZTkzYzRiYTQ3NmFlN2I2ZWMxNQ==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZjEwNTNmYWU2ODNjMGI2NDA4ZjEzOTA2YzhkMGQyNmZjMDRlNzJiMWY2NDYw
|
10
|
+
ZGFlNTMzZTIxZDZmYzg0NjkxY2EzZTc3NzllMjFmOWVlYjc5Y2IwNjFjYTRi
|
11
|
+
MWE4MmY4NGRmOTM3Zjc3YTE5NTdhNDBhZDM5N2VmZjg1NGEwYzE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjE4NmE1ZTMzY2U2MDBkNWFhMzY0YTAzYTY0YTM4Y2MzYTU2MDMxZDU3MDQw
|
14
|
+
MDgzNGI5ZTE4NzBiY2FkMjdiNzM2ZjE4YjAwODdkYThjMGI0NWJjNmJlMjgy
|
15
|
+
OTQwNWU3NzIyZjZjNTNmN2U0OGVjMmUxZDdlMmYwNDc5OWYyOTI=
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'logbert/consumers/redis_consumer'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'logbert/message'
|
3
|
+
require 'logbert/handlers'
|
4
|
+
|
5
|
+
module Logbert
|
6
|
+
module Consumers
|
7
|
+
|
8
|
+
class RedisConsumer
|
9
|
+
|
10
|
+
attr_accessor :redis, :key, :handlers
|
11
|
+
|
12
|
+
def initialize(redis_connection, key, options = {})
|
13
|
+
@redis = redis_connection
|
14
|
+
@key = key
|
15
|
+
@handlers = options.fetch(:handlers, [])
|
16
|
+
end
|
17
|
+
|
18
|
+
def work
|
19
|
+
# Blocking loop to read serialized messages of the redis queue
|
20
|
+
loop do
|
21
|
+
msg = redis.brpop(key) # blocking list pop primitive
|
22
|
+
@handlers.each do |h|
|
23
|
+
m = Message.from_json(msg)
|
24
|
+
h.handle_message(m)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class RedisConsumer
|
30
|
+
|
31
|
+
end # module Consumers
|
32
|
+
end # module Logbert
|
data/lib/logbert/handlers.rb
CHANGED
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logbert/handlers/base_handler'
|
3
|
+
require 'lockfile'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
module Logbert
|
7
|
+
module Handlers
|
8
|
+
|
9
|
+
###########################################################################
|
10
|
+
# Default Time Formatter #
|
11
|
+
###########################################################################
|
12
|
+
class LocaltimeFormatter
|
13
|
+
|
14
|
+
attr_accessor :format
|
15
|
+
|
16
|
+
def initialize(format = "%Y-%m-%d-%H%M")
|
17
|
+
@format = format
|
18
|
+
end
|
19
|
+
|
20
|
+
def format(time)
|
21
|
+
time.strftime(@format)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class LocaltimeFormatter
|
25
|
+
|
26
|
+
###########################################################################
|
27
|
+
# This class is a custom Handler responsible for rotating logs. This #
|
28
|
+
# means that it will periodically: #
|
29
|
+
# * Close the current log file and rename it. #
|
30
|
+
# * Begin writing future log messages to a new file. #
|
31
|
+
# * Delete the oldest log files to free up space #
|
32
|
+
# Time-based log rotation #
|
33
|
+
###########################################################################
|
34
|
+
class LogRotator < Logbert::Handlers::BaseHandler
|
35
|
+
|
36
|
+
attr_reader :file_handle, :timestamp_formatter, :interval
|
37
|
+
attr_reader :expiration_timestamp, :max_backups
|
38
|
+
attr_reader :path
|
39
|
+
|
40
|
+
def initialize(path, options = {})
|
41
|
+
@path = path
|
42
|
+
@max_backups = options.fetch(:max_backups, nil)
|
43
|
+
@timestamp_formatter = options.fetch(:timestamp_formatter, LocaltimeFormatter.new)
|
44
|
+
@interval = options.fetch(:interval, (24 * 60 * 60))
|
45
|
+
end
|
46
|
+
|
47
|
+
def attached?
|
48
|
+
return !file_handle.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock_file_name_for(log_path)
|
52
|
+
return "#{log_path}.lock"
|
53
|
+
end
|
54
|
+
|
55
|
+
def rotation_required?
|
56
|
+
return Time.now > @expiration_timestamp
|
57
|
+
end
|
58
|
+
|
59
|
+
def already_rotated?
|
60
|
+
return File.ctime(@path) > @expiration_timestamp
|
61
|
+
end
|
62
|
+
|
63
|
+
def attach_to_logfile!
|
64
|
+
lock do
|
65
|
+
dirname = File.dirname(File.absolute_path(@path))
|
66
|
+
FileUtils.mkdir_p dirname unless File.exists? dirname
|
67
|
+
@file_handle = File.open(@path, 'a')
|
68
|
+
creation_timestamp = File.ctime(@path)
|
69
|
+
@expiration_timestamp = compute_expiration_timestamp_from(creation_timestamp)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def emit(output)
|
74
|
+
attach_to_logfile! unless attached?
|
75
|
+
rotate_logs! if rotation_required?
|
76
|
+
@file_handle.puts output
|
77
|
+
@file_handle.flush
|
78
|
+
end
|
79
|
+
|
80
|
+
def compute_expiration_timestamp_from(timestamp)
|
81
|
+
return timestamp + @interval
|
82
|
+
end
|
83
|
+
|
84
|
+
def lock(&block)
|
85
|
+
Lockfile.new(lock_file_name_for(@path)) do
|
86
|
+
block.call
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def rotate_logs!
|
91
|
+
performed_swap = false
|
92
|
+
|
93
|
+
lock do
|
94
|
+
# Double-check that the file wasn't already rotated
|
95
|
+
unless already_rotated?
|
96
|
+
performed_swap = true
|
97
|
+
|
98
|
+
# Close the old log
|
99
|
+
if @file_handle and not @file_handle.closed?
|
100
|
+
@file_handle.close
|
101
|
+
end
|
102
|
+
|
103
|
+
# Rename the old log
|
104
|
+
if File.exists? @path
|
105
|
+
FileUtils.mv @path, archive_destination
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end # Lockfile lock
|
110
|
+
|
111
|
+
attach_to_logfile!
|
112
|
+
|
113
|
+
# Post-Processing logic if the rotation was performed
|
114
|
+
post_process if performed_swap
|
115
|
+
end
|
116
|
+
|
117
|
+
# This will essentially perform at most two things:
|
118
|
+
# 1.) Compress older log files
|
119
|
+
# 2.) Delete, if any, old log files based upon max_backups
|
120
|
+
def post_process
|
121
|
+
compress_backups
|
122
|
+
delete_backups
|
123
|
+
end
|
124
|
+
|
125
|
+
def compress_backups
|
126
|
+
# Compress older log files (unless already compressed)
|
127
|
+
get_old_logs.each do |log|
|
128
|
+
unless File.extname(log) == ".gz"
|
129
|
+
# Compress the file
|
130
|
+
gzip(log)
|
131
|
+
# Delete the actual log file
|
132
|
+
File.delete(log)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def delete_backups
|
138
|
+
# Delete, if any, old log files based upon max_backups
|
139
|
+
unless @max_backups.nil?
|
140
|
+
# Grab all the logs. Sort from newest to oldest
|
141
|
+
old_logs = get_old_logs.sort_by {|f| File.ctime(f)}.reverse[@max_backups..-1]
|
142
|
+
|
143
|
+
# If we have more than max_backups logs, then delete the extras
|
144
|
+
old_logs.each {|f| File.delete(f)} if old_logs
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def gzip(file)
|
149
|
+
Zlib::GzipWriter.open("#{file}.gz") do |gz|
|
150
|
+
gz.mtime = File.mtime(file)
|
151
|
+
gz.orig_name = file
|
152
|
+
gz.write IO.binread(file)
|
153
|
+
gz.close
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_old_logs
|
158
|
+
absolute_dir = File.dirname(File.absolute_path(@path))
|
159
|
+
older_files = Dir[File.join(absolute_dir, "#{@path}.backup.*")]
|
160
|
+
return older_files
|
161
|
+
end
|
162
|
+
|
163
|
+
def archive_destination
|
164
|
+
timestamp = @timestamp_formatter.format(File.ctime(@path))
|
165
|
+
dest = "#{@path}.backup.#{timestamp}"
|
166
|
+
return dest
|
167
|
+
end
|
168
|
+
|
169
|
+
private :archive_destination, :post_process
|
170
|
+
end # class LogRotator
|
171
|
+
|
172
|
+
end # module Handlers
|
173
|
+
end # module Logbert
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'logbert/handlers/base_handler'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module Logbert
|
5
|
+
module Handlers
|
6
|
+
|
7
|
+
class RedisQueueHandler < Logbert::Handlers::BaseHandler
|
8
|
+
|
9
|
+
attr_accessor :redis, :key
|
10
|
+
|
11
|
+
def initialize(redis_connection, key, options = {})
|
12
|
+
@redis = redis_connection
|
13
|
+
@key = key
|
14
|
+
end
|
15
|
+
|
16
|
+
def publish(message)
|
17
|
+
redis.lpush(@key, message.to_json)
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class RedisQueueHandler
|
21
|
+
|
22
|
+
end # module Handlers
|
23
|
+
end # module Logbert
|
data/lib/logbert/logger.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
|
2
2
|
require 'logbert/message'
|
3
3
|
require 'logbert/handlers'
|
4
|
+
require 'logbert/consumers'
|
4
5
|
|
5
6
|
module Logbert
|
6
|
-
|
7
|
+
|
7
8
|
class Logger
|
8
|
-
|
9
|
+
|
9
10
|
attr_reader :factory, :level_manager, :name, :handlers
|
10
|
-
|
11
|
+
|
11
12
|
def initialize(factory, level_manager, name)
|
12
13
|
@factory = factory
|
13
14
|
@level_manager = level_manager
|
@@ -15,15 +16,15 @@ module Logbert
|
|
15
16
|
@name = name.dup.freeze
|
16
17
|
@handlers = []
|
17
18
|
end
|
18
|
-
|
19
|
+
|
19
20
|
def level_inherited?
|
20
21
|
!@level
|
21
22
|
end
|
22
|
-
|
23
|
+
|
23
24
|
def level
|
24
25
|
@level || self.parent.level
|
25
26
|
end
|
26
|
-
|
27
|
+
|
27
28
|
def level=(x)
|
28
29
|
@level = @level_manager[x]
|
29
30
|
end
|
@@ -40,10 +41,10 @@ module Logbert
|
|
40
41
|
def root
|
41
42
|
self.factory.root
|
42
43
|
end
|
43
|
-
|
44
|
+
|
44
45
|
def log(level, *args, &block)
|
45
46
|
content, options = self.prepare_message_args(*args, &block)
|
46
|
-
|
47
|
+
|
47
48
|
exception = options[:exc_info]
|
48
49
|
if exception
|
49
50
|
# If the user passed in an exception, then use that one.
|
@@ -55,15 +56,15 @@ module Logbert
|
|
55
56
|
message = Logbert::Message.create(self, @level_manager[level], exception, options, content, &block)
|
56
57
|
handle_message(message)
|
57
58
|
end
|
58
|
-
|
59
|
-
|
59
|
+
|
60
|
+
|
60
61
|
def to_s
|
61
62
|
@name
|
62
63
|
end
|
63
|
-
|
64
|
+
|
64
65
|
protected
|
65
|
-
|
66
|
-
|
66
|
+
|
67
|
+
|
67
68
|
# This method will be unnecessary once we upgrade to Ruby 2.x
|
68
69
|
def prepare_message_args(*args, &block)
|
69
70
|
if args.size == 0
|
@@ -93,4 +94,3 @@ module Logbert
|
|
93
94
|
end
|
94
95
|
|
95
96
|
end
|
96
|
-
|
data/lib/logbert/message.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
|
2
2
|
module Logbert
|
3
|
-
|
4
3
|
class Message
|
5
4
|
attr_reader :logger, :level, :time, :pid, :exception, :options, :content_proc
|
6
|
-
|
5
|
+
|
7
6
|
def initialize(logger, level, time, pid, exception, options, content = nil, &content_proc)
|
8
7
|
@logger = logger
|
9
8
|
@level = level
|
@@ -11,13 +10,44 @@ module Logbert
|
|
11
10
|
@pid = pid
|
12
11
|
@exception = exception
|
13
12
|
@options = options
|
14
|
-
|
13
|
+
|
15
14
|
@content = content
|
16
15
|
@content_proc = content_proc
|
17
16
|
end
|
18
17
|
|
19
18
|
def self.create(logger, level, exception, options, content = nil, &content_proc)
|
20
|
-
Message.new logger, level, Time.now, Process.pid, exception, options, content, &content_proc
|
19
|
+
Message.new logger, level, Time.now, Process.pid, Message.convert_exception(exception), options, content, &content_proc
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_json(json_msg)
|
23
|
+
l = Level.new(json_msg[:level_name], json_msg[:level_value])
|
24
|
+
# note: the exception key contains a hash-level representation of an exception
|
25
|
+
Message.create(json_msg[:logger], l, json_msg[:exception], json_msg[:options], json_msg[:content], json_msg[:content_proc])
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_json
|
29
|
+
return {
|
30
|
+
logger: @logger.to_s,
|
31
|
+
level: {level_value: @level.value, level_name: @level.name},
|
32
|
+
time: @time.to_s,
|
33
|
+
pid: @pid,
|
34
|
+
exception: @exception,
|
35
|
+
options: @options,
|
36
|
+
content: self.content,
|
37
|
+
content_proc: nil
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.convert_exception(exc)
|
42
|
+
if exc.is_a? Exception
|
43
|
+
return {
|
44
|
+
exc_class: exc.exception.class,
|
45
|
+
exc_message: exc.exception.message,
|
46
|
+
exc_backtrace: exc.exception.backtrace
|
47
|
+
}
|
48
|
+
else
|
49
|
+
return exc
|
50
|
+
end
|
21
51
|
end
|
22
52
|
|
23
53
|
# Returns the content. If the content has not been created yet,
|
@@ -32,8 +62,5 @@ module Logbert
|
|
32
62
|
end
|
33
63
|
end
|
34
64
|
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
65
|
+
end # class Message
|
66
|
+
end # module Logbert
|
data/lib/logbert/naming.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logbert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Lauber
|
@@ -9,18 +9,49 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2014-04-29 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lockfile
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.1.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.1.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.1.0
|
13
41
|
description: Change your logging behaviors without mucking with your code!
|
14
42
|
email: constructible.truth@gmail.com
|
15
43
|
executables: []
|
16
44
|
extensions: []
|
17
45
|
extra_rdoc_files: []
|
18
46
|
files:
|
47
|
+
- lib/logbert/consumers/redis_consumer.rb
|
48
|
+
- lib/logbert/consumers.rb
|
19
49
|
- lib/logbert/formatters.rb
|
20
50
|
- lib/logbert/handlers/array_handler.rb
|
21
51
|
- lib/logbert/handlers/base_handler.rb
|
52
|
+
- lib/logbert/handlers/log_rotator.rb
|
53
|
+
- lib/logbert/handlers/redis_handler.rb
|
22
54
|
- lib/logbert/handlers/stream_handler.rb
|
23
|
-
- lib/logbert/handlers/time_rotator.rb
|
24
55
|
- lib/logbert/handlers.rb
|
25
56
|
- lib/logbert/levels.rb
|
26
57
|
- lib/logbert/logger.rb
|
@@ -38,17 +69,17 @@ require_paths:
|
|
38
69
|
- lib
|
39
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
40
71
|
requirements:
|
41
|
-
- - '>='
|
72
|
+
- - ! '>='
|
42
73
|
- !ruby/object:Gem::Version
|
43
74
|
version: '0'
|
44
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
76
|
requirements:
|
46
|
-
- - '>='
|
77
|
+
- - ! '>='
|
47
78
|
- !ruby/object:Gem::Version
|
48
79
|
version: '0'
|
49
80
|
requirements: []
|
50
81
|
rubyforge_project:
|
51
|
-
rubygems_version: 2.
|
82
|
+
rubygems_version: 2.1.11
|
52
83
|
signing_key:
|
53
84
|
specification_version: 4
|
54
85
|
summary: Logging for winners.
|
@@ -1,80 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'fileutils'
|
3
|
-
require 'logbert/handlers/base_handler'
|
4
|
-
|
5
|
-
module Logbert
|
6
|
-
|
7
|
-
module Handlers
|
8
|
-
|
9
|
-
class LocaltimeFormatter
|
10
|
-
|
11
|
-
attr_accessor :format
|
12
|
-
|
13
|
-
def initialize(format = "%Y-%m-%d-%H%M")
|
14
|
-
@format = format
|
15
|
-
end
|
16
|
-
|
17
|
-
def format(time)
|
18
|
-
time.strftime(@format)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
class TimeRotator < Logbert::Handlers::BaseHandler
|
24
|
-
|
25
|
-
|
26
|
-
attr_reader :path, :stream, :timestamp_formatter
|
27
|
-
attr_reader :interval, :expiration_time
|
28
|
-
|
29
|
-
def initialize(path, options = {})
|
30
|
-
@path = path
|
31
|
-
@timestamp_formatter = options[:timestamp_formatter] || LocaltimeFormatter.new
|
32
|
-
@interval = options.fetch(:iterval, 24 * 60 * 60)
|
33
|
-
|
34
|
-
rotate_log!
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
def rotation_needed?
|
39
|
-
Time.now > @expiration_time
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
def rotate_log!
|
44
|
-
if @stream and not @stream.closed?
|
45
|
-
@stream.close
|
46
|
-
end
|
47
|
-
|
48
|
-
if File.exists? @path
|
49
|
-
FileUtils.mv @path, archive_destination
|
50
|
-
end
|
51
|
-
|
52
|
-
@stream = File.open(@path, "ab")
|
53
|
-
|
54
|
-
@expiration_time = Time.now + @interval
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
def emit(output)
|
60
|
-
rotate_log! if rotation_needed?
|
61
|
-
@stream.puts output
|
62
|
-
@stream.flush
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
|
69
|
-
def archive_destination
|
70
|
-
timestamp = @timestamp_formatter.format(File.ctime(@path))
|
71
|
-
dest = "#{path}.#{timestamp}"
|
72
|
-
return dest
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|