logbert 0.6.19 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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