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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b275b578f275a5128b532985399a1fe462f9378e
4
- data.tar.gz: dfa06f7f9fa72e013f4ad2a7497f11eff15d9a76
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWZhNDk3MmU1ZjgxMDViZjhhZjE5NTZhNDVhYjJlY2RmZjljOTdkYw==
5
+ data.tar.gz: !binary |-
6
+ ZmQwMjJiYjMxNjk4OGYxMTg3OGIzZTkzYzRiYTQ3NmFlN2I2ZWMxNQ==
5
7
  SHA512:
6
- metadata.gz: b762386179f3eeca54471dc85f0454fd35135cbae10a37c4d2c457c830a74117c77022d5a327ece3a1ff812ec33cd827adff2ddd8be378c89e3c9efb486a2590
7
- data.tar.gz: cbb567dd894426805334729352422c433c3184d4bddfa5ea9e73ec90c3589946cdb9b283be4fbd7a2ae8227056920765a35def1723f127b1ae3d306f1542dcfc
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
@@ -1,4 +1,5 @@
1
1
 
2
2
  require 'logbert/handlers/stream_handler'
3
- require 'logbert/handlers/time_rotator'
4
- require 'logbert/handlers/array_handler'
3
+ require 'logbert/handlers/log_rotator'
4
+ require 'logbert/handlers/array_handler'
5
+ require 'logbert/handlers/redis_handler'
@@ -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
@@ -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
-
@@ -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
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Logbert
3
-
3
+
4
4
  NameSeparator = "/"
5
5
 
6
6
  def self.split_name(name)
@@ -16,5 +16,3 @@ module Logbert
16
16
  end
17
17
 
18
18
  end
19
-
20
-
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.6.19
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.0.3
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