contextualized_logs 0.0.1.pre.alpha → 0.0.2.pre.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +217 -155
- data/app/controllers/application_controller.rb +1 -0
- data/app/controllers/concerns/datadog_set_trace_details.rb +36 -0
- data/app/controllers/custom_context_controller.rb +17 -0
- data/app/controllers/model_controller.rb +1 -1
- data/app/models/model.rb +1 -1
- data/app/workers/model_worker.rb +2 -2
- data/config/initializers/contextualized_logs.rb +23 -0
- data/config/initializers/datadog.rb +17 -0
- data/config/initializers/sidekiq.rb +13 -13
- data/config/routes.rb +1 -0
- data/db/development.sqlite3 +0 -0
- data/lib/contextualized_logs.rb +25 -0
- data/lib/contextualized_logs/config.rb +27 -0
- data/lib/contextualized_logs/contextualized_controller.rb +54 -40
- data/lib/contextualized_logs/contextualized_logger.rb +74 -67
- data/lib/contextualized_logs/contextualized_model.rb +28 -24
- data/lib/contextualized_logs/contextualized_worker.rb +52 -18
- data/lib/contextualized_logs/current_context.rb +69 -67
- data/lib/contextualized_logs/sidekiq/middleware/client/inject_current_context.rb +3 -3
- data/lib/contextualized_logs/sidekiq/middleware/server/restore_current_context.rb +3 -3
- data/lib/contextualized_logs/version.rb +1 -1
- metadata +7 -2
@@ -0,0 +1,36 @@
|
|
1
|
+
module DatadogSetTraceDetails
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :set_trace_tags, unless: -> { Rails.env.development? }
|
6
|
+
end
|
7
|
+
|
8
|
+
def set_trace_tags
|
9
|
+
begin
|
10
|
+
# set log context as (Datadog) APM trace tags
|
11
|
+
tracer = Datadog.configuration[:rails][:tracer]
|
12
|
+
span = tracer.active_span
|
13
|
+
dotted_hash(ContextualizedLogger.config.current_context).each do |k, v|
|
14
|
+
span.set_tag(k, v)
|
15
|
+
end
|
16
|
+
rescue StandardError => e
|
17
|
+
Rails.logger.info "Error setting trace tags #{e}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# {http: {uuid: 123}} => {http.uuid: 123}
|
24
|
+
# could be added to Hash
|
25
|
+
def dotted_hash(hash, recursive_key = "")
|
26
|
+
hash.each_with_object({}) do |(k, v), ret|
|
27
|
+
key = recursive_key + k.to_s
|
28
|
+
if v.is_a? Hash
|
29
|
+
ret.merge! dotted_hash(v, key + ".")
|
30
|
+
else
|
31
|
+
ret[key] = v
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CustomContextController < ApplicationController
|
2
|
+
include ContextualizedLogs::ContextualizedController
|
3
|
+
|
4
|
+
def contextualize_request(controller)
|
5
|
+
super(controller)
|
6
|
+
|
7
|
+
ContextualizedLogs::ContextualizedController.current_context.custom_attributes = {
|
8
|
+
http: {
|
9
|
+
service: 'rails'
|
10
|
+
}
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
render json: {}, status: 200
|
16
|
+
end
|
17
|
+
end
|
data/app/models/model.rb
CHANGED
data/app/workers/model_worker.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class ModelWorker
|
2
2
|
include Sidekiq::Worker
|
3
3
|
include ContextualizedLogs::ContextualizedWorker
|
4
|
-
|
5
|
-
|
4
|
+
contextualize_worker true
|
5
|
+
contextualize_model true
|
6
6
|
def self.contextualize_args(args)
|
7
7
|
{ model_id: args.first, action: args.last }
|
8
8
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'contextualized_logs'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ContextualizedLogs
|
5
|
+
configure do |config|
|
6
|
+
config.log_formatter = proc do |severity, timestamp, progname, msg|
|
7
|
+
log = ContextualizedLogger.default_formatter.call(severity, timestamp, progname, msg)
|
8
|
+
log = JSON.parse(log)
|
9
|
+
# set log <> APM trace correlation
|
10
|
+
datadog_correlation = Datadog.tracer.active_correlation
|
11
|
+
log.merge(
|
12
|
+
dd: {
|
13
|
+
trace_id: datadog_correlation.trace_id,
|
14
|
+
span_id: datadog_correlation.span_id
|
15
|
+
},
|
16
|
+
ddsource: ['ruby']
|
17
|
+
).to_json + "\n"
|
18
|
+
end
|
19
|
+
config.controller_default_contextualizer = proc do |controller|
|
20
|
+
ContextualizedController.default_contextualize_request(controller)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Datadog.configure do |c|
|
2
|
+
service_name = "#{Rails.env}-rails-app"
|
3
|
+
# https://docs.datadoghq.com/tracing/trace_search_and_analytics/?tab=ruby#automatic-configuration
|
4
|
+
# c.analytics_enabled = true
|
5
|
+
|
6
|
+
# add runtime metrics (runtime.ruby.class_count, runtime.ruby.thread_count, runtime.ruby.gc.*)
|
7
|
+
# http://gems.datadoghq.com/trace/docs/#Processing_Pipeline
|
8
|
+
# c.runtime_metrics_enabled = true
|
9
|
+
#c.tracer debug: true, hostname: '127.0.0.1', log: Logger.new(File.new('log/datadog.log', 'w+'))
|
10
|
+
c.tracer hostname: '127.0.0.1'
|
11
|
+
|
12
|
+
# c.use :rack, service_name: "#{service_name}-rack"
|
13
|
+
c.use :rails, service_name: service_name, database_service: "#{service_name}-active_record", analytics_enabled: false
|
14
|
+
# c.use :rake, service_name: "#{service_name}-rake"
|
15
|
+
# c.use :redis, service_name: "#{service_name}-redis"
|
16
|
+
# c.use :sidekiq, client_service_name: "#{service_name}-sidekiq-client", service_name: "#{service_name}-sidekiq-worker"
|
17
|
+
end
|
@@ -8,16 +8,16 @@ require 'sidekiq'
|
|
8
8
|
# require 'sidekiq/throttled'
|
9
9
|
# Sidekiq::Throttled.setup!
|
10
10
|
|
11
|
-
Sidekiq.configure_server do |config|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
Sidekiq.configure_client do |config|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
11
|
+
# Sidekiq.configure_server do |config|
|
12
|
+
# config.redis = { url: 'redis://127.0.0.1:6379/1' }
|
13
|
+
# config.server_middleware do |chain|
|
14
|
+
# chain.add ContextualizedLogs::Sidekiq::Middleware::Server::RestoreCurrentContext
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Sidekiq.configure_client do |config|
|
19
|
+
# config.redis = { url: 'redis://127.0.0.1:6379/1' }
|
20
|
+
# config.client_middleware do |chain|
|
21
|
+
# chain.add ContextualizedLogs::Sidekiq::Middleware::Client::InjectCurrentContext
|
22
|
+
# end
|
23
|
+
# end
|
data/config/routes.rb
CHANGED
data/db/development.sqlite3
CHANGED
Binary file
|
data/lib/contextualized_logs.rb
CHANGED
@@ -5,7 +5,32 @@ require "contextualized_logs/current_context"
|
|
5
5
|
require "contextualized_logs/sidekiq/middleware/client/inject_current_context"
|
6
6
|
require "contextualized_logs/sidekiq/middleware/server/restore_current_context"
|
7
7
|
require "contextualized_logs/contextualized_worker"
|
8
|
+
require "contextualized_logs/config"
|
8
9
|
|
9
10
|
module ContextualizedLogs
|
10
11
|
require "contextualized_logs/railtie" if defined?(Rails)
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :config
|
15
|
+
|
16
|
+
def config
|
17
|
+
@config || Config.default
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(&block)
|
21
|
+
config = Config.default
|
22
|
+
block.call(config)
|
23
|
+
self.config = config
|
24
|
+
ContextualizedLogger.formatter = config.log_formatter
|
25
|
+
ContextualizedController.default_contextualizer = config.controller_default_contextualizer
|
26
|
+
ContextualizedController.default_contextualize_model = config.controller_default_contextualize_model
|
27
|
+
ContextualizedWorker.default_contextualize_model = config.worker_default_contextualize_model
|
28
|
+
ContextualizedWorker.default_contextualize_worker = config.worker_default_contextualize_worker
|
29
|
+
if defined?(Rails) && Rails.logger.is_a?(ContextualizedLogger)
|
30
|
+
Rails.logger.formatter = config.log_formatter
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
11
34
|
end
|
35
|
+
|
36
|
+
ContextualizedLogs.configure { |config| } # set default configuration
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module ContextualizedLogs
|
3
|
+
class Config
|
4
|
+
DEFAULT_CURRENT_CONTEXT = CurrentContext
|
5
|
+
DEFAULT_CONTROLLER_CONTEXTUALIZE_MODEL = false
|
6
|
+
DEFAULT_WORKER_CONTEXTUALIZE_MODEL = false
|
7
|
+
DEFAULT_WORKER_CONTEXTUALIZE_WORKER = true
|
8
|
+
|
9
|
+
attr_accessor :log_formatter
|
10
|
+
attr_accessor :current_context
|
11
|
+
attr_accessor :controller_default_contextualizer
|
12
|
+
attr_accessor :controller_default_contextualize_model
|
13
|
+
attr_accessor :worker_default_contextualize_worker
|
14
|
+
attr_accessor :worker_default_contextualize_model
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def default
|
18
|
+
config = new
|
19
|
+
config.current_context = DEFAULT_CURRENT_CONTEXT
|
20
|
+
config.controller_default_contextualize_model = DEFAULT_CONTROLLER_CONTEXTUALIZE_MODEL
|
21
|
+
config.worker_default_contextualize_worker = DEFAULT_WORKER_CONTEXTUALIZE_MODEL
|
22
|
+
config.worker_default_contextualize_model = DEFAULT_WORKER_CONTEXTUALIZE_WORKER
|
23
|
+
config
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,62 +1,76 @@
|
|
1
1
|
# https://github.com/rails/rails/pull/29180
|
2
2
|
require 'active_support'
|
3
|
-
|
3
|
+
require 'action_controller'
|
4
4
|
|
5
5
|
module ContextualizedLogs
|
6
6
|
module ContextualizedController
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
class << self
|
10
|
+
attr_accessor :default_contextualizer
|
11
|
+
attr_writer :default_contextualize_model
|
12
|
+
attr_writer :current_context
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def current_context
|
15
|
+
@current_context || ContextualizedLogs.config.current_context
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
self.class.current_context.request_x_forwarded_for = request.x_forwarded_for
|
31
|
-
self.class.current_context.request_xhr = request.xhr? ? 'true' : 'false'
|
32
|
-
rescue StandardError => e
|
33
|
-
Rails.logger.dump_error('error setting context', e)
|
34
|
-
end
|
35
|
-
end
|
18
|
+
def default_contextualize_model
|
19
|
+
@default_contextualize_model || ContextualizedLogs.config.controller_default_contextualize_model
|
20
|
+
end
|
21
|
+
|
22
|
+
def included(base)
|
23
|
+
if !base.ancestors.include?(ActionController::Base) && !base.ancestors.include?(ActionController::API)
|
24
|
+
raise ArgumentError, "ContextualizedLogs::ContextualizedController can only be included in a ActionController::Base or ActionController::API"
|
25
|
+
end
|
26
|
+
|
27
|
+
base.class_eval do
|
28
|
+
before_action do |controller|
|
29
|
+
contextualize_request(controller)
|
30
|
+
end
|
31
|
+
end
|
36
32
|
|
37
|
-
|
38
|
-
if !base.ancestors.include?(ActionController::Base) && !base.ancestors.include?(ActionController::API)
|
39
|
-
raise ArgumentError, "ContextualizedLogs::ContextualizedController can only be included in a ActionController::Base or ActionController::API"
|
33
|
+
base.extend(ClassMethods)
|
40
34
|
end
|
41
35
|
|
42
|
-
|
36
|
+
def default_contextualize_request(controller)
|
37
|
+
# Rails.logger.debug "contextualize_request"
|
38
|
+
# store request && user info in CurrentContext ActiveSupport attribute
|
39
|
+
# which can then be read from anywhere
|
40
|
+
ContextualizedController.current_context.contextualize_model_enabled = controller.class.contextualize_model_enabled?
|
41
|
+
ContextualizedController.current_context.resource_name = "#{controller.class.name.downcase}_#{controller.action_name.downcase}" rescue nil
|
42
|
+
ContextualizedController.current_context.request_uuid = controller.request.uuid
|
43
|
+
ContextualizedController.current_context.request_origin = controller.request.origin
|
44
|
+
ContextualizedController.current_context.request_user_agent = controller.request.user_agent
|
45
|
+
ContextualizedController.current_context.request_referer = controller.request.referer&.to_s
|
46
|
+
ContextualizedController.current_context.request_ip = controller.request.ip
|
47
|
+
ContextualizedController.current_context.request_remote_ip = controller.request.remote_ip
|
48
|
+
ContextualizedController.current_context.request_remote_addr = controller.request.remote_addr
|
49
|
+
ContextualizedController.current_context.request_x_forwarded_for = controller.request.x_forwarded_for
|
50
|
+
ContextualizedController.current_context.request_xhr = controller.request.xhr? ? 'true' : 'false'
|
51
|
+
end
|
43
52
|
end
|
44
53
|
|
45
|
-
|
46
|
-
|
47
|
-
|
54
|
+
def contextualize_request(controller)
|
55
|
+
if ContextualizedController.default_contextualizer
|
56
|
+
ContextualizedController.default_contextualizer.call(controller)
|
57
|
+
return
|
48
58
|
end
|
49
59
|
|
50
|
-
|
51
|
-
|
52
|
-
|
60
|
+
ContextualizedController.default_contextualize_request(controller)
|
61
|
+
rescue StandardError => e
|
62
|
+
Rails.logger.dump_error('error setting controller context', e)
|
63
|
+
end
|
53
64
|
|
54
|
-
|
55
|
-
|
65
|
+
module ClassMethods
|
66
|
+
def contextualize_model_enabled?
|
67
|
+
return @contextualize_model_enabled if defined?(@contextualize_model_enabled)
|
68
|
+
|
69
|
+
ContextualizedController.default_contextualize_model
|
56
70
|
end
|
57
71
|
|
58
|
-
def
|
59
|
-
@
|
72
|
+
def contextualize_model(enable)
|
73
|
+
@contextualize_model_enabled = enable
|
60
74
|
end
|
61
75
|
end
|
62
76
|
end
|
@@ -6,9 +6,82 @@ module ContextualizedLogs
|
|
6
6
|
# support Rails.logger.dump('msg', hash)
|
7
7
|
|
8
8
|
class ContextualizedLogger < ActiveSupport::Logger
|
9
|
+
class << self
|
10
|
+
attr_accessor :formatter
|
11
|
+
attr_writer :current_context
|
12
|
+
|
13
|
+
def current_context
|
14
|
+
@current_context || ContextualizedLogs.config.current_context
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_formatter
|
18
|
+
proc do |severity, timestamp, progname, msg|
|
19
|
+
# format (and enrich) log in JSON format (-> )
|
20
|
+
# https://docs.hq.com/logs/processing/attributes_naming_convention/#source-code
|
21
|
+
# correlation = Datadog.tracer.active_correlation
|
22
|
+
data = {
|
23
|
+
# dd: {
|
24
|
+
# trace_id: correlation.trace_id,
|
25
|
+
# span_id: correlation.span_id
|
26
|
+
# },
|
27
|
+
# ddsource: ['ruby'],
|
28
|
+
syslog: { env: Rails.env, host: Socket.gethostname },
|
29
|
+
type: severity.to_s,
|
30
|
+
time: timestamp
|
31
|
+
}
|
32
|
+
data[:stack] = Kernel.caller.
|
33
|
+
map { |caller| caller.gsub(/#{Rails.root}/, '') }.
|
34
|
+
reject { |caller| caller.start_with?('/usr/local') || caller.include?('/shared/bundle/') || caller.start_with?('/Users/') }.
|
35
|
+
first(15)
|
36
|
+
data[:log_type] = 'log'
|
37
|
+
data.merge!(parse_msg(msg)) # parse message (string, hash, error, ...)
|
38
|
+
data.merge!(current_context.context) # merge current request context
|
39
|
+
data.to_json + "\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def parse_msg(msg)
|
46
|
+
data = {}
|
47
|
+
case msg
|
48
|
+
when Hash
|
49
|
+
# used by logger.dump(msg|error, attributes = {})
|
50
|
+
if msg.include?(:attributes)
|
51
|
+
# adding message as log attributes if is a hash
|
52
|
+
data.merge!(parse_error(msg[:msg]))
|
53
|
+
data[:attributes] = msg[:attributes]
|
54
|
+
else
|
55
|
+
data.merge!(parse_error(msg))
|
56
|
+
end
|
57
|
+
else
|
58
|
+
data.merge!(parse_error(msg))
|
59
|
+
end
|
60
|
+
data
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_error(msg)
|
64
|
+
data = {}
|
65
|
+
case msg
|
66
|
+
when ::Exception
|
67
|
+
# format data to be interpreted as an error logs by
|
68
|
+
data[:error] = {
|
69
|
+
kind: msg.class.to_s,
|
70
|
+
message: msg.message,
|
71
|
+
stack: (msg.backtrace || []).join('; ')
|
72
|
+
}
|
73
|
+
else
|
74
|
+
data[:message] = msg.to_s
|
75
|
+
end
|
76
|
+
data
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_accessor :formatter
|
81
|
+
|
9
82
|
def initialize(*args)
|
10
83
|
super(*args)
|
11
|
-
@formatter = formatter
|
84
|
+
@formatter = self.class.formatter || self.class.default_formatter
|
12
85
|
end
|
13
86
|
|
14
87
|
def dump(msg, attributes, severity = :info)
|
@@ -19,72 +92,6 @@ module ContextualizedLogs
|
|
19
92
|
def dump_error(msg, attributes)
|
20
93
|
dump(msg, attributes, :error)
|
21
94
|
end
|
22
|
-
|
23
|
-
def current_context
|
24
|
-
CurrentContext.context
|
25
|
-
end
|
26
|
-
|
27
|
-
def formatter
|
28
|
-
Proc.new{|severity, timestamp, progname, msg|
|
29
|
-
# format (and enrich) log in JSON format (-> )
|
30
|
-
# https://docs.hq.com/logs/processing/attributes_naming_convention/#source-code
|
31
|
-
# correlation = Datadog.tracer.active_correlation
|
32
|
-
data = {
|
33
|
-
# dd: {
|
34
|
-
# trace_id: correlation.trace_id,
|
35
|
-
# span_id: correlation.span_id
|
36
|
-
# },
|
37
|
-
# ddsource: ['ruby'],
|
38
|
-
syslog: { env: Rails.env, host: Socket.gethostname },
|
39
|
-
type: severity.to_s,
|
40
|
-
time: timestamp
|
41
|
-
}
|
42
|
-
data[:stack] = Kernel.caller.
|
43
|
-
map { |caller| caller.gsub(/#{Rails.root}/, '') }.
|
44
|
-
reject { |caller| caller.start_with?('/usr/local') || caller.include?('/shared/bundle/') || caller.start_with?('/Users/') }.
|
45
|
-
first(15)
|
46
|
-
data[:log_type] = 'log'
|
47
|
-
data.merge!(parse_msg(msg)) # parse message (string, hash, error, ...)
|
48
|
-
data.merge!(current_context) # merge current request context
|
49
|
-
data.to_json + "\n"
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def parse_msg(msg)
|
56
|
-
data = {}
|
57
|
-
case msg
|
58
|
-
when Hash
|
59
|
-
# used by logger.dump(msg|error, attributes = {})
|
60
|
-
if msg.include?(:attributes)
|
61
|
-
# adding message as log attributes if is a hash
|
62
|
-
data.merge!(parse_error(msg[:msg]))
|
63
|
-
data[:attributes] = msg[:attributes]
|
64
|
-
else
|
65
|
-
data.merge!(parse_error(msg))
|
66
|
-
end
|
67
|
-
else
|
68
|
-
data.merge!(parse_error(msg))
|
69
|
-
end
|
70
|
-
data
|
71
|
-
end
|
72
|
-
|
73
|
-
def parse_error(msg)
|
74
|
-
data = {}
|
75
|
-
case msg
|
76
|
-
when ::Exception
|
77
|
-
# format data to be interpreted as an error logs by
|
78
|
-
data[:error] = {
|
79
|
-
kind: msg.class.to_s,
|
80
|
-
message: msg.message,
|
81
|
-
stack: (msg.backtrace || []).join('; ')
|
82
|
-
}
|
83
|
-
else
|
84
|
-
data[:message] = msg.to_s
|
85
|
-
end
|
86
|
-
data
|
87
|
-
end
|
88
95
|
end
|
89
96
|
|
90
97
|
end
|