epilog 0.2.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.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class ActionMailerSubscriber < LogSubscriber
6
+ def deliver(event)
7
+ info do
8
+ hash(
9
+ event,
10
+ message: 'Sent mail',
11
+ recipients: Array(event.payload[:to])
12
+ )
13
+ end
14
+ end
15
+
16
+ def receive(event)
17
+ info do
18
+ hash(
19
+ event,
20
+ message: 'Received mail'
21
+ )
22
+ end
23
+ end
24
+
25
+ def process(event)
26
+ debug do
27
+ hash(
28
+ event,
29
+ message: 'Processed outbound mail',
30
+ mailer: event.payload[:mailer],
31
+ action: event.payload[:action]
32
+ )
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def hash(event, attrs = {})
39
+ if logger.debug? && event.payload[:mail]
40
+ attrs[:body] = event.payload[:mail]
41
+ end
42
+
43
+ attrs[:metrics] = { duration: event.duration.round(2) }
44
+ attrs
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class ActionViewSubscriber < LogSubscriber
6
+ def render_template(event)
7
+ info { hash(event, 'Rendered template') }
8
+ end
9
+
10
+ def render_partial(event)
11
+ info { hash(event, 'Rendered partial') }
12
+ end
13
+
14
+ def render_collection(event)
15
+ info { hash(event, 'Rendered collection') }
16
+ end
17
+
18
+ private
19
+
20
+ def hash(event, message)
21
+ {
22
+ message: message,
23
+ template: fix_path(event.payload[:identifier]),
24
+ layout: fix_path(event.payload[:layout]),
25
+ metrics: {
26
+ duration: event.duration.round(2)
27
+ }
28
+ }
29
+ end
30
+
31
+ def fix_path(template)
32
+ return if template.nil?
33
+
34
+ base = File.join(::Rails.root, 'app', 'views', '')
35
+ pattern = /^#{Regexp.escape(base)}/
36
+ template.gsub(pattern, '')
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class ActiveJobSubscriber < LogSubscriber
6
+ def enqueue(event)
7
+ info { event_hash('Enqueued job', event) }
8
+ end
9
+
10
+ def enqueue_at(event)
11
+ enqueue(event)
12
+ end
13
+
14
+ def perform_start(event)
15
+ info { event_hash('Performing job', event) }
16
+ end
17
+
18
+ def perform(event)
19
+ info do
20
+ event_hash('Performed job', event).merge(
21
+ metrics: {
22
+ job_runtime: event.duration
23
+ }
24
+ )
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def event_hash(message, event)
31
+ {
32
+ message: message,
33
+ job: job_hash(event.payload[:job]),
34
+ adapter: adapter_name(event.payload[:adapter])
35
+ }
36
+ end
37
+
38
+ def job_hash(job)
39
+ {
40
+ class: job.class.name,
41
+ id: job.job_id,
42
+ queue: job.queue_name,
43
+ arguments: job.arguments,
44
+ scheduled_at: job.scheduled_at ? format_time(job.scheduled_at) : nil
45
+ }
46
+ end
47
+
48
+ def format_time(time)
49
+ Time.at(time).utc.strftime(Epilog::Formatter::DEFAULT_TIME_FORMAT)
50
+ end
51
+
52
+ def adapter_name(adapter)
53
+ adapter = adapter.class unless adapter.is_a?(Class)
54
+ adapter.name
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class ActiveRecordSubscriber < LogSubscriber
6
+ IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
7
+
8
+ def sql(event)
9
+ ActiveRecord::LogSubscriber.runtime += event.duration
10
+
11
+ return unless logger.debug?
12
+
13
+ payload = event.payload
14
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
15
+
16
+ debug(
17
+ message: payload[:name],
18
+ sql: payload[:sql],
19
+ binds: binds_info(payload[:binds] || []),
20
+ metrics: metrics(event)
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def metrics(event)
27
+ {
28
+ query_runtime: event.duration.round(2)
29
+ }
30
+ end
31
+
32
+ def binds_info(binds)
33
+ binds.map do |bind|
34
+ if bind.is_a?(Array)
35
+ bind_column_info(*bind)
36
+ else
37
+ bind_attr_info(bind)
38
+ end
39
+ end
40
+ end
41
+
42
+ def bind_column_info(column, value)
43
+ info = { type: column.type, name: column.name }
44
+ if column.binary?
45
+ info[:bytes] = value.bytesize
46
+ else
47
+ info[:value] = value
48
+ end
49
+ info
50
+ end
51
+
52
+ def bind_attr_info(attr)
53
+ info = { type: attr.type.type, name: attr.name }
54
+ if attr.type.binary? && attr.value
55
+ info[:bytes] = attr.value_for_database.to_s.bytesize
56
+ else
57
+ info[:value] = attr.value_for_database
58
+ end
59
+ info
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ class Logger
5
+ include LoggerSilence
6
+
7
+ def silencer
8
+ false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module ActionControllerExt
5
+ def process_action(*)
6
+ epilog_instrument('request_received')
7
+ epilog_instrument('process_request') do |payload|
8
+ begin
9
+ super
10
+ ensure
11
+ payload[:response] = response
12
+ payload[:metrics] = epilog_metrics
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def epilog_instrument(name, &block)
20
+ ActiveSupport::Notifications.instrument(
21
+ "#{name}.action_controller",
22
+ epilog_payload,
23
+ &block
24
+ )
25
+ end
26
+
27
+ def epilog_payload
28
+ {
29
+ request: request,
30
+ response: response,
31
+ controller: self.class.name,
32
+ action: action_name
33
+ }
34
+ end
35
+
36
+ def epilog_metrics
37
+ {
38
+ db_runtime: try(:db_runtime),
39
+ view_runtime: view_runtime
40
+ }
41
+ end
42
+ end
43
+ end
44
+
45
+ ActionController::Base.prepend(Epilog::ActionControllerExt)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ class Logger
5
+ # Rails uses this method to attach additional loggers to the main
6
+ # Rails.logger object when using the rails console or server. This results
7
+ # in extra unformatted log output in those cases. Prevent that by
8
+ # overriding the method with a stub. Examples can be found in
9
+ #
10
+ # - railties/lib/rails/commands/server.rb
11
+ # - active_record/lib/active_record/railtie.rb
12
+ def self.broadcast(*_args)
13
+ Module.new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ class DebugExceptions
5
+ private
6
+
7
+ def log_error(env, wrapper)
8
+ logger = logger(env)
9
+ return unless logger.is_a?(Epilog::Logger)
10
+
11
+ if wrapper.status_code == 500
12
+ logger.fatal(wrapper.exception)
13
+ else
14
+ logger.warn(wrapper.exception)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module EventDelegateExt
5
+ # Rails has no public API to determine the delegate for an event
6
+ # object. Add this method to allow checking if the delegate matches
7
+ # a given object.
8
+ def delegates_to?(delegate)
9
+ @delegate == delegate
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveSupport::Notifications::Fanout::Subscribers::Evented.include(
15
+ Epilog::EventDelegateExt
16
+ )
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Rack
5
+ class Logger
6
+ # The Rails rack logger extension adds unformatted log output with
7
+ # duplicate information. Disable those logs completely.
8
+ def logger
9
+ @logger ||= ::Logger.new(nil)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class LogSubscriber < ActiveSupport::LogSubscriber
6
+ attr_reader :logger
7
+
8
+ def initialize(logger)
9
+ super()
10
+ @logger = logger
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ module Rails
5
+ class Railtie < ::Rails::Railtie
6
+ SUBSCRIBERS = {
7
+ action_controller: ActionControllerSubscriber,
8
+ action_mailer: ActionMailerSubscriber,
9
+ action_view: ActionViewSubscriber,
10
+ active_record: ActiveRecordSubscriber,
11
+ active_job: ActiveJobSubscriber
12
+ }.freeze
13
+
14
+ SUBSCRIBER_BLACKLIST = [
15
+ ActionController::LogSubscriber,
16
+ ActionMailer::LogSubscriber,
17
+ ActionView::LogSubscriber,
18
+ ActiveRecord::LogSubscriber,
19
+ ActiveJob::Logging::LogSubscriber
20
+ ].freeze
21
+
22
+ config.epilog = ActiveSupport::OrderedOptions.new
23
+ config.epilog.subscriptions = %i[
24
+ action_controller
25
+ action_mailer
26
+ action_view
27
+ active_record
28
+ active_job
29
+ ]
30
+
31
+ initializer 'epilog.configure' do |app|
32
+ disable_rails_defaults
33
+
34
+ ::Rails.logger ||= Logger.new($stdout)
35
+
36
+ app.config.epilog.subscriptions.each do |namespace|
37
+ subscriber_class = SUBSCRIBERS[namespace]
38
+ subscriber_class.attach_to(
39
+ namespace,
40
+ subscriber_class.new(::Rails.logger)
41
+ )
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def disable_rails_defaults
48
+ blacklisted_subscribers.each do |subscriber|
49
+ subscriber.patterns.each do |pattern|
50
+ unsubscribe_listeners(subscriber, pattern)
51
+ end
52
+ end
53
+ end
54
+
55
+ def unsubscribe_listeners(subscriber, pattern)
56
+ notifier = ActiveSupport::Notifications.notifier
57
+ notifier.listeners_for(pattern).each do |listener|
58
+ if listener.delegates_to?(subscriber)
59
+ ActiveSupport::Notifications.unsubscribe(listener)
60
+ end
61
+ end
62
+ end
63
+
64
+ def blacklisted_subscribers
65
+ ActiveSupport::LogSubscriber.log_subscribers.select do |subscriber|
66
+ SUBSCRIBER_BLACKLIST.include?(subscriber.class)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epilog
4
+ VERSION = '0.2.0'
5
+ end
metadata ADDED
@@ -0,0 +1,258 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epilog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Howard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '9.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '9.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: combustion
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '4.2'
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '6'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '4.2'
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '6'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '10.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '10.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: redcarpet
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.4'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.4'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rspec
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.4'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.4'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec-rails
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 3.8.1
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 3.8.1
131
+ - !ruby/object:Gem::Dependency
132
+ name: rubocop
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.61'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.61'
145
+ - !ruby/object:Gem::Dependency
146
+ name: simplecov
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.12'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.12'
159
+ - !ruby/object:Gem::Dependency
160
+ name: sqlite3
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '1.3'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '1.3'
173
+ - !ruby/object:Gem::Dependency
174
+ name: yard
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: 0.9.11
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: 0.9.11
187
+ description:
188
+ email:
189
+ - jmhoward0@gmail.com
190
+ executables: []
191
+ extensions: []
192
+ extra_rdoc_files: []
193
+ files:
194
+ - ".gitignore"
195
+ - ".rspec"
196
+ - ".rubocop.yml"
197
+ - ".travis.yml"
198
+ - ".yardopts"
199
+ - Gemfile
200
+ - LICENSE.txt
201
+ - README.md
202
+ - Rakefile
203
+ - bin/check-version
204
+ - bin/console
205
+ - bin/rake
206
+ - bin/rspec
207
+ - bin/rubocop
208
+ - bin/yard
209
+ - bin/yardoc
210
+ - bin/yri
211
+ - epilog.gemspec
212
+ - lib/epilog.rb
213
+ - lib/epilog/filter.rb
214
+ - lib/epilog/filter/blacklist.rb
215
+ - lib/epilog/filter/hash_key.rb
216
+ - lib/epilog/log_formatter.rb
217
+ - lib/epilog/logger.rb
218
+ - lib/epilog/mock_logger.rb
219
+ - lib/epilog/rails.rb
220
+ - lib/epilog/rails/action_controller_subscriber.rb
221
+ - lib/epilog/rails/action_mailer_subscriber.rb
222
+ - lib/epilog/rails/action_view_subscriber.rb
223
+ - lib/epilog/rails/active_job_subscriber.rb
224
+ - lib/epilog/rails/active_record_subscriber.rb
225
+ - lib/epilog/rails/epilog_ext.rb
226
+ - lib/epilog/rails/ext/action_controller.rb
227
+ - lib/epilog/rails/ext/active_support_logger.rb
228
+ - lib/epilog/rails/ext/debug_exceptions.rb
229
+ - lib/epilog/rails/ext/event_delegate.rb
230
+ - lib/epilog/rails/ext/rack_logger.rb
231
+ - lib/epilog/rails/log_subscriber.rb
232
+ - lib/epilog/rails/railtie.rb
233
+ - lib/epilog/version.rb
234
+ homepage: https://github.com/machinima/epilog
235
+ licenses:
236
+ - Apache-2.0
237
+ metadata: {}
238
+ post_install_message:
239
+ rdoc_options: []
240
+ require_paths:
241
+ - lib
242
+ required_ruby_version: !ruby/object:Gem::Requirement
243
+ requirements:
244
+ - - ">="
245
+ - !ruby/object:Gem::Version
246
+ version: '0'
247
+ required_rubygems_version: !ruby/object:Gem::Requirement
248
+ requirements:
249
+ - - ">="
250
+ - !ruby/object:Gem::Version
251
+ version: '0'
252
+ requirements: []
253
+ rubyforge_project:
254
+ rubygems_version: 2.7.8
255
+ signing_key:
256
+ specification_version: 4
257
+ summary: A JSON logger with Rails support
258
+ test_files: []