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,304 @@
1
+ require 'spec_helper'
2
+
3
+ module PaulBunyan
4
+ RSpec.describe LogSubscriber do
5
+ let(:aggregator) { subject.send(:aggregator) }
6
+
7
+ def preserving_event_lists
8
+ original_events = LogSubscriber.action_controller_events.dup
9
+ LogSubscriber.action_controller_events.clear
10
+ yield
11
+ ensure
12
+ LogSubscriber.instance_variable_set(:@action_controller_events, original_events)
13
+ end
14
+
15
+ before do
16
+ RequestStore.clear!
17
+ end
18
+
19
+ describe '.action_controller_event(event_name)' do
20
+ around(:each) do |example|
21
+ preserving_event_lists do
22
+ example.run
23
+ end
24
+ end
25
+
26
+ it 'must store the event name' do
27
+ LogSubscriber.action_controller_event :foo
28
+ expect(LogSubscriber.action_controller_events).to include :foo
29
+ end
30
+
31
+ it 'must only add one copy of each event name' do
32
+ LogSubscriber.action_controller_event :foo
33
+ LogSubscriber.action_controller_event :foo
34
+ LogSubscriber.action_controller_event :bar
35
+ LogSubscriber.action_controller_event :bar
36
+ expect(LogSubscriber.action_controller_events).to contain_exactly :foo, :bar
37
+ end
38
+ end
39
+
40
+ describe '.event_patterns' do
41
+ it 'must return a set including all action_controller events in that namespace', aggregate_failures: true do
42
+ LogSubscriber.action_controller_events.each do |event|
43
+ expect(LogSubscriber.event_patterns).to include "#{event}.action_controller"
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '.subscribe_to_events' do
49
+ around(:each) do |example|
50
+ preserving_event_lists do
51
+ example.run
52
+ end
53
+ end
54
+
55
+ it 'must add a subscripton for all action_controller events' do
56
+ LogSubscriber.action_controller_event :nx_event
57
+ LogSubscriber.subscribe_to_events
58
+ expect(subscriber_classes_for('nx_event.action_controller')).
59
+ to include LogSubscriber
60
+ end
61
+ end
62
+
63
+ describe '#process_action(event)' do
64
+ let(:event) {
65
+ ActiveSupport::Notifications::Event.new(
66
+ 'process_action.action_controller',
67
+ Time.now,
68
+ Time.at(Time.now.to_f - 0.240),
69
+ SecureRandom.hex(5),
70
+ {
71
+ :controller=>"FoosController",
72
+ :action => "show",
73
+ :params => {"controller"=>"foos", "action"=>"show", "id" => "42", "foo" => "bar"},
74
+ :format => :html,
75
+ :method => "GET",
76
+ :path => "/foos/42",
77
+ :request_id => "dfa6f7b5-8400-4572-a2f7-80504ed9d09a",
78
+ :ip => '127.0.0.1',
79
+ :status => 200,
80
+ :view_runtime => 220.038,
81
+ :db_runtime => 10.177,
82
+ }.with_indifferent_access
83
+ )
84
+ }
85
+
86
+ before do
87
+ allow(::PaulBunyan.logger).to receive(:info)
88
+ subject.process_action(event)
89
+ end
90
+
91
+ it 'it must log the contents of the aggregator to the specified logger at the info level' do
92
+ expect(::PaulBunyan.logger).to have_received(:info)
93
+ end
94
+
95
+ it 'must set the method on the aggregator' do
96
+ expect(aggregator.method).to eq 'GET'
97
+ end
98
+
99
+ it 'must set the controller on the aggregator' do
100
+ expect(aggregator.controller).to eq 'FoosController'
101
+ end
102
+
103
+ it 'must set the action on the aggregator' do
104
+ expect(aggregator.action).to eq 'show'
105
+ end
106
+
107
+ it 'must set the format on the aggregator' do
108
+ expect(aggregator.format).to eq :html
109
+ end
110
+
111
+ it 'must set the path on the aggregator' do
112
+ expect(aggregator.path).to eq '/foos/42'
113
+ end
114
+
115
+ it 'must set the request_id on the aggregator' do
116
+ expect(aggregator.request_id).to eq "dfa6f7b5-8400-4572-a2f7-80504ed9d09a"
117
+ end
118
+
119
+ it 'must set the ip address on the aggregator' do
120
+ expect(aggregator.ip).to eq '127.0.0.1'
121
+ end
122
+
123
+ it 'must set the status on the aggregator' do
124
+ expect(aggregator.status).to eq 200
125
+ end
126
+
127
+ it 'must set the view_runtime value on the aggregator' do
128
+ expect(aggregator.view_runtime).to eq 220.038
129
+ end
130
+
131
+ it 'must set the db_runtime value on the aggregator' do
132
+ expect(aggregator.db_runtime).to eq 10.177
133
+ end
134
+
135
+ context 'extracting the params' do
136
+ it 'must not include the ActionController internal params from the hash' do
137
+ expect(aggregator.params).to_not include ActionController::LogSubscriber::INTERNAL_PARAMS
138
+ end
139
+
140
+ it 'must include params passed to the controller action' do
141
+ expect(aggregator.params).to include "id" => "42", "foo" => "bar"
142
+ end
143
+ end
144
+ end
145
+
146
+ describe '#halted_callback(event)' do
147
+ let(:event) {
148
+ ActiveSupport::Notifications::Event.new(
149
+ 'halted_callback.action_controller',
150
+ Time.now,
151
+ Time.at(Time.now.to_f - 0.240),
152
+ SecureRandom.hex(5),
153
+ {filter: described_class}.with_indifferent_access
154
+ )
155
+ }
156
+
157
+ before do
158
+ subject.halted_callback(event)
159
+ end
160
+
161
+ it 'must set the halting_filter field on the request aggregator' do
162
+ expect(aggregator.halting_filter).to eq 'PaulBunyan::LogSubscriber'
163
+ end
164
+ end
165
+
166
+ describe '#send_file(event)' do
167
+ let(:event) {
168
+ ActiveSupport::Notifications::Event.new(
169
+ 'send_file.action_controller',
170
+ Time.at(Time.now.to_f - 0.240),
171
+ Time.now,
172
+ SecureRandom.hex(5),
173
+ {path: '/path/to/a/file'}.with_indifferent_access
174
+ )
175
+ }
176
+ let(:file_data) { aggregator.sent_file }
177
+
178
+ before do
179
+ subject.send_file(event)
180
+ end
181
+
182
+ it "must set the path field on the request aggregator's sent_file object" do
183
+ expect(file_data.path).to eq '/path/to/a/file'
184
+ end
185
+
186
+ it "must set the transfer_time on the request aggregator's sent_file object" do
187
+ expect(file_data.transfer_time).to be_within(1).of(240)
188
+ end
189
+ end
190
+
191
+ describe '#redirect_to(event)' do
192
+ let(:event) {
193
+ ActiveSupport::Notifications::Event.new(
194
+ 'redirect_to.action_controller',
195
+ Time.now,
196
+ Time.at(Time.now.to_f - 0.240),
197
+ SecureRandom.hex(5),
198
+ {location: 'https://example.com/another/resource'}.with_indifferent_access
199
+ )
200
+ }
201
+
202
+ before do
203
+ subject.redirect_to(event)
204
+ end
205
+
206
+ it 'must set the redirect_location field on the request aggregator' do
207
+ expect(aggregator.redirect_location).to eq 'https://example.com/another/resource'
208
+ end
209
+ end
210
+
211
+ describe '#send_data(event)' do
212
+ let(:event) {
213
+ ActiveSupport::Notifications::Event.new(
214
+ 'send_data.action_controller',
215
+ Time.at(Time.now.to_f - 0.240),
216
+ Time.now,
217
+ SecureRandom.hex(5),
218
+ {filename: '/path/to/a/file'}.with_indifferent_access
219
+ )
220
+ }
221
+ let(:file_data) { aggregator.sent_data }
222
+
223
+ before do
224
+ subject.send_data(event)
225
+ end
226
+
227
+ it "must set the path field on the request aggregator's sent_file object" do
228
+ expect(file_data.path).to eq '/path/to/a/file'
229
+ end
230
+
231
+ it "must set the transfer_time on the request aggregator's sent_file object" do
232
+ expect(file_data.transfer_time).to be_within(1).of(240)
233
+ end
234
+ end
235
+
236
+ describe '#render_template(event)' do
237
+ let(:event) {
238
+ ActiveSupport::Notifications::Event.new(
239
+ 'render_template.action_view',
240
+ Time.at(Time.now.to_f - 0.240),
241
+ Time.now,
242
+ SecureRandom.hex(5),
243
+ {
244
+ identifier: Rails.root.join('app', 'views', 'controller', 'index.html.erb'),
245
+ layout: Rails.root.join('app', 'views', 'application', 'application.html.erb'),
246
+ }.with_indifferent_access
247
+ )
248
+ }
249
+ let(:view_data) { aggregator.view }
250
+
251
+ before do
252
+ subject.render_template(event)
253
+ end
254
+
255
+ it 'must set the view attribute on the aggregator' do
256
+ expect(aggregator.view).to_not be_nil
257
+ end
258
+
259
+ it 'must capture the rendered template path' do
260
+ expect(view_data.path.to_s).to end_with('controller/index.html.erb')
261
+ end
262
+
263
+ it 'must capture the render run time' do
264
+ expect(view_data.runtime).to be_within(1).of(240)
265
+ end
266
+
267
+ it 'must capture the layout used' do
268
+ expect(view_data.layout.to_s).to end_with('application/application.html.erb')
269
+ end
270
+
271
+ it 'must remove the Rails root path from the rendered template path' do
272
+ expect(view_data.path.to_s).to_not start_with Rails.root.to_s
273
+ end
274
+
275
+ it 'must remove the default rails view path from the rendered template path' do
276
+ expect(view_data.path.to_s).to_not match /\A\/?app\/views\//
277
+ end
278
+ end
279
+
280
+ describe '#render_partial(event)' do
281
+ let(:event) {
282
+ ActiveSupport::Notifications::Event.new(
283
+ 'render_partial.action_view',
284
+ Time.at(Time.now.to_f - 0.240),
285
+ Time.now,
286
+ SecureRandom.hex(5),
287
+ {
288
+ identifier: Rails.root.join('app', 'views', 'controller', '_form.html.erb'),
289
+ }.with_indifferent_access
290
+ )
291
+ }
292
+ let(:view_data) { aggregator.view }
293
+
294
+ before do
295
+ subject.render_partial(event)
296
+ end
297
+
298
+ it 'must add an object to the partials array on every call' do
299
+ subject.render_partial(event)
300
+ expect(aggregator.partials.size).to eq 2
301
+ end
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module PaulBunyan
4
+ RSpec.describe Railtie do
5
+ describe '#unsubscribe_default_log_subscribers' do
6
+ before do
7
+ @action_controller_subscriber = ActiveSupport::LogSubscriber.subscribers.find{|s|
8
+ s.class == ActionController::LogSubscriber
9
+ }
10
+
11
+ Railtie.instance.unsubscribe_default_log_subscribers
12
+ end
13
+
14
+ after do
15
+ # replace any subscriptions we may have blown away so the next test
16
+ # can be assured of a clean slate
17
+ ActionController::LogSubscriber.attach_to(
18
+ :action_controller,
19
+ @action_controller_subscriber
20
+ )
21
+ end
22
+
23
+ it 'must remove the ActionController::LogSubscriber subscription to process_action' do
24
+ expect(subscriber_classes_for('process_action.action_controller')).
25
+ to_not include ActionController::LogSubscriber
26
+ end
27
+
28
+ it 'must leave the ActionController::LogSubscriber subscription to deep_munge.action_controller in place' do
29
+ # I don't expect that we'll ever care to unsubcribe the logger
30
+ # non-event so we'll use it as a check to ensure we don't
31
+ # clobber all of the listeners, only the ones we care about
32
+ expect(subscriber_classes_for('logger.action_controller')).
33
+ to include ActionController::LogSubscriber
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ initial_default_formatter_type = PaulBunyan.default_formatter_type
4
+
5
+ describe PaulBunyan do
6
+ shared_examples 'respecting the ::default_formatter_type' do
7
+ let(:logger) { double('logger') }
8
+
9
+ before do
10
+ allow(Logger).to receive(:new).and_return(logger)
11
+ end
12
+
13
+ it 'sets the default formatter to json, if set to :json' do
14
+ PaulBunyan.default_formatter_type = :json
15
+ expect(logger).to receive(:formatter=).with(instance_of(PaulBunyan::JSONFormatter))
16
+ default_formatter_type_call
17
+ end
18
+
19
+ it 'sets the default formatter to text, if set to :text' do
20
+ PaulBunyan.default_formatter_type = :text
21
+ expect(logger).to receive(:formatter=).with(instance_of(PaulBunyan::TextFormatter))
22
+ default_formatter_type_call
23
+ end
24
+
25
+ it 'does not modify the default formatter, if set to nil' do
26
+ PaulBunyan.default_formatter_type = nil
27
+ expect(logger).not_to receive(:formatter=)
28
+ default_formatter_type_call
29
+ end
30
+ end
31
+
32
+ before do
33
+ # reset the PaulBunyan module before each test
34
+ PaulBunyan.remove_instance_variable(:@logger) if PaulBunyan.instance_variable_defined?(:@logger)
35
+ PaulBunyan.default_formatter_type = initial_default_formatter_type
36
+ end
37
+
38
+ describe 'include' do
39
+ subject(:klass) { Class.new }
40
+ subject(:logging_klass) { Class.new { include PaulBunyan } }
41
+
42
+ it 'adds #logger to the host class' do
43
+ expect(klass.new).not_to respond_to(:logger)
44
+ expect(logging_klass.new).to respond_to(:logger)
45
+ end
46
+ end
47
+
48
+ describe '::logger' do
49
+ it 'instantiates a log relayer' do
50
+ expect(PaulBunyan.logger).to be_a(PaulBunyan::LogRelayer)
51
+ end
52
+
53
+ it 'maintains a single instance' do
54
+ logger = PaulBunyan.logger
55
+ expect(PaulBunyan.logger).to be(logger)
56
+ end
57
+
58
+ it 'adds a default STDOUT logger' do
59
+ expect(STDOUT).to receive(:write)
60
+ PaulBunyan.logger << 'msg'
61
+ end
62
+
63
+ def default_formatter_type_call
64
+ PaulBunyan.logger
65
+ end
66
+
67
+ it_behaves_like 'respecting the ::default_formatter_type'
68
+ end
69
+
70
+ describe '::create_logger' do
71
+ let(:device) { double('device') }
72
+ let(:logger) { double('logger') }
73
+
74
+ before do
75
+ allow(logger).to receive(:formatter=)
76
+ end
77
+
78
+ it 'creates a new logger and adds it to the log relayer' do
79
+ expect(Logger).to receive(:new).with(device, 12, 42).and_return(logger)
80
+ PaulBunyan.create_logger(device, 12, 42)
81
+ end
82
+
83
+ it 'returns the newly created logger' do
84
+ expect(Logger).to receive(:new).and_return(logger)
85
+ expect(PaulBunyan.create_logger(device)).to be(logger)
86
+ end
87
+
88
+ it 'allows the default formatter type to be overridden' do
89
+ PaulBunyan.default_formatter_type = :json
90
+ expect(Logger).to receive(:new).and_return(logger)
91
+ expect(logger).to receive(:formatter=).with(instance_of(PaulBunyan::TextFormatter))
92
+ PaulBunyan.create_logger(device, formatter_type: :text)
93
+ end
94
+
95
+ def default_formatter_type_call
96
+ PaulBunyan.create_logger(device)
97
+ end
98
+
99
+ it_behaves_like 'respecting the ::default_formatter_type'
100
+ end
101
+
102
+ describe '::add_logger' do
103
+ it 'adds the logger to the log relayer' do
104
+ logger = double('logger')
105
+ expect(PaulBunyan.logger).not_to be(nil)
106
+ PaulBunyan.add_logger(logger)
107
+ expect(PaulBunyan.logger.secondary_loggers).to include(logger)
108
+ end
109
+
110
+ it 'creates an empty log relayer, if not present' do
111
+ logger = double('logger')
112
+ PaulBunyan.add_logger(logger)
113
+ expect(PaulBunyan.logger.loggers).to eq([logger])
114
+ end
115
+
116
+ it 'does not recreate the log relayer, if already present' do
117
+ logger1 = double('logger1')
118
+ logger2 = double('logger2')
119
+ PaulBunyan.add_logger(logger1)
120
+ PaulBunyan.add_logger(logger2)
121
+ expect(PaulBunyan.logger.loggers).to include(logger1)
122
+ expect(PaulBunyan.logger.loggers).to include(logger2)
123
+ end
124
+
125
+ it 'returns the logger added' do
126
+ logger = double('logger')
127
+ expect(PaulBunyan.add_logger(logger)).to be(logger)
128
+ end
129
+ end
130
+
131
+ describe '::remove_logger' do
132
+ it 'removes the logger from the log relayer' do
133
+ expect(PaulBunyan.logger).to receive(:remove_logger).with(PaulBunyan.logger.primary_logger)
134
+ PaulBunyan.remove_logger(PaulBunyan.logger.primary_logger)
135
+ end
136
+ end
137
+ end