Skip to content

Commit e8f8b79

Browse files
committed
Logger option to specify ignored locations
Introduce option :ignored_locations for Steno::Logger. When provided all frames from the callstack matching the regex are ignored, i.e. the first non-matching frame denotes the record's location. This option can also be set via a Steno::Config instance that is passed to Steno.init before using Steno.logger to get a (cached) Steno::Logger instance. Inspired by Sequel's caller_logging extension [1]. [1] https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/ CallerLogging.html#attribute-i-caller_logging_ignore
1 parent cb5a012 commit e8f8b79

File tree

9 files changed

+116
-46
lines changed

9 files changed

+116
-46
lines changed

.rubocop.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ require:
66

77
AllCops:
88
NewCops: enable
9+
10+
RSpec/NestedGroups:
11+
Max: 4
12+
13+
RSpec/MultipleExpectations:
14+
Max: 7

lib/steno.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def logger(name)
4747

4848
logger = Steno::Logger.new(name, @config.sinks,
4949
level: level,
50-
context: @config.context)
50+
context: @config.context,
51+
ignored_locations: @config.ignored_locations)
5152

5253
@loggers[name] = logger
5354
end

lib/steno/config.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,13 @@ def symbolize_keys(hash)
7070
end
7171
end
7272

73-
attr_reader :sinks, :codec, :context, :default_log_level
73+
attr_reader :sinks, :codec, :context, :ignored_locations, :default_log_level
7474

7575
def initialize(opts = {})
7676
@sinks = opts[:sinks] || []
7777
@codec = opts[:codec] || Steno::Codec::Json.new
7878
@context = opts[:context] || Steno::Context::Null.new
79+
@ignored_locations = opts[:ignored_locations]
7980

8081
@sinks.each { |sink| sink.codec = @codec }
8182

lib/steno/log_level.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Steno::LogLevel
66

77
attr_reader :name, :priority
88

9-
# @param [String] name "info", "debug", etc.
9+
# @param [Symbol] name :info, :debug, etc.
1010
# @param [Integer] priority "info" > "debug", etc.
1111
def initialize(name, priority)
1212
@name = name

lib/steno/logger.rb

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ module Steno
99
class Steno::Logger
1010
LEVELS = {
1111
off: Steno::LogLevel.new(:off, 0),
12-
fatal: Steno::LogLevel.new(:fatal, 1),
13-
error: Steno::LogLevel.new(:error, 5),
14-
warn: Steno::LogLevel.new(:warn, 10),
15-
info: Steno::LogLevel.new(:info, 15),
12+
fatal: Steno::LogLevel.new(:fatal, 1),
13+
error: Steno::LogLevel.new(:error, 5),
14+
warn: Steno::LogLevel.new(:warn, 10),
15+
info: Steno::LogLevel.new(:info, 15),
1616
debug: Steno::LogLevel.new(:debug, 16),
1717
debug1: Steno::LogLevel.new(:debug1, 17),
1818
debug2: Steno::LogLevel.new(:debug2, 18),
@@ -60,18 +60,21 @@ def lookup_level(name)
6060

6161
attr_reader :name
6262

63-
# @param [String] name The logger name.
63+
# @param [String] name The logger name.
6464
# @param [Array<Steno::Sink::Base>] sinks
6565
# @param [Hash] opts
6666
# @option opts [Symbol] :level The minimum level for which this logger will
6767
# emit log records. Defaults to :info.
6868
# @option opts [Steno::Context] :context
69+
# @option opts [Regex] :ignored_locations User specified regex matching
70+
# ignored locations.
6971
def initialize(name, sinks, opts = {})
70-
@name = name
71-
@min_level = self.class.lookup_level(opts[:level] || :info)
72-
@min_level_lock = Mutex.new
73-
@sinks = sinks
74-
@context = opts[:context] || Steno::Context::Null.new
72+
@name = name
73+
@min_level = self.class.lookup_level(opts[:level] || :info)
74+
@min_level_lock = Mutex.new
75+
@sinks = sinks
76+
@context = opts[:context] || Steno::Context::Null.new
77+
@ignored_locations = opts[:ignored_locations]
7578
end
7679

7780
# Sets the minimum level for which records will be added to sinks.
@@ -114,7 +117,7 @@ def log_exception(ex, user_data = {})
114117

115118
# Adds a record to the configured sinks.
116119
#
117-
# @param [Symbol] level_name The level associated with the record
120+
# @param [Symbol] level_name The level associated with the record
118121
# @param [String] message
119122
# @param [Hash] user_data
120123
#
@@ -124,7 +127,7 @@ def log(level_name, message = nil, user_data = nil)
124127

125128
message = yield if block_given?
126129

127-
callstack = caller
130+
callstack = Kernel.caller
128131
loc = parse_record_loc(callstack)
129132

130133
data = @context.data.merge(user_data || {})
@@ -149,22 +152,16 @@ def tag(user_data = {})
149152
private
150153

151154
def parse_record_loc(callstack)
152-
file = nil
153-
lineno = nil
154-
method = nil
155+
frame = callstack.find { |f| !(f =~ /logger\.rb/ || ignored_location?(f)) } || callstack.last
155156

156-
callstack.each do |frame|
157-
next if frame =~ /logger\.rb/
158-
159-
file, lineno, method = frame.split(':')
160-
161-
lineno = lineno.to_i
162-
163-
method = ::Regexp.last_match(1) if method =~ /in `([^']+)/
164-
165-
break
166-
end
157+
file, lineno, method = frame.split(':')
158+
lineno = lineno.to_i
159+
method = ::Regexp.last_match(1) if method =~ /in `([^`']+)/
167160

168161
[file, lineno, method]
169162
end
163+
164+
def ignored_location?(frame)
165+
!@ignored_locations.nil? && frame =~ @ignored_locations
166+
end
170167
end

lib/steno/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Steno
2-
VERSION = '1.3.6'
2+
VERSION = '1.4.0'
33
end

spec/unit/config_spec.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
expect(@config.default_log_level).to eq(:debug2)
2929
expect(@config.context.class).to eq(Steno::Context::Null)
30+
expect(@config.ignored_locations).to eq(nil)
3031
expect(@config.codec.class).to eq(Steno::Codec::Json)
3132

3233
expect(@config.sinks.size).to eq(2)
@@ -76,6 +77,7 @@
7677

7778
expect(@config.default_log_level).to eq(:debug2)
7879
expect(@config.context.class).to eq(Steno::Context::Null)
80+
expect(@config.ignored_locations).to eq(nil)
7981
expect(@config.codec.class).to eq(Steno::Codec::Json)
8082

8183
expect(@config.sinks.size).to eq(2)
@@ -127,6 +129,8 @@
127129

128130
expect(config.context.class).to eq(Steno::Context::Null)
129131

132+
expect(config.ignored_locations).to eq(nil)
133+
130134
expect(config.codec.class).to eq(Steno::Codec::Json)
131135
expect(config.codec.iso8601_timestamps?).to eq(false)
132136
end
@@ -208,10 +212,13 @@
208212
write_config(@config_path, { 'default_log_level' => 'debug' })
209213

210214
context = Steno::Context::ThreadLocal.new
215+
ignored_locations = /location-to-ignore/
211216
config = Steno::Config.from_file(@config_path,
212217
default_log_level: 'warn',
213-
context: context)
218+
context: context,
219+
ignored_locations: ignored_locations)
214220
expect(config.context).to eq(context)
221+
expect(config.ignored_locations).to eq(ignored_locations)
215222
expect(config.default_log_level).to eq(:warn)
216223
end
217224
end

spec/unit/logger_spec.rb

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,23 @@
4343
end
4444

4545
describe '#log' do
46-
it 'does not forward any messages for levels that are inactive' do
47-
sink = double('sink')
48-
expect(sink).not_to receive(:add_record)
46+
let(:sink) { instance_double(Steno::Sink::Base) }
47+
let(:logger) { Steno::Logger.new('test', [sink]) }
4948

50-
my_logger = Steno::Logger.new('test', [sink])
49+
before do
50+
allow(sink).to receive(:add_record)
51+
end
5152

52-
my_logger.debug('test')
53+
it 'does not forward any messages for levels that are inactive' do
54+
logger.debug('test')
55+
56+
expect(sink).not_to have_received(:add_record)
5357
end
5458

5559
it 'forwards messages for levels that are active' do
56-
sink = double('sink')
57-
expect(sink).to receive(:add_record).with(any_args)
58-
59-
my_logger = Steno::Logger.new('test', [sink])
60+
logger.warn('test')
6061

61-
my_logger.warn('test')
62+
expect(sink).to have_received(:add_record).with(any_args)
6263
end
6364

6465
it 'does not invoke a supplied block if the level is inactive' do
@@ -74,13 +75,70 @@
7475
end
7576

7677
it 'creates a record with the proper level' do
77-
sink = double('sink')
7878
expect(Steno::Record).to receive(:new).with('test', :warn, 'message', anything, anything).and_call_original
79-
allow(sink).to receive(:add_record)
8079

81-
my_logger = Steno::Logger.new('test', [sink])
80+
logger.warn('message')
81+
end
82+
83+
it 'includes the location where the record was generated' do
84+
location = [__FILE__, an_instance_of(Integer), 'log']
85+
expect(Steno::Record).to receive(:new).with('test', :warn, 'message', location, anything).and_call_original
86+
87+
def log(logger)
88+
logger.warn('message')
89+
end
90+
91+
log(logger)
92+
end
93+
94+
describe 'option :ignored_locations' do
95+
let(:logger) { Steno::Logger.new('test', [sink], ignored_locations: ignored_locations) }
96+
let(:callstack) do
97+
[
98+
'/path/to/lib/steno/logger.rb:12:in `block in define_log_method`',
99+
'/path/to/another_file.rb:34:in `yet_another_method`',
100+
'/path/to/some_file.rb:56:in `another_method`',
101+
'/path/to/some_file.rb:78:in `some_method`',
102+
'/path/to/program.rb:90:in `<main>'
103+
]
104+
end
105+
106+
before do
107+
allow(Kernel).to receive(:caller).and_return(callstack)
108+
end
109+
110+
context 'when ignoring a file' do
111+
let(:ignored_locations) { /another_file\.rb/ }
112+
113+
it 'includes the next file as location' do
114+
location = ['/path/to/some_file.rb', 56, 'another_method']
115+
expect(Steno::Record).to receive(:new).with('test', :warn, 'message', location, anything).and_call_original
116+
117+
logger.warn('message')
118+
end
119+
end
82120

83-
my_logger.warn('message')
121+
context 'when ignoring multiple files and methods' do
122+
let(:ignored_locations) { /(another_file\.rb|some_file\.rb.*another_method)/ }
123+
124+
it 'includes the next file/method as location' do
125+
location = ['/path/to/some_file.rb', 78, 'some_method']
126+
expect(Steno::Record).to receive(:new).with('test', :warn, 'message', location, anything).and_call_original
127+
128+
logger.warn('message')
129+
end
130+
end
131+
132+
context 'when regex is too broad' do
133+
let(:ignored_locations) { /.*/ }
134+
135+
it 'still includes a location (the last one) and does not fail' do
136+
location = [an_instance_of(String), an_instance_of(Integer), an_instance_of(String)]
137+
expect(Steno::Record).to receive(:new).with('test', :warn, 'message', location, anything).and_call_original
138+
139+
logger.warn('message')
140+
end
141+
end
84142
end
85143
end
86144

steno.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
2828

2929
gem.add_development_dependency('rack-test')
3030
gem.add_development_dependency('rake')
31-
gem.add_development_dependency('rspec', '~>3.13.0')
31+
gem.add_development_dependency('rspec', '~> 3.13.0')
3232
gem.add_development_dependency('rubocop', '~> 1.62.0')
3333
gem.add_development_dependency('rubocop-rake', '~> 0.6.0')
3434
gem.add_development_dependency('rubocop-rspec', '~> 2.27.0')

0 commit comments

Comments
 (0)