betterlog 0.1.0 → 0.2.0

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