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.
@@ -1,2 +1,3 @@
1
1
  class ApplicationController < ActionController::API
2
+ include DatadogSetTraceDetails # inject user_id in datadog APM trace
2
3
  end
@@ -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
@@ -1,6 +1,6 @@
1
1
  class ModelController < ApplicationController
2
2
  include ContextualizedLogs::ContextualizedController
3
- contextualized_model true
3
+ contextualize_model true
4
4
 
5
5
  def index
6
6
  models = Model.all.map { |model| { id: model.id, value: model.value} }
data/app/models/model.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  class Model < ActiveRecord::Base
2
2
  include ContextualizedLogs::ContextualizedModel
3
- contextualizable keys: { values: :value }
3
+ contextualizable keys: { model_ids: :id, model_values: :value }
4
4
  end
@@ -1,8 +1,8 @@
1
1
  class ModelWorker
2
2
  include Sidekiq::Worker
3
3
  include ContextualizedLogs::ContextualizedWorker
4
- contextualized_worker true
5
- contextualized_model true
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
- 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
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
@@ -1,3 +1,4 @@
1
1
  Rails.application.routes.draw do
2
2
  resources :model
3
+ get '/custom_context' => 'custom_context#show'
3
4
  end
Binary file
@@ -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
- require_relative 'current_context'
3
+ require 'action_controller'
4
4
 
5
5
  module ContextualizedLogs
6
6
  module ContextualizedController
7
7
  extend ActiveSupport::Concern
8
8
 
9
- DEFAULT_CONTEXTUALIZED_MODEL_ENABLED = false
10
- DEFAULT_CURRENT_CONTEXT = ContextualizedLogs::CurrentContext
9
+ class << self
10
+ attr_accessor :default_contextualizer
11
+ attr_writer :default_contextualize_model
12
+ attr_writer :current_context
11
13
 
12
- included do
13
- before_action :contextualize_requests
14
- end
14
+ def current_context
15
+ @current_context || ContextualizedLogs.config.current_context
16
+ end
15
17
 
16
- def contextualize_requests
17
- # Rails.logger.debug "contextualize_requests"
18
- # store request && user info in CurrentContext ActiveSupport attribute
19
- # which can then be read from anywhere
20
- self.class.current_context.contextualized_model_enabled = self.class.contextualized_model_enabled?
21
- begin
22
- self.class.current_context.resource_name = "#{self.class.name.downcase}_#{action_name.downcase}" rescue nil
23
- self.class.current_context.request_uuid = request.uuid
24
- self.class.current_context.request_origin = request.origin
25
- self.class.current_context.request_user_agent = request.user_agent
26
- self.class.current_context.request_referer = request.referer&.to_s
27
- self.class.current_context.request_ip = request.ip
28
- self.class.current_context.request_remote_ip = request.remote_ip
29
- self.class.current_context.request_remote_addr = request.remote_addr
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
- def self.included(base)
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
- base.extend(ClassMethods)
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
- module ClassMethods
46
- def contextualized_model_enabled?
47
- @contextualized_model_enabled || DEFAULT_CONTEXTUALIZED_MODEL_ENABLED
54
+ def contextualize_request(controller)
55
+ if ContextualizedController.default_contextualizer
56
+ ContextualizedController.default_contextualizer.call(controller)
57
+ return
48
58
  end
49
59
 
50
- def contextualized_model(enable)
51
- @contextualized_model_enabled = enable
52
- end
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
- def current_context
55
- @current_context || DEFAULT_CURRENT_CONTEXT
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 set_current_context(current_context)
59
- @current_context = current_context
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