rails_semantic_logger 4.1.3 → 4.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +17 -10
- data/Rakefile +9 -9
- data/lib/rails_semantic_logger/action_controller/log_subscriber.rb +125 -0
- data/lib/rails_semantic_logger/action_mailer/log_subscriber.rb +135 -0
- data/lib/rails_semantic_logger/action_view/log_subscriber.rb +111 -0
- data/lib/rails_semantic_logger/active_job/log_subscriber.rb +126 -0
- data/lib/rails_semantic_logger/active_record/log_subscriber.rb +218 -0
- data/lib/rails_semantic_logger/delayed_job/plugin.rb +11 -0
- data/lib/rails_semantic_logger/engine.rb +189 -194
- data/lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb +1 -1
- data/lib/rails_semantic_logger/extensions/action_controller/live.rb +8 -4
- data/lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb +11 -7
- data/lib/rails_semantic_logger/extensions/action_view/streaming_template_renderer.rb +10 -6
- data/lib/rails_semantic_logger/extensions/active_job/logging.rb +10 -6
- data/lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb +12 -9
- data/lib/rails_semantic_logger/extensions/active_support/logger.rb +24 -0
- data/lib/rails_semantic_logger/extensions/active_support/tagged_logging.rb +8 -0
- data/lib/rails_semantic_logger/extensions/mongoid/config.rb +11 -0
- data/lib/rails_semantic_logger/extensions/rack/server.rb +12 -0
- data/lib/rails_semantic_logger/extensions/rails/server.rb +9 -5
- data/lib/rails_semantic_logger/options.rb +122 -0
- data/lib/rails_semantic_logger/rack/logger.rb +100 -0
- data/lib/rails_semantic_logger/version.rb +2 -2
- data/lib/rails_semantic_logger.rb +58 -3
- metadata +46 -24
- data/lib/rails_semantic_logger/extensions/action_controller/log_subscriber.rb +0 -107
- data/lib/rails_semantic_logger/extensions/action_controller/log_subscriber_processing.rb +0 -28
- data/lib/rails_semantic_logger/extensions/action_view/log_subscriber.rb +0 -12
- data/lib/rails_semantic_logger/extensions/active_record/log_subscriber.rb +0 -44
- data/lib/rails_semantic_logger/extensions/rails/rack/logger.rb +0 -63
- data/lib/rails_semantic_logger/extensions/rails/rack/logger_info_as_debug.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 73a37ac27f2cf94d083cc75aa557a778f32e2de852b3be6447b072dc1da89047
|
4
|
+
data.tar.gz: 681f2145e71def6b336792fe3d581dfd89da6fd3ca471befcbd8ffa4f11abb50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d299a14cb4c3eaf282e4bf502d8e60c2a36095b138c17969cf0a8f79b0431a731adbe8963ae2017bf429f12b7f42fe65d5c199a2c5f88e984a71d4605766bea
|
7
|
+
data.tar.gz: d837dc46c0dcd38b19dcf2a62c926c91ef1775542d55c8919d3a76db2e62764ab8675890238da78ab19b157f3b920e9d92257d5b2e347bb66d0c5a2bde60441f
|
data/README.md
CHANGED
@@ -1,26 +1,33 @@
|
|
1
|
-
#
|
2
|
-
![](https://img.shields.io/gem/v/rails_semantic_logger.svg) ![](https://img.shields.io/gem/dt/
|
1
|
+
# Rails Semantic Logger
|
2
|
+
[![Gem Version](https://img.shields.io/gem/v/rails_semantic_logger.svg)](https://rubygems.org/gems/rails_semantic_logger) [![Build Status](https://github.com/reidmorrison/rails_semantic_logger/workflows/build/badge.svg)](https://github.com/reidmorrison/rails_semantic_logger/actions?query=workflow%3Abuild) [![Downloads](https://img.shields.io/gem/dt/rails_semantic_logger.svg)](https://rubygems.org/gems/rails_semantic_logger) [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg)
|
3
3
|
|
4
|
-
|
4
|
+
Rails Semantic Logger replaces the Rails default logger with [Semantic Logger](https://logger.rocketjob.io/)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
* http://github.com/rocketjob/rails_semantic_logger
|
6
|
+
* http://github.com/reidmorrison/rails_semantic_logger
|
9
7
|
|
10
8
|
## Documentation
|
11
9
|
|
12
|
-
For complete documentation see:
|
10
|
+
For complete documentation see: https://logger.rocketjob.io/rails
|
11
|
+
|
12
|
+
## Upgrading to Semantic Logger v4.4
|
13
|
+
|
14
|
+
With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
|
15
|
+
workaround for Ruby 2.5 crashes is no longer needed.
|
16
|
+
I.e. Please remove the following line if being called anywhere:
|
17
|
+
|
18
|
+
~~~ruby
|
19
|
+
SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
|
20
|
+
~~~
|
13
21
|
|
14
22
|
## Supports
|
15
23
|
|
16
|
-
|
17
|
-
- Rails 3.2, 4, 5 (or above)
|
24
|
+
For the complete list of supported Ruby and Rails versions, see the [Testing file](https://github.com/reidmorrison/rails_semantic_logger/blob/master/.github/workflows/ci.yml).
|
18
25
|
|
19
26
|
## Author
|
20
27
|
|
21
28
|
[Reid Morrison](https://github.com/reidmorrison)
|
22
29
|
|
23
|
-
[Contributors](https://github.com/
|
30
|
+
[Contributors](https://github.com/reidmorrison/rails_semantic_logger/graphs/contributors)
|
24
31
|
|
25
32
|
## Versioning
|
26
33
|
|
data/Rakefile
CHANGED
@@ -1,30 +1,30 @@
|
|
1
1
|
# Setup bundler to avoid having to run bundle exec all the time.
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
4
|
|
5
|
-
require
|
6
|
-
require_relative
|
5
|
+
require "rake/testtask"
|
6
|
+
require_relative "lib/rails_semantic_logger/version"
|
7
7
|
|
8
8
|
task :gem do
|
9
|
-
system
|
9
|
+
system "gem build rails_semantic_logger.gemspec"
|
10
10
|
end
|
11
11
|
|
12
|
-
task :
|
12
|
+
task publish: :gem do
|
13
13
|
system "git tag -a v#{RailsSemanticLogger::VERSION} -m 'Tagging #{RailsSemanticLogger::VERSION}'"
|
14
|
-
system
|
14
|
+
system "git push --tags"
|
15
15
|
system "gem push rails_semantic_logger-#{RailsSemanticLogger::VERSION}.gem"
|
16
16
|
system "rm rails_semantic_logger-#{RailsSemanticLogger::VERSION}.gem"
|
17
17
|
end
|
18
18
|
|
19
19
|
Rake::TestTask.new(:test) do |t|
|
20
|
-
t.pattern =
|
20
|
+
t.pattern = "test/**/*_test.rb"
|
21
21
|
t.verbose = true
|
22
22
|
t.warning = false
|
23
23
|
end
|
24
24
|
|
25
25
|
# By default run tests against all appraisals
|
26
26
|
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
|
27
|
-
require
|
27
|
+
require "appraisal"
|
28
28
|
task default: :appraisal
|
29
29
|
else
|
30
30
|
task default: :test
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RailsSemanticLogger
|
2
|
+
module ActionController
|
3
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
4
|
+
INTERNAL_PARAMS = %w[controller action format _method only_path].freeze
|
5
|
+
|
6
|
+
# Log as debug to hide Processing messages in production
|
7
|
+
def start_processing(event)
|
8
|
+
controller_logger(event).debug { "Processing ##{event.payload[:action]}" }
|
9
|
+
end
|
10
|
+
|
11
|
+
def process_action(event)
|
12
|
+
controller_logger(event).info do
|
13
|
+
payload = event.payload.dup
|
14
|
+
|
15
|
+
# Unused, but needed for Devise 401 status code monkey patch to still work.
|
16
|
+
::ActionController::Base.log_process_action(payload)
|
17
|
+
|
18
|
+
params = payload[:params]
|
19
|
+
|
20
|
+
if params.kind_of?(Hash) || params.kind_of?(::ActionController::Parameters)
|
21
|
+
# According to PR https://github.com/reidmorrison/rails_semantic_logger/pull/37/files
|
22
|
+
# params is not always a Hash.
|
23
|
+
payload[:params] = params.to_unsafe_h unless params.is_a?(Hash)
|
24
|
+
payload[:params] = params.except(*INTERNAL_PARAMS)
|
25
|
+
|
26
|
+
if payload[:params].empty?
|
27
|
+
payload.delete(:params)
|
28
|
+
elsif params["file"]
|
29
|
+
# When logging to JSON the entire tempfile is logged, so convert it to a string.
|
30
|
+
payload[:params]["file"] = params["file"].inspect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
format = payload[:format]
|
35
|
+
payload[:format] = format.to_s.upcase if format.is_a?(Symbol)
|
36
|
+
|
37
|
+
payload[:path] = extract_path(payload[:path]) if payload.key?(:path)
|
38
|
+
|
39
|
+
exception = payload.delete(:exception)
|
40
|
+
if payload[:status].nil? && exception.present?
|
41
|
+
exception_class_name = exception.first
|
42
|
+
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Rounds off the runtimes. For example, :view_runtime, :mongo_runtime, etc.
|
46
|
+
payload.keys.each do |key|
|
47
|
+
payload[key] = payload[key].to_f.round(2) if key.to_s =~ /(.*)_runtime/
|
48
|
+
end
|
49
|
+
|
50
|
+
# Rails 6+ includes allocation count
|
51
|
+
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
|
52
|
+
|
53
|
+
payload[:status_message] = ::Rack::Utils::HTTP_STATUS_CODES[payload[:status]] if payload[:status].present?
|
54
|
+
|
55
|
+
# Causes excessive log output with Rails 5 RC1
|
56
|
+
payload.delete(:headers)
|
57
|
+
# Causes recursion in Rails 6.1.rc1
|
58
|
+
payload.delete(:request)
|
59
|
+
payload.delete(:response)
|
60
|
+
|
61
|
+
{
|
62
|
+
message: "Completed ##{payload[:action]}",
|
63
|
+
duration: event.duration,
|
64
|
+
payload: payload
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def halted_callback(event)
|
70
|
+
controller_logger(event).info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
|
71
|
+
end
|
72
|
+
|
73
|
+
def send_file(event)
|
74
|
+
controller_logger(event).info(message: "Sent file", payload: {path: event.payload[:path]}, duration: event.duration)
|
75
|
+
end
|
76
|
+
|
77
|
+
def redirect_to(event)
|
78
|
+
controller_logger(event).info(message: "Redirected to", payload: {location: event.payload[:location]})
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_data(event)
|
82
|
+
controller_logger(event).info(message: "Sent data", payload: {file_name: event.payload[:filename]}, duration: event.duration)
|
83
|
+
end
|
84
|
+
|
85
|
+
def unpermitted_parameters(event)
|
86
|
+
controller_logger(event).debug do
|
87
|
+
unpermitted_keys = event.payload[:keys]
|
88
|
+
"Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(', ')}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
%w[write_fragment read_fragment exist_fragment?
|
93
|
+
expire_fragment expire_page write_page].each do |method|
|
94
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
95
|
+
def #{method}(event)
|
96
|
+
# enable_fragment_cache_logging as of Rails 5
|
97
|
+
return if ::ActionController::Base.respond_to?(:enable_fragment_cache_logging) && !::ActionController::Base.enable_fragment_cache_logging
|
98
|
+
controller_logger(event).info do
|
99
|
+
key_or_path = event.payload[:key] || event.payload[:path]
|
100
|
+
{message: "#{method.to_s.humanize} \#{key_or_path}", duration: event.duration}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
METHOD
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Returns the logger for the supplied event.
|
109
|
+
# Returns ActionController::Base.logger if no controller is present
|
110
|
+
def controller_logger(event)
|
111
|
+
controller = event.payload[:controller]
|
112
|
+
return ::ActionController::Base.logger unless controller
|
113
|
+
|
114
|
+
controller.constantize.logger || ::ActionController::Base.logger
|
115
|
+
rescue NameError
|
116
|
+
::ActionController::Base.logger
|
117
|
+
end
|
118
|
+
|
119
|
+
def extract_path(path)
|
120
|
+
index = path.index("?")
|
121
|
+
index ? path[0, index] : path
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "active_support/log_subscriber"
|
2
|
+
require "action_mailer"
|
3
|
+
|
4
|
+
module RailsSemanticLogger
|
5
|
+
module ActionMailer
|
6
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
7
|
+
def deliver(event)
|
8
|
+
ex = event.payload[:exception_object]
|
9
|
+
message_id = event.payload[:message_id]
|
10
|
+
duration = event.duration.round(1)
|
11
|
+
if ex
|
12
|
+
log_with_formatter event: event, log_duration: true, level: :error do |fmt|
|
13
|
+
{
|
14
|
+
message: "Error delivering mail #{message_id} (#{duration}ms)",
|
15
|
+
exception: ex
|
16
|
+
}
|
17
|
+
end
|
18
|
+
else
|
19
|
+
message = begin
|
20
|
+
if event.payload[:perform_deliveries]
|
21
|
+
"Delivered mail #{message_id} (#{duration}ms)"
|
22
|
+
else
|
23
|
+
"Skipped delivery of mail #{message_id} as `perform_deliveries` is false"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
log_with_formatter event: event, log_duration: true do |fmt|
|
27
|
+
{ message: message }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# An email was generated.
|
33
|
+
def process(event)
|
34
|
+
mailer = event.payload[:mailer]
|
35
|
+
action = event.payload[:action]
|
36
|
+
duration = event.duration.round(1)
|
37
|
+
log_with_formatter event: event do |fmt|
|
38
|
+
{ message: "#{mailer}##{action}: processed outbound mail in #{duration}ms" }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
class EventFormatter
|
45
|
+
def initialize(event:, log_duration: false)
|
46
|
+
@event = event
|
47
|
+
@log_duration = log_duration
|
48
|
+
end
|
49
|
+
|
50
|
+
def mailer
|
51
|
+
event.payload[:mailer]
|
52
|
+
end
|
53
|
+
|
54
|
+
def payload
|
55
|
+
{}.tap do |h|
|
56
|
+
h[:event_name] = event.name
|
57
|
+
h[:mailer] = mailer
|
58
|
+
h[:action] = action
|
59
|
+
h[:message_id] = event.payload[:message_id]
|
60
|
+
h[:perform_deliveries] = event.payload[:perform_deliveries]
|
61
|
+
h[:subject] = event.payload[:subject]
|
62
|
+
h[:to] = event.payload[:to]
|
63
|
+
h[:from] = event.payload[:from]
|
64
|
+
h[:bcc] = event.payload[:bcc]
|
65
|
+
h[:cc] = event.payload[:cc]
|
66
|
+
h[:date] = date
|
67
|
+
h[:duration] = event.duration.round(2) if log_duration?
|
68
|
+
h[:args] = formatted_args
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def date
|
73
|
+
if event.payload[:date].respond_to?(:to_time)
|
74
|
+
event.payload[:date].to_time.utc
|
75
|
+
elsif event.payload[:date].is_a?(String)
|
76
|
+
Time.parse(date).utc
|
77
|
+
else
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :event
|
85
|
+
|
86
|
+
def mailer
|
87
|
+
event.payload[:mailer]
|
88
|
+
end
|
89
|
+
|
90
|
+
def action
|
91
|
+
event.payload[:action]
|
92
|
+
end
|
93
|
+
|
94
|
+
def formatted_args
|
95
|
+
if defined?(mailer.contantize.log_arguments?) && !mailer.contantize.log_arguments?
|
96
|
+
""
|
97
|
+
else
|
98
|
+
JSON.pretty_generate(event.payload[:args].map { |arg| format(arg) }) if event.payload[:args].present?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def format(arg)
|
103
|
+
case arg
|
104
|
+
when Hash
|
105
|
+
arg.transform_values { |value| format(value) }
|
106
|
+
when Array
|
107
|
+
arg.map { |value| format(value) }
|
108
|
+
when GlobalID::Identification
|
109
|
+
begin
|
110
|
+
arg.to_global_id
|
111
|
+
rescue StandardError
|
112
|
+
arg
|
113
|
+
end
|
114
|
+
else
|
115
|
+
arg
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def log_duration?
|
120
|
+
@log_duration
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def log_with_formatter(level: :info, **kw_args)
|
125
|
+
fmt = EventFormatter.new(**kw_args)
|
126
|
+
msg = yield fmt
|
127
|
+
logger.public_send(level, **msg, payload: fmt.payload)
|
128
|
+
end
|
129
|
+
|
130
|
+
def logger
|
131
|
+
::ActionMailer::Base.logger
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "active_support/log_subscriber"
|
2
|
+
|
3
|
+
module RailsSemanticLogger
|
4
|
+
module ActionView
|
5
|
+
# Output Semantic logs from Action View.
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
VIEWS_PATTERN = %r{^app/views/}.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_reader :logger
|
11
|
+
attr_accessor :rendered_log_level
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@rails_root = nil
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_template(event)
|
20
|
+
return unless should_log?
|
21
|
+
|
22
|
+
payload = {
|
23
|
+
template: from_rails_root(event.payload[:identifier])
|
24
|
+
}
|
25
|
+
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
|
26
|
+
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
|
27
|
+
|
28
|
+
logger.measure(
|
29
|
+
self.class.rendered_log_level,
|
30
|
+
"Rendered",
|
31
|
+
payload: payload,
|
32
|
+
duration: event.duration
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def render_partial(event)
|
37
|
+
return unless should_log?
|
38
|
+
|
39
|
+
payload = {
|
40
|
+
partial: from_rails_root(event.payload[:identifier])
|
41
|
+
}
|
42
|
+
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
|
43
|
+
payload[:cache] = event.payload[:cache_hit] unless event.payload[:cache_hit].nil?
|
44
|
+
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
|
45
|
+
|
46
|
+
logger.measure(
|
47
|
+
self.class.rendered_log_level,
|
48
|
+
"Rendered",
|
49
|
+
payload: payload,
|
50
|
+
duration: event.duration
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def render_collection(event)
|
55
|
+
return unless should_log?
|
56
|
+
|
57
|
+
identifier = event.payload[:identifier] || "templates"
|
58
|
+
|
59
|
+
payload = {
|
60
|
+
template: from_rails_root(identifier),
|
61
|
+
count: event.payload[:count]
|
62
|
+
}
|
63
|
+
payload[:cache_hits] = event.payload[:cache_hits] if event.payload[:cache_hits]
|
64
|
+
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
|
65
|
+
|
66
|
+
logger.measure(
|
67
|
+
self.class.rendered_log_level,
|
68
|
+
"Rendered",
|
69
|
+
payload: payload,
|
70
|
+
duration: event.duration
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def start(name, id, payload)
|
75
|
+
if (name == "render_template.action_view") && should_log?
|
76
|
+
payload = {template: from_rails_root(payload[:identifier])}
|
77
|
+
payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
|
78
|
+
|
79
|
+
logger.send(self.class.rendered_log_level, message: "Rendering", payload: payload)
|
80
|
+
end
|
81
|
+
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
@logger = SemanticLogger["ActionView"]
|
88
|
+
@rendered_log_level = :debug
|
89
|
+
|
90
|
+
EMPTY = "".freeze
|
91
|
+
|
92
|
+
def should_log?
|
93
|
+
logger.send("#{self.class.rendered_log_level}?")
|
94
|
+
end
|
95
|
+
|
96
|
+
def from_rails_root(string)
|
97
|
+
string = string.sub(rails_root, EMPTY)
|
98
|
+
string.sub!(VIEWS_PATTERN, EMPTY)
|
99
|
+
string
|
100
|
+
end
|
101
|
+
|
102
|
+
def rails_root
|
103
|
+
@rails_root ||= "#{Rails.root}/"
|
104
|
+
end
|
105
|
+
|
106
|
+
def logger
|
107
|
+
self.class.logger
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "active_job"
|
2
|
+
|
3
|
+
module RailsSemanticLogger
|
4
|
+
module ActiveJob
|
5
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
6
|
+
def enqueue(event)
|
7
|
+
log_with_formatter event: event do |fmt|
|
8
|
+
{message: "Enqueued #{fmt.job_info}"}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def enqueue_at(event)
|
13
|
+
log_with_formatter event: event do |fmt|
|
14
|
+
{message: "Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform_start(event)
|
19
|
+
log_with_formatter event: event do |fmt|
|
20
|
+
{message: "Performing #{fmt.job_info}"}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform(event)
|
25
|
+
ex = event.payload[:exception_object]
|
26
|
+
if ex
|
27
|
+
log_with_formatter event: event, log_duration: true, level: :error do |fmt|
|
28
|
+
{
|
29
|
+
message: "Error performing #{fmt.job_info} in #{event.duration.round(2)}ms",
|
30
|
+
exception: ex
|
31
|
+
}
|
32
|
+
end
|
33
|
+
else
|
34
|
+
log_with_formatter event: event, log_duration: true do |fmt|
|
35
|
+
{message: "Performed #{fmt.job_info} in #{event.duration.round(2)}ms"}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
class EventFormatter
|
43
|
+
def initialize(event:, log_duration: false)
|
44
|
+
@event = event
|
45
|
+
@log_duration = log_duration
|
46
|
+
end
|
47
|
+
|
48
|
+
def job_info
|
49
|
+
"#{job.class.name} (Job ID: #{job.job_id}) to #{queue_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def payload
|
53
|
+
{}.tap do |h|
|
54
|
+
h[:event_name] = event.name
|
55
|
+
h[:adapter] = adapter_name
|
56
|
+
h[:queue] = job.queue_name
|
57
|
+
h[:job_class] = job.class.name
|
58
|
+
h[:job_id] = job.job_id
|
59
|
+
h[:provider_job_id] = job.try(:provider_job_id) # Not available in Rails 4.2
|
60
|
+
h[:duration] = event.duration.round(2) if log_duration?
|
61
|
+
h[:arguments] = formatted_args
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def queue_name
|
66
|
+
adapter_name + "(#{job.queue_name})"
|
67
|
+
end
|
68
|
+
|
69
|
+
def scheduled_at
|
70
|
+
Time.at(event.payload[:job].scheduled_at).utc
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
attr_reader :event
|
76
|
+
|
77
|
+
def job
|
78
|
+
event.payload[:job]
|
79
|
+
end
|
80
|
+
|
81
|
+
def adapter_name
|
82
|
+
event.payload[:adapter].class.name.demodulize.remove("Adapter")
|
83
|
+
end
|
84
|
+
|
85
|
+
def formatted_args
|
86
|
+
if defined?(job.class.log_arguments?) && !job.class.log_arguments?
|
87
|
+
""
|
88
|
+
else
|
89
|
+
JSON.pretty_generate(job.arguments.map { |arg| format(arg) })
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def format(arg)
|
94
|
+
case arg
|
95
|
+
when Hash
|
96
|
+
arg.transform_values { |value| format(value) }
|
97
|
+
when Array
|
98
|
+
arg.map { |value| format(value) }
|
99
|
+
when GlobalID::Identification
|
100
|
+
begin
|
101
|
+
arg.to_global_id
|
102
|
+
rescue StandardError
|
103
|
+
arg
|
104
|
+
end
|
105
|
+
else
|
106
|
+
arg
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def log_duration?
|
111
|
+
@log_duration
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def log_with_formatter(level: :info, **kw_args)
|
116
|
+
fmt = EventFormatter.new(**kw_args)
|
117
|
+
msg = yield fmt
|
118
|
+
logger.public_send(level, **msg, payload: fmt.payload)
|
119
|
+
end
|
120
|
+
|
121
|
+
def logger
|
122
|
+
::ActiveJob::Base.logger
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|