betterlog 0.1.0 → 0.2.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.
data/lib/betterlog/log.rb CHANGED
@@ -4,178 +4,195 @@ require 'betterlog/log/event'
4
4
  require 'betterlog/log/event_formatter'
5
5
  require 'betterlog/log/severity'
6
6
 
7
- class Log
8
- include Tins::SexySingleton
7
+ module Betterlog
8
+ class Log
9
+ include Tins::SexySingleton
9
10
 
10
- def logger
11
- Rails.logger
12
- end
11
+ class_attr_accessor :default_logger
12
+ self.default_logger = Logger.new(STDERR)
13
13
 
14
- # Logs a message on severity info.
15
- #
16
- # @param object this object is logged
17
- # @param **rest additional data is logged as well.
18
- # @return [ Log ] this object itself.
19
- def info(object, **rest)
20
- protect do
21
- emit Log::Event.ify(object, severity: __method__, rest: rest)
14
+ def logger
15
+ defined?(Rails) && Rails.logger || self.class.default_logger
22
16
  end
23
- end
24
17
 
25
- # Logs a message on severity warn.
26
- #
27
- # @param object this object is logged
28
- # @param **rest additional data is logged as well.
29
- # @return [ Log ] this object itself.
30
- def warn(object, **rest)
31
- protect do
32
- emit Log::Event.ify(object, severity: __method__, rest: rest)
18
+ # Logs a message on severity info.
19
+ #
20
+ # @param object this object is logged
21
+ # @param **rest additional data is logged as well.
22
+ # @return [ Log ] this object itself.
23
+ def info(object, **rest)
24
+ protect do
25
+ emit Log::Event.ify(object, severity: __method__, rest: rest)
26
+ end
33
27
  end
34
- end
35
28
 
36
- # Logs a message on severity debug.
37
- #
38
- # @param object this object is logged
39
- # @param **rest additional data is logged as well.
40
- # @return [ Log ] this object itself.
41
- def debug(object, **rest)
42
- protect do
43
- emit Log::Event.ify(object, severity: __method__, rest: rest)
29
+ # Logs a message on severity warn.
30
+ #
31
+ # @param object this object is logged
32
+ # @param **rest additional data is logged as well.
33
+ # @return [ Log ] this object itself.
34
+ def warn(object, **rest)
35
+ protect do
36
+ emit Log::Event.ify(object, severity: __method__, rest: rest)
37
+ end
44
38
  end
45
- end
46
39
 
47
- # Logs a message on severity error.
48
- #
49
- # @param object this object is logged
50
- # @param **rest additional data is logged as well.
51
- # @return [ Log ] this object itself.
52
- def error(object, **rest)
53
- protect do
54
- emit Log::Event.ify(object, severity: __method__, rest: rest)
40
+ # Logs a message on severity debug.
41
+ #
42
+ # @param object this object is logged
43
+ # @param **rest additional data is logged as well.
44
+ # @return [ Log ] this object itself.
45
+ def debug(object, **rest)
46
+ protect do
47
+ emit Log::Event.ify(object, severity: __method__, rest: rest)
48
+ end
55
49
  end
56
- end
57
50
 
58
- # Logs a message on severity fatal.
59
- #
60
- # @param object this object is logged
61
- # @param **rest additional data is logged as well.
62
- # @return [ Log ] this object itself.
63
- def fatal(object, **rest)
64
- protect do
65
- emit Log::Event.ify(object, severity: __method__, rest: rest)
51
+ # Logs a message on severity error.
52
+ #
53
+ # @param object this object is logged
54
+ # @param **rest additional data is logged as well.
55
+ # @return [ Log ] this object itself.
56
+ def error(object, **rest)
57
+ protect do
58
+ emit Log::Event.ify(object, severity: __method__, rest: rest)
59
+ end
66
60
  end
67
- end
68
61
 
69
- # Logs a message on severity debug, by default, this can be changed by
70
- # passing the severity: keyword.
71
- #
72
- # @param object this object is logged
73
- # @param **rest additional data is logged as well.
74
- # @return [ Log ] this object itself.
75
- def output(object, **rest)
76
- protect do
77
- emit Log::Event.ify(object, severity: rest[:severity], rest: rest)
62
+ # Logs a message on severity fatal.
63
+ #
64
+ # @param object this object is logged
65
+ # @param **rest additional data is logged as well.
66
+ # @return [ Log ] this object itself.
67
+ def fatal(object, **rest)
68
+ protect do
69
+ emit Log::Event.ify(object, severity: __method__, rest: rest)
70
+ end
78
71
  end
79
- end
80
72
 
81
- # Logs a metric on severity debug, by default, this can be changed by passing
82
- # the severity: keyword.
83
- #
84
- # @param metric the name of the recorded metric.
85
- # @param type of the recorded metric.
86
- # @param value of the recorded metric.
87
- # @param **rest additional rest is logged as well.
88
- # @return [ Log ] this object itself.
89
- def metric(metric:, type:, value:, **rest)
90
- protect do
91
- event = build_metric(metric: metric, type: type, value: value, **rest)
92
- emit event
73
+ # Logs a message on severity debug, by default, this can be changed by
74
+ # passing the severity: keyword.
75
+ #
76
+ # @param object this object is logged
77
+ # @param **rest additional data is logged as well.
78
+ # @return [ Log ] this object itself.
79
+ def output(object, **rest)
80
+ protect do
81
+ emit Log::Event.ify(object, severity: rest[:severity], rest: rest)
82
+ end
93
83
  end
94
- end
95
84
 
96
- # Logs a time measure on severity debug, by default, this can be changed by
97
- # passing the severity: keyword.
98
- #
99
- # If an error occurs during measurement details about it are added to the
100
- # metric event.
101
- #
102
- # @param metric the name of the recorded metric.
103
- # @param **rest additional rest is logged as well.
104
- # @param block the block around which the measure is teaken.
105
- # @return [ Log ] this object itself.
106
- def measure(metric:, **rest, &block)
107
- raise ArgumentError, 'must be called with a block' unless block_given?
108
- time_block { yield }
109
- rescue => error
110
- e = Log::Event.ify(error)
111
- rest |= e.as_hash.subhash(:error_class, :backtrace, :message)
112
- rest[:message] = "#{rest[:message]} while measuring metric #{metric}"
113
- raise error
114
- ensure
115
- protect do
116
- event = build_metric(metric: metric, type: 'seconds', value: timed_duration, **rest)
117
- emit event
85
+ # Logs a metric on severity debug, by default, this can be changed by passing
86
+ # the severity: keyword.
87
+ #
88
+ # @param metric the name of the recorded metric.
89
+ # @param type of the recorded metric.
90
+ # @param value of the recorded metric.
91
+ # @param **rest additional rest is logged as well.
92
+ # @return [ Log ] this object itself.
93
+ def metric(metric:, type:, value:, **rest)
94
+ protect do
95
+ event = build_metric(metric: metric, type: type, value: value, **rest)
96
+ emit event
97
+ end
118
98
  end
119
- end
120
99
 
121
- private
122
-
123
- def protect
124
- yield
125
- rescue => e
126
- begin
127
- # Try logging e once by ourselves
128
- emit Log::Event.ify(e, severity: :fatal)
129
- rescue
130
- # Ok, I give up let's use logger directly instead
131
- logger.fatal(
132
- "Crashed during logging with #{e.class}: #{e.message}):\n"\
133
- "#{e.backtrace * ?\n}"
134
- )
100
+ # Logs a time measure on severity debug, by default, this can be changed by
101
+ # passing the severity: keyword.
102
+ #
103
+ # If an error occurs during measurement details about it are added to the
104
+ # metric event.
105
+ #
106
+ # @param metric the name of the recorded metric.
107
+ # @param **rest additional rest is logged as well.
108
+ # @param block the block around which the measure is teaken.
109
+ # @return [ Log ] this object itself.
110
+ def measure(metric:, **rest, &block)
111
+ raise ArgumentError, 'must be called with a block' unless block_given?
112
+ time_block { yield }
113
+ rescue => error
114
+ e = Log::Event.ify(error)
115
+ rest |= e.as_hash.subhash(:error_class, :backtrace, :message)
116
+ rest[:message] = "#{rest[:message]} while measuring metric #{metric}"
117
+ raise error
118
+ ensure
119
+ protect do
120
+ event = build_metric(metric: metric, type: 'seconds', value: timed_duration, **rest)
121
+ emit event
122
+ end
135
123
  end
136
- self
137
- end
138
124
 
139
- def build_metric(metric:, type:, value:, **rest)
140
- severity = rest.fetch(:severity, :debug)
141
- rest |= {
142
- message: "a metric #{metric} of type #{type}",
143
- }
144
- Log::Event.ify(
145
- {
146
- metric: metric,
147
- type: type,
148
- value: value,
149
- } | rest,
150
- severity: severity
151
- )
152
- end
125
+ def context(data_hash)
126
+ GlobalMetadata.add data_hash
127
+ self
128
+ end
129
+
130
+ def self.context(data_hash)
131
+ instance.context(data_hash)
132
+ end
153
133
 
154
- def emit(event)
155
- if l = caller_locations.reverse_each.each_cons(3).find { |c, n1, n2|
156
- n2.absolute_path =~ /app\/lib\/log\.rb/ and break c
157
- }
158
- then
159
- event[:location] = [ l.absolute_path, l.lineno ] * ?:
134
+ private
135
+
136
+ def protect
137
+ yield
138
+ rescue => e
139
+ begin
140
+ # Try logging e once by ourselves
141
+ emit Log::Event.ify(e, severity: :fatal)
142
+ rescue
143
+ # Ok, I give up let's use logger directly instead
144
+ logger.fatal(
145
+ "Crashed during logging with #{e.class}: #{e.message}):\n"\
146
+ "#{e.backtrace * ?\n}"
147
+ )
148
+ end
149
+ self
150
+ end
151
+
152
+ def build_metric(metric:, type:, value:, **rest)
153
+ severity = rest.fetch(:severity, :debug)
154
+ rest |= {
155
+ message: "a metric #{metric} of type #{type}",
156
+ }
157
+ Log::Event.ify(
158
+ {
159
+ metric: metric,
160
+ type: type,
161
+ value: value,
162
+ } | rest,
163
+ severity: severity
164
+ )
160
165
  end
161
- event[:emitter] = self.class.name.downcase
162
- if event.notify?
166
+
167
+ def emit(event)
168
+ l = caller_locations.reverse_each.each_cons(3).find { |c, n1, n2|
169
+ n2.absolute_path =~ /betterlog\/log\.rb/ and break c # TODO check if this still works
170
+ }
171
+ if l
172
+ event[:location] = [ l.absolute_path, l.lineno ] * ?:
173
+ end
174
+ event[:emitter] = self.class.name.downcase
163
175
  notify(event)
176
+ logger.send(event.severity.to_sym, event.to_json)
177
+ self
178
+ ensure
179
+ GlobalMetadata.data.clear
164
180
  end
165
- logger.send(event.severity.to_sym, event.to_json)
166
- self
167
- end
168
181
 
169
- def notify(event)
170
- Honeybadger.notify(event.notify?, event.as_hash)
171
- end
182
+ def notify(event)
183
+ if event.notify?
184
+ Notifiers.notify(event)
185
+ self
186
+ end
187
+ end
172
188
 
173
- thread_local :timed_duration
189
+ thread_local :timed_duration
174
190
 
175
- def time_block
176
- s = Time.now
177
- yield
178
- ensure
179
- self.timed_duration = Time.now - s
191
+ def time_block
192
+ s = Time.now
193
+ yield
194
+ ensure
195
+ self.timed_duration = Time.now - s
196
+ end
180
197
  end
181
198
  end
@@ -1,47 +1,49 @@
1
- class LogEventFormatter < ActiveSupport::Logger::Formatter
2
- include ActiveSupport::TaggedLogging::Formatter
1
+ module Betterlog
2
+ class LogEventFormatter < ActiveSupport::Logger::Formatter
3
+ include ActiveSupport::TaggedLogging::Formatter
3
4
 
4
- def emitter
5
- 'legacy'
6
- end
5
+ def emitter
6
+ 'legacy'
7
+ end
7
8
 
8
- def call(severity, timestamp, program, message)
9
- super
10
- message = message.to_s
11
- if cc.log.legacy_supported
12
- if message.blank?
13
- return ''
14
- elsif !Log::Event.is?(message)
15
- m = message.sub(/\s+$/, '')
16
- timestamp = timestamp.utc.iso8601(3)
17
- event = Log::Event.new(
18
- emitter: emitter,
19
- timestamp: timestamp,
20
- message: m,
21
- severity: severity.to_s.downcase,
22
- # tags: current_tags,
23
- )
24
- if backtrace = m.grep(/^\s*([^:]+):(\d+)/)
25
- if backtrace.size > 1
26
- event[:backtrace] = backtrace.map(&:chomp)
27
- event[:message] = 'a logged backtrace'
9
+ def call(severity, timestamp, program, message)
10
+ super
11
+ message = message.to_s
12
+ if cc.log.legacy_supported
13
+ if message.blank?
14
+ return ''
15
+ elsif !Log::Event.is?(message)
16
+ m = message.sub(/\s+$/, '')
17
+ timestamp = timestamp.utc.iso8601(3)
18
+ event = Log::Event.new(
19
+ emitter: emitter,
20
+ timestamp: timestamp,
21
+ message: m,
22
+ severity: severity.to_s.downcase,
23
+ # tags: current_tags,
24
+ )
25
+ if backtrace = m.grep(/^\s*([^:]+):(\d+)/)
26
+ if backtrace.size > 1
27
+ event[:backtrace] = backtrace.map(&:chomp)
28
+ event[:message] = 'a logged backtrace'
29
+ end
28
30
  end
31
+ if l = caller_locations.reverse_each.each_cons(2).find { |c, n|
32
+ n.absolute_path =~ /\/lib\/ruby\/.*?\/logger\.rb/ and break c
33
+ }
34
+ then
35
+ event[:location] = [ l.absolute_path, l.lineno ] * ?:
36
+ end
37
+ program and event[:program] = program
38
+ message = event.to_json
29
39
  end
30
- if l = caller_locations.reverse_each.each_cons(2).find { |c, n|
31
- n.absolute_path =~ /\/lib\/ruby\/.*?\/logger\.rb/ and break c
32
- }
33
- then
34
- event[:location] = [ l.absolute_path, l.lineno ] * ?:
35
- end
36
- program and event[:program] = program
37
- message = event.to_json
38
40
  end
41
+ rescue => e
42
+ Betterlog::Log.logger.error(e)
43
+ ensure
44
+ # Do not "message << ?\n" - A frozn string may be passed in
45
+ message.end_with?(?\n) or message = "#{message}\n"
46
+ return message
39
47
  end
40
- rescue => e
41
- Rails.logger.error(e)
42
- ensure
43
- # Do not "message << ?\n" - A frozn string may be passed in
44
- message.end_with?(?\n) or message = "#{message}\n"
45
- return message
46
48
  end
47
49
  end
@@ -0,0 +1,88 @@
1
+ require 'redis'
2
+
3
+ module Betterlog
4
+ class Logger < ::Logger
5
+ def initialize(redis, shift_age = 0, shift_size = 1048576, name: nil, **opts)
6
+ @redis = redis
7
+ @name = name || self.class.name
8
+ super(@logdev, shift_age, shift_size, **opts)
9
+ end
10
+
11
+ private def redis_write(msg)
12
+ # Redis string limit is at 512MB, stop before that after warning a lot.
13
+ if @redis.strlen(@name) > 511 * 1024 ** 2
14
+ return nil
15
+ end
16
+ if @redis.strlen(@name) > 510 * 1024 ** 2
17
+ @redis.append @name, "\nRedis memory limit will soon be reached =>"\
18
+ " Log output to redis stops now unless log data is pushed away!\n"
19
+ return nil
20
+ end
21
+ @redis.append @name, msg
22
+ self
23
+ end
24
+
25
+
26
+ def add(severity, message = nil, progname = nil)
27
+ severity ||= UNKNOWN
28
+ if severity < @level
29
+ return true
30
+ end
31
+ if progname.nil?
32
+ progname = @progname
33
+ end
34
+ if message.nil?
35
+ if block_given?
36
+ message = yield
37
+ else
38
+ message = progname
39
+ progname = @progname
40
+ end
41
+ end
42
+ redis_write(
43
+ format_message(format_severity(severity), Time.now, progname, message))
44
+ true
45
+ end
46
+
47
+ def <<(msg)
48
+ redis_write(msg)
49
+ end
50
+
51
+ def clear
52
+ @redis.del @name
53
+ self
54
+ end
55
+
56
+ def each_chunk(chunk_size: 100 * 1024, &block)
57
+ chunk_size > 0 or raise ArgumentError, 'chunk_size > 0 required'
58
+ @redis.exists(@name) or return Enumerator.new {}
59
+ Enumerator.new do |y|
60
+ name_tmp = "#{@name}_#{rand}"
61
+ @redis.rename @name, name_tmp
62
+ s = 0
63
+ e = @redis.strlen(name_tmp) - 1
64
+ until s > e
65
+ y.yield @redis.getrange(name_tmp, s, s + chunk_size - 1)
66
+ s += chunk_size
67
+ end
68
+ @redis.del name_tmp
69
+ end.each(&block)
70
+ end
71
+
72
+ def each(chunk_size: 100 * 1024, &block)
73
+ chunk_size > 0 or raise ArgumentError, 'chunk_size > 0 required'
74
+ Enumerator.new do |y|
75
+ buffer = ''
76
+ each_chunk(chunk_size: chunk_size) do |chunk|
77
+ buffer << chunk
78
+ buffer.gsub!(/\A(.*?#$/)/) do |line|
79
+ y.yield(line)
80
+ ''
81
+ end
82
+ end
83
+ buffer.length > 0 and y.yield(buffer)
84
+ end.each(&block)
85
+ end
86
+ include Enumerable
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ module Betterlog
2
+ module Notifiers
3
+
4
+ class_attr_accessor :notifiers
5
+
6
+ self.notifiers = Set[]
7
+
8
+ def self.register(notifier)
9
+ notifier.respond_to?(:notify) or raise TypeError,
10
+ "notifier has to respond to notify(message, hash) interface"
11
+ notifiers << notifier
12
+ self
13
+ end
14
+
15
+ def self.notify(event)
16
+ notifiers.each do |notifier|
17
+ notifier.notify(event.notify?, event.as_hash)
18
+ end
19
+ end
20
+
21
+ def self.context(data_hash)
22
+ notifiers.each do |notifier|
23
+ notifier.respond_to?(:context) or next
24
+ notifier.context(data_hash)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ module Betterlog
2
+ class Railtie < Rails::Railtie
3
+ initializer "betterlog_railtie.configure_rails_initialization" do
4
+ Rails.logger = Betterlog::Logger.new(Redis.new) # TODO
5
+ Rails.logger.formatter = Betterlog::LogEventFormatter.new
6
+ end
7
+ end
8
+ end
@@ -1,6 +1,6 @@
1
1
  module Betterlog
2
2
  # Betterlog version
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/betterlog.rb CHANGED
@@ -2,13 +2,19 @@ require 'tins/xt'
2
2
  require 'json'
3
3
  require 'logger'
4
4
  require 'time'
5
- require 'tins'
6
- require 'rails'
7
-
8
- require 'active_support'
9
5
  require 'term/ansicolor'
10
6
 
11
- require 'betterlog/global_metadata'
12
- require 'betterlog/betterlog_railtie'
7
+ module Betterlog
8
+ end
9
+
13
10
  require 'betterlog/log'
14
- require 'betterlog/log_event_formatter'
11
+ require 'betterlog/notifiers'
12
+ require 'betterlog/global_metadata'
13
+ require 'betterlog/logger'
14
+
15
+ if defined? Rails
16
+ require 'betterlog/log_event_formatter'
17
+ require 'betterlog/railtie'
18
+ end
19
+
20
+ Log = Betterlog::Log
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Betterlog::GlobalMetadata do
4
+ let :notifier do
5
+ Class.new do
6
+ def notify(message, hash) end
7
+
8
+ def context(data_hash) end
9
+ end.new
10
+ end
11
+
12
+ around do |example|
13
+ Betterlog::Notifiers.register(notifier)
14
+ example.run
15
+ ensure
16
+ Betterlog::Notifiers.notifiers.clear
17
+ described_class.data.clear
18
+ end
19
+
20
+ it 'can haz empty data' do
21
+ expect(described_class.data).to eq({})
22
+ end
23
+
24
+ it 'can haz some data' do
25
+ described_class.data |= { foo: 'bar' }
26
+ expect(described_class.data).to eq({ foo: 'bar' })
27
+ end
28
+
29
+ it 'can "add" data' do
30
+ expect(notifier).to receive(:context).with(foo: 'bar')
31
+ expect(described_class.add(foo: 'bar')).to eq described_class.instance
32
+ end
33
+
34
+ it 'can "add" data via Log.context' do
35
+ expect(notifier).to receive(:context).with(foo: 'bar')
36
+ Betterlog::Log.context(foo: 'bar')
37
+ end
38
+ end