paul_bunyan 1.0.0

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