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,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