paul_bunyan 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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.travis.yml +9 -0
  4. data/Dockerfile +13 -0
  5. data/Gemfile +6 -0
  6. data/Guardfile +16 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +51 -0
  9. data/README.rdoc +3 -0
  10. data/Rakefile +19 -0
  11. data/bin/logging_demo +17 -0
  12. data/build.sh +7 -0
  13. data/docker-compose.yml +4 -0
  14. data/lib/paul_bunyan.rb +70 -0
  15. data/lib/paul_bunyan/json_formatter.rb +122 -0
  16. data/lib/paul_bunyan/level.rb +28 -0
  17. data/lib/paul_bunyan/log_relayer.rb +148 -0
  18. data/lib/paul_bunyan/rails_ext.rb +7 -0
  19. data/lib/paul_bunyan/rails_ext/instrumentation.rb +41 -0
  20. data/lib/paul_bunyan/rails_ext/rack_logger.rb +24 -0
  21. data/lib/paul_bunyan/railtie.rb +75 -0
  22. data/lib/paul_bunyan/railtie/log_subscriber.rb +182 -0
  23. data/lib/paul_bunyan/text_formatter.rb +11 -0
  24. data/lib/paul_bunyan/version.rb +3 -0
  25. data/lib/tasks/paul_bunyan_tasks.rake +4 -0
  26. data/paul_bunyan.gemspec +30 -0
  27. data/spec/dummy/Rakefile +6 -0
  28. data/spec/dummy/app/assets/images/.keep +0 -0
  29. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  32. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  33. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  34. data/spec/dummy/app/mailers/.keep +0 -0
  35. data/spec/dummy/app/models/.keep +0 -0
  36. data/spec/dummy/app/models/concerns/.keep +0 -0
  37. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/spec/dummy/bin/bundle +3 -0
  39. data/spec/dummy/bin/rails +4 -0
  40. data/spec/dummy/bin/rake +4 -0
  41. data/spec/dummy/bin/setup +29 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/config/application.rb +28 -0
  44. data/spec/dummy/config/boot.rb +5 -0
  45. data/spec/dummy/config/database.yml +25 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +41 -0
  48. data/spec/dummy/config/environments/production.rb +79 -0
  49. data/spec/dummy/config/environments/test.rb +42 -0
  50. data/spec/dummy/config/initializers/assets.rb +11 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  53. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/spec/dummy/config/initializers/inflections.rb +16 -0
  55. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  56. data/spec/dummy/config/initializers/secret_token.rb +1 -0
  57. data/spec/dummy/config/initializers/session_store.rb +3 -0
  58. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/dummy/config/locales/en.yml +23 -0
  60. data/spec/dummy/config/routes.rb +56 -0
  61. data/spec/dummy/config/secrets.yml +22 -0
  62. data/spec/dummy/lib/assets/.keep +0 -0
  63. data/spec/dummy/log/.keep +0 -0
  64. data/spec/dummy/public/404.html +67 -0
  65. data/spec/dummy/public/422.html +67 -0
  66. data/spec/dummy/public/500.html +66 -0
  67. data/spec/dummy/public/favicon.ico +0 -0
  68. data/spec/gemfiles/40.gemfile +5 -0
  69. data/spec/gemfiles/40.gemfile.lock +137 -0
  70. data/spec/gemfiles/41.gemfile +5 -0
  71. data/spec/gemfiles/41.gemfile.lock +142 -0
  72. data/spec/gemfiles/42.gemfile +5 -0
  73. data/spec/gemfiles/42.gemfile.lock +167 -0
  74. data/spec/lib/paul_bunyan/json_formatter_spec.rb +237 -0
  75. data/spec/lib/paul_bunyan/level_spec.rb +78 -0
  76. data/spec/lib/paul_bunyan/log_relayer_spec.rb +333 -0
  77. data/spec/lib/paul_bunyan/rails_ext/instrumentation_spec.rb +81 -0
  78. data/spec/lib/paul_bunyan/railtie/log_subscriber_spec.rb +304 -0
  79. data/spec/lib/paul_bunyan/railtie_spec.rb +37 -0
  80. data/spec/lib/paul_bunyan_spec.rb +137 -0
  81. data/spec/spec_helper.rb +24 -0
  82. data/spec/support/notification_helpers.rb +22 -0
  83. metadata +303 -0
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe PaulBunyan::Level do
4
+ describe '::coerce_level' do
5
+ it 'accepts integers', :aggregate_failures do
6
+ (0..5).each do |level|
7
+ aggregate_failures do
8
+ expect(PaulBunyan::Level.coerce_level(level)).to eq(level)
9
+ end
10
+ end
11
+ end
12
+
13
+ it 'rejects integers that are out-of-range', :aggregate_failures do
14
+ expect { PaulBunyan::Level.coerce_level(-1) }.to raise_error(PaulBunyan::UnknownLevelError)
15
+ expect { PaulBunyan::Level.coerce_level(6) }.to raise_error(PaulBunyan::UnknownLevelError)
16
+ end
17
+
18
+ it 'accepts integer strings', :aggregate_failures do
19
+ (0..5).each do |level|
20
+ aggregate_failures do
21
+ expect(PaulBunyan::Level.coerce_level(level.to_s)).to eq(level)
22
+ end
23
+ end
24
+ end
25
+
26
+ it 'rejects integer strings that are out-of-range', :aggregate_failures do
27
+ expect { PaulBunyan::Level.coerce_level(-1) }.to raise_error(PaulBunyan::UnknownLevelError)
28
+ expect { PaulBunyan::Level.coerce_level(6) }.to raise_error(PaulBunyan::UnknownLevelError)
29
+ end
30
+
31
+ it 'accepts integer strings with whitespace', :aggregate_failures do
32
+ (0..5).each do |level|
33
+ aggregate_failures do
34
+ expect(PaulBunyan::Level.coerce_level(" \t#{level}")).to eq(level)
35
+ expect(PaulBunyan::Level.coerce_level("#{level} \t")).to eq(level)
36
+ end
37
+ end
38
+ end
39
+
40
+ it 'accepts log level strings', :aggregate_failures do
41
+ {
42
+ 'DeBug' => Logger::DEBUG,
43
+ 'Info' => Logger::INFO,
44
+ 'warn' => Logger::WARN,
45
+ 'ERROR' => Logger::ERROR,
46
+ 'FataL' => Logger::FATAL,
47
+ 'uNKNOWn' => Logger::UNKNOWN
48
+ }.each do |k, v|
49
+ aggregate_failures do
50
+ expect(PaulBunyan::Level.coerce_level(k)).to eq(v)
51
+ end
52
+ end
53
+ end
54
+
55
+ it 'rejects invalid log level strings' do
56
+ expect { PaulBunyan::Level.coerce_level('critical') }.to raise_error(PaulBunyan::UnknownLevelError)
57
+ end
58
+
59
+ it 'accepts log level symbols', :aggregate_failures do
60
+ {
61
+ DeBug: Logger::DEBUG,
62
+ Info: Logger::INFO,
63
+ warn: Logger::WARN,
64
+ ERROR: Logger::ERROR,
65
+ FataL: Logger::FATAL,
66
+ uNKNOWn: Logger::UNKNOWN
67
+ }.each do |k, v|
68
+ aggregate_failures do
69
+ expect(PaulBunyan::Level.coerce_level(k)).to eq(v)
70
+ end
71
+ end
72
+ end
73
+
74
+ it 'rejects invalid log level symbols' do
75
+ expect { PaulBunyan::Level.coerce_level(:critical) }.to raise_error(PaulBunyan::UnknownLevelError)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,333 @@
1
+ require 'set'
2
+ require 'spec_helper'
3
+ require 'stringio'
4
+
5
+ describe PaulBunyan::LogRelayer do
6
+ let(:device) { StringIO.new }
7
+
8
+ let(:empty_relayer) { PaulBunyan::LogRelayer.new }
9
+
10
+ let(:primary) { double('primary logger') }
11
+ let(:single_relayer) do
12
+ PaulBunyan::LogRelayer.new.tap do |relayer|
13
+ relayer.add_logger(primary)
14
+ end
15
+ end
16
+
17
+ let(:secondary) { double('secondary logger') }
18
+ let(:double_relayer) do
19
+ single_relayer.tap do |relayer|
20
+ relayer.add_logger(secondary)
21
+ end
22
+ end
23
+
24
+ let(:tertiary) { double('tertiary logger') }
25
+ let(:triple_relayer) do
26
+ double_relayer.tap do |relayer|
27
+ relayer.add_logger(tertiary)
28
+ end
29
+ end
30
+
31
+ describe '::new' do
32
+ it 'with no parameters, creates an empty log relayer' do
33
+ expect(empty_relayer.loggers).to be_empty
34
+ end
35
+ end
36
+
37
+ describe '#loggers' do
38
+ # tested by specs for #primary_logger and #secondary_loggers
39
+ end
40
+
41
+ describe '#primary_logger' do
42
+ it 'matches the first value in #loggers' do
43
+ expect(double_relayer.primary_logger).to eq(double_relayer.loggers.first)
44
+ end
45
+
46
+ it 'is nil if there are no loggers' do
47
+ expect(empty_relayer.primary_logger).to eq(nil)
48
+ end
49
+ end
50
+
51
+ describe '#secondary_loggers' do
52
+ it 'includes all but the first value in #loggers' do
53
+ expect(triple_relayer.secondary_loggers).to eq([secondary, tertiary])
54
+ expect(triple_relayer.secondary_loggers).to eq(triple_relayer.loggers[1..-1])
55
+ end
56
+
57
+ it 'is an empty array if there are no loggers' do
58
+ expect(empty_relayer.secondary_loggers).to eq([])
59
+ end
60
+
61
+ it 'is an empty array if there is a single logger' do
62
+ expect(single_relayer.secondary_loggers).to eq([])
63
+ end
64
+ end
65
+
66
+ describe '#add_logger' do
67
+ it 'adds loggers to the end of the #loggers array' do
68
+ empty_relayer.add_logger(primary)
69
+ expect(empty_relayer.loggers.last).to be(primary)
70
+ end
71
+
72
+ it 'sets #primary_logger when the first logger is added' do
73
+ empty_relayer.add_logger(primary)
74
+ expect(empty_relayer.primary_logger).to be(primary)
75
+ end
76
+
77
+ it 'adds to #secondary_loggers when additional loggers are added' do
78
+ single_relayer.add_logger(secondary)
79
+ expect(single_relayer.primary_logger).not_to be(secondary)
80
+ expect(single_relayer.secondary_loggers).to include(secondary)
81
+ end
82
+ end
83
+
84
+ describe '#remove_logger' do
85
+ it 'removes loggers' do
86
+ single_relayer.remove_logger(primary)
87
+ expect(single_relayer.loggers).not_to include(primary)
88
+ end
89
+
90
+ it 'can remove the primary logger' do
91
+ single_relayer.remove_logger(single_relayer.primary_logger)
92
+ expect(single_relayer.primary_logger).to eq(nil)
93
+ end
94
+
95
+ it 'promotes another logger to primary when removing the primary' do
96
+ double_relayer.remove_logger(primary)
97
+ expect(double_relayer.primary_logger).to eq(secondary)
98
+ end
99
+
100
+ it 'removes secondary loggers' do
101
+ triple_relayer.remove_logger(secondary)
102
+ expect(triple_relayer.secondary_loggers).not_to include(secondary)
103
+ end
104
+ end
105
+
106
+ describe '#add' do
107
+ it 'delegates to all child loggers' do
108
+ expect(primary).to receive(:add).with(Logger::FATAL, 'msg', 'progname').and_return(true)
109
+ expect(secondary).to receive(:add).with(Logger::FATAL, 'msg', 'progname').and_return(true)
110
+ double_relayer.add(Logger::FATAL, 'msg', 'progname')
111
+ end
112
+
113
+ it 'aggregates responses from all child loggers' do
114
+ expect(primary).to receive(:add).and_return(true)
115
+ expect(secondary).to receive(:add).and_return(false)
116
+ expect(double_relayer.add(Logger::FATAL)).to eq(false)
117
+ end
118
+
119
+ it 'only calls the block given once' do
120
+ allow(primary).to receive(:add).and_yield
121
+ allow(secondary).to receive(:add).and_yield
122
+
123
+ block = proc {}
124
+ expect(block).to receive(:call).once
125
+ double_relayer.add(Logger::FATAL, &block)
126
+ end
127
+
128
+ it 'passes a block to child loggers if a block was given' do
129
+ expect(primary).to receive(:add) { |*, &b| expect(b).not_to be(nil) }
130
+ single_relayer.add(Logger::FATAL) {}
131
+ end
132
+
133
+ it 'does not pass a block to child loggers if a block was not given' do
134
+ expect(primary).to receive(:add) { |*, &b| expect(b).to be(nil) }
135
+ single_relayer.add(Logger::FATAL)
136
+ end
137
+ end
138
+
139
+ describe '#<<' do
140
+ it 'delegates to all child loggers' do
141
+ expect(primary).to receive(:<<).with('msg')
142
+ expect(secondary).to receive(:<<).with('msg')
143
+
144
+ double_relayer << 'msg'
145
+ end
146
+
147
+ it 'returns the lowest value returned by child loggers' do
148
+ expect(primary).to receive(:<<).with('msg').and_return(3)
149
+ expect(secondary).to receive(:<<).with('msg').and_return(1)
150
+ expect(tertiary).to receive(:<<).with('msg').and_return(2)
151
+
152
+ expect(triple_relayer << 'msg').to eq(1)
153
+ end
154
+ end
155
+
156
+ describe '#debug' do
157
+ it 'calls add with DEBUG severity' do
158
+ block = proc {}
159
+ expect(empty_relayer).to receive(:add).with(Logger::DEBUG, nil, 'progname') { |*, &b| expect(b).to eq(block) }
160
+ empty_relayer.debug('progname', &block)
161
+ end
162
+ end
163
+
164
+ describe '#info' do
165
+ it 'calls add with INFO severity' do
166
+ block = proc {}
167
+ expect(empty_relayer).to receive(:add).with(Logger::INFO, nil, 'progname') { |*, &b| expect(b).to eq(block) }
168
+ empty_relayer.info('progname', &block)
169
+ end
170
+ end
171
+
172
+ describe '#warn' do
173
+ it 'calls add with WARN severity' do
174
+ block = proc {}
175
+ expect(empty_relayer).to receive(:add).with(Logger::WARN, nil, 'progname') { |*, &b| expect(b).to eq(block) }
176
+ empty_relayer.warn('progname', &block)
177
+ end
178
+ end
179
+
180
+ describe '#error' do
181
+ it 'calls add with ERROR severity' do
182
+ block = proc {}
183
+ expect(empty_relayer).to receive(:add).with(Logger::ERROR, nil, 'progname') { |*, &b| expect(b).to eq(block) }
184
+ empty_relayer.error('progname', &block)
185
+ end
186
+ end
187
+
188
+ describe '#fatal' do
189
+ it 'calls add with FATAL severity' do
190
+ block = proc {}
191
+ expect(empty_relayer).to receive(:add).with(Logger::FATAL, nil, 'progname') { |*, &b| expect(b).to eq(block) }
192
+ empty_relayer.fatal('progname', &block)
193
+ end
194
+ end
195
+
196
+ describe '#unknown' do
197
+ it 'calls add with UNKNOWN severity' do
198
+ block = proc {}
199
+ expect(empty_relayer).to receive(:add).with(Logger::UNKNOWN, nil, 'progname') { |*, &b| expect(b).to eq(block) }
200
+ empty_relayer.unknown('progname', &block)
201
+ end
202
+ end
203
+
204
+ describe '#level' do
205
+ it 'returns the lowest level returned by child loggers' do
206
+ expect(primary).to receive(:level).and_return(Logger::FATAL).at_least(:once)
207
+ expect(secondary).to receive(:level).and_return(Logger::INFO).at_least(:once)
208
+ expect(tertiary).to receive(:level).and_return(Logger::UNKNOWN).at_least(:once)
209
+
210
+ expect(triple_relayer.level).to eq(Logger::INFO)
211
+ end
212
+ end
213
+
214
+ context 'delegation' do
215
+ let(:value) { double('value') }
216
+
217
+ before do
218
+ expect(double_relayer.primary_logger).to be(primary)
219
+ end
220
+
221
+ %i(
222
+ progname
223
+ sev_threshold
224
+ formatter
225
+ datetime_format
226
+ close
227
+ debug?
228
+ info?
229
+ warn?
230
+ error?
231
+ fatal?
232
+ ).each do |m|
233
+ describe "##{m}" do
234
+ it 'delegates to the primary logger' do
235
+ expect(primary).to receive(m).and_return(value)
236
+ expect(secondary).not_to receive(m)
237
+ expect(double_relayer.public_send(m)).to eq(value)
238
+ end
239
+ end
240
+ end
241
+
242
+ %i(
243
+ progname=
244
+ level=
245
+ sev_threshold=
246
+ formatter=
247
+ datetime_format=
248
+ ).each do |m|
249
+ describe "##{m}" do
250
+ it 'delegates to the primary logger' do
251
+ expect(primary).to receive(m).with(value).and_return(value)
252
+ expect(secondary).not_to receive(m)
253
+ expect(double_relayer.public_send(m, value)).to eq(value)
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ context 'tagging' do
260
+ describe '#current_tags' do
261
+ it 'aggregates the #current_tags of child loggers (skipping non-tagged loggers)' do
262
+ expect(primary).to receive(:current_tags).and_return(%w(a b))
263
+ expect(tertiary).to receive(:current_tags).and_return(%w(b c))
264
+ expect(Set.new(triple_relayer.current_tags)).to be_superset(Set.new(%w(a b c)))
265
+ end
266
+ end
267
+
268
+ describe '#push_tags' do
269
+ it 'delegates to all child loggers' do
270
+ expect(primary).to receive(:push_tags).with(*%w(a b c))
271
+ expect(tertiary).to receive(:push_tags).with(*%w(a b c))
272
+ triple_relayer.push_tags(*%w(a b c))
273
+ end
274
+
275
+ it 'flattens the tags list' do
276
+ expect(primary).to receive(:push_tags).with(*%w(a b c d))
277
+ single_relayer.push_tags(%w(a b), %w(c d))
278
+ end
279
+
280
+ it 'omits blank tags' do
281
+ expect(primary).to receive(:push_tags).with(*%w(a b c d))
282
+ single_relayer.push_tags(['a', 'b', '', nil, 'c', 'd'])
283
+ end
284
+
285
+ it 'returns the flattened, non-blank list of tags' do
286
+ allow(primary).to receive(:push_tags)
287
+ expect(single_relayer.push_tags(%w(a b), nil, '', %w(c d))).to eq(%w(a b c d))
288
+ end
289
+ end
290
+
291
+ describe '#pop_tags' do
292
+ it 'delegates to all child loggers' do
293
+ expect(primary).to receive(:pop_tags).with(4)
294
+ expect(tertiary).to receive(:pop_tags).with(4)
295
+ triple_relayer.pop_tags(4)
296
+ end
297
+ end
298
+
299
+ describe '#clear_tags!' do
300
+ it 'delegates to all child loggers' do
301
+ expect(primary).to receive(:clear_tags!)
302
+ expect(secondary).to receive(:clear_tags!)
303
+ triple_relayer.clear_tags!
304
+ end
305
+ end
306
+
307
+ describe '#flush' do
308
+ it 'delegates to all child loggers' do
309
+ expect(primary).to receive(:flush)
310
+ expect(secondary).to receive(:flush)
311
+ triple_relayer.flush
312
+ end
313
+ end
314
+
315
+ describe '#tagged' do
316
+ it 'calls #push_tags with the given tags' do
317
+ expect(single_relayer).to receive(:push_tags).with(*%w(a b c)).and_return(%w(a b c))
318
+ expect { |b| single_relayer.tagged(*%w(a b c), &b) }.to yield_control
319
+ end
320
+
321
+ it 'calls #pop_tags with the number of tags returned from #push_tags' do
322
+ expect(single_relayer).to receive(:push_tags).with('a', 'b', '', 'd').and_return(%w(a b c))
323
+ expect(single_relayer).to receive(:pop_tags).with(3)
324
+ single_relayer.tagged('a', 'b', '', 'd') {}
325
+ end
326
+
327
+ it 'calls #pop_tags when the block raises an exception' do
328
+ expect(single_relayer).to receive(:pop_tags)
329
+ expect { single_relayer.tagged('a') { fail StandardError, ':(' } }.to raise_error(StandardError)
330
+ end
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActionController::Base do
4
+ let(:request_id) { '5ab8b021-71bf-40f9-97cc-136f7b626a66' }
5
+
6
+ around :each do |example|
7
+ begin
8
+ original_logger = PaulBunyan.logger.primary_logger
9
+ null_logger = Logger.new('/dev/null')
10
+ PaulBunyan.add_logger(null_logger)
11
+ PaulBunyan.remove_logger(original_logger)
12
+
13
+ example.run
14
+ ensure
15
+ PaulBunyan.add_logger(original_logger)
16
+ PaulBunyan.remove_logger(null_logger)
17
+ end
18
+ end
19
+
20
+ before :each do
21
+ subject.request = ActionDispatch::TestRequest.new({
22
+ 'action_dispatch.request_id' => request_id,
23
+ 'REMOTE_ADDR' => '127.0.0.1',
24
+ })
25
+ subject.response = ActionDispatch::TestResponse.new
26
+
27
+ def subject.index(*args)
28
+ head :ok
29
+ end
30
+ end
31
+
32
+
33
+ describe '.process_action(*args)' do
34
+ context 'active support notification events' do
35
+ before do
36
+ allow(ActiveSupport::Notifications).to receive(:instrument)
37
+ end
38
+
39
+ it 'must emit a start_processing event in the action_controller namespace' do
40
+ subject.send(:process_action, :index)
41
+ expect(ActiveSupport::Notifications).to have_received(:instrument).
42
+ with('start_processing.action_controller', anything).once
43
+ end
44
+
45
+ it 'must emit a process_action event in the action_controller namespace' do
46
+ subject.send(:process_action, :index)
47
+ expect(ActiveSupport::Notifications).to have_received(:instrument).
48
+ with('process_action.action_controller', anything).once
49
+ end
50
+ end
51
+
52
+ it 'must include the original rails payload keys in the event payload' do
53
+ calling_index do |*args|
54
+ payload = args.last
55
+ expect(payload.keys).to include(
56
+ *[:controller, :action, :params, :format, :method, :path]
57
+ )
58
+ end
59
+ end
60
+
61
+ it 'must add the request id to the notification payload hash' do
62
+ calling_index do |*args|
63
+ payload = args.last
64
+ expect(payload).to include request_id: request_id
65
+ end
66
+ end
67
+
68
+ it 'must add the request ip address to the notification payload' do
69
+ calling_index do |*args|
70
+ payload = args.last
71
+ expect(payload).to include ip: '127.0.0.1'
72
+ end
73
+ end
74
+ end
75
+
76
+ def calling_index(&block)
77
+ with_subscription_to('process_action.action_controller', block) do
78
+ subject.send(:process_action, :index)
79
+ end
80
+ end
81
+ end