loga 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.rubocop_todo.yml +33 -0
- data/Appraisals +14 -0
- data/Gemfile +8 -0
- data/README.md +147 -0
- data/Rakefile +9 -0
- data/circle.yml +23 -0
- data/gemfiles/rails32.gemfile +11 -0
- data/gemfiles/rails40.gemfile +11 -0
- data/gemfiles/sinatra14.gemfile +11 -0
- data/gemfiles/unit.gemfile +9 -0
- data/lib/loga.rb +33 -0
- data/lib/loga/configuration.rb +96 -0
- data/lib/loga/event.rb +21 -0
- data/lib/loga/ext/rails/rack/logger3.rb +21 -0
- data/lib/loga/ext/rails/rack/logger4.rb +13 -0
- data/lib/loga/formatter.rb +104 -0
- data/lib/loga/parameter_filter.rb +65 -0
- data/lib/loga/rack/logger.rb +102 -0
- data/lib/loga/rack/request.rb +77 -0
- data/lib/loga/rack/request_id.rb +44 -0
- data/lib/loga/railtie.rb +139 -0
- data/lib/loga/tagged_logging.rb +76 -0
- data/lib/loga/utilities.rb +7 -0
- data/lib/loga/version.rb +3 -0
- data/loga.gemspec +31 -0
- data/spec/fixtures/README.md +8 -0
- data/spec/fixtures/rails32/Rakefile +7 -0
- data/spec/fixtures/rails32/app/controllers/application_controller.rb +28 -0
- data/spec/fixtures/rails32/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails32/app/views/layouts/application.html.erb +14 -0
- data/spec/fixtures/rails32/app/views/user.html.erb +1 -0
- data/spec/fixtures/rails32/config.ru +4 -0
- data/spec/fixtures/rails32/config/application.rb +71 -0
- data/spec/fixtures/rails32/config/boot.rb +6 -0
- data/spec/fixtures/rails32/config/environment.rb +5 -0
- data/spec/fixtures/rails32/config/environments/development.rb +26 -0
- data/spec/fixtures/rails32/config/environments/production.rb +50 -0
- data/spec/fixtures/rails32/config/environments/test.rb +35 -0
- data/spec/fixtures/rails32/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails32/config/initializers/inflections.rb +15 -0
- data/spec/fixtures/rails32/config/initializers/mime_types.rb +5 -0
- data/spec/fixtures/rails32/config/initializers/secret_token.rb +7 -0
- data/spec/fixtures/rails32/config/initializers/session_store.rb +8 -0
- data/spec/fixtures/rails32/config/initializers/wrap_parameters.rb +10 -0
- data/spec/fixtures/rails32/config/locales/en.yml +5 -0
- data/spec/fixtures/rails32/config/routes.rb +64 -0
- data/spec/fixtures/rails32/public/404.html +26 -0
- data/spec/fixtures/rails32/public/422.html +26 -0
- data/spec/fixtures/rails32/public/500.html +25 -0
- data/spec/fixtures/rails32/public/favicon.ico +0 -0
- data/spec/fixtures/rails32/public/index.html +241 -0
- data/spec/fixtures/rails32/public/robots.txt +5 -0
- data/spec/fixtures/rails32/script/rails +6 -0
- data/spec/fixtures/rails40/Rakefile +6 -0
- data/spec/fixtures/rails40/app/controllers/application_controller.rb +30 -0
- data/spec/fixtures/rails40/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails40/app/views/layouts/application.html.erb +14 -0
- data/spec/fixtures/rails40/app/views/user.html.erb +1 -0
- data/spec/fixtures/rails40/bin/bundle +3 -0
- data/spec/fixtures/rails40/bin/rails +4 -0
- data/spec/fixtures/rails40/bin/rake +4 -0
- data/spec/fixtures/rails40/config.ru +4 -0
- data/spec/fixtures/rails40/config/application.rb +37 -0
- data/spec/fixtures/rails40/config/boot.rb +4 -0
- data/spec/fixtures/rails40/config/environment.rb +5 -0
- data/spec/fixtures/rails40/config/environments/development.rb +24 -0
- data/spec/fixtures/rails40/config/environments/production.rb +65 -0
- data/spec/fixtures/rails40/config/environments/test.rb +39 -0
- data/spec/fixtures/rails40/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails40/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails40/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails40/config/initializers/mime_types.rb +5 -0
- data/spec/fixtures/rails40/config/initializers/secret_token.rb +12 -0
- data/spec/fixtures/rails40/config/initializers/session_store.rb +3 -0
- data/spec/fixtures/rails40/config/initializers/wrap_parameters.rb +9 -0
- data/spec/fixtures/rails40/config/locales/en.yml +23 -0
- data/spec/fixtures/rails40/config/routes.rb +62 -0
- data/spec/fixtures/rails40/public/404.html +58 -0
- data/spec/fixtures/rails40/public/422.html +58 -0
- data/spec/fixtures/rails40/public/500.html +57 -0
- data/spec/fixtures/rails40/public/favicon.ico +0 -0
- data/spec/fixtures/rails40/public/robots.txt +5 -0
- data/spec/integration/rails/railtie_spec.rb +64 -0
- data/spec/integration/rails/request_spec.rb +42 -0
- data/spec/integration/sinatra_spec.rb +54 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/request_spec.rb +183 -0
- data/spec/support/timecop_shared.rb +7 -0
- data/spec/unit/loga/configuration_spec.rb +123 -0
- data/spec/unit/loga/event_spec.rb +20 -0
- data/spec/unit/loga/formatter_spec.rb +186 -0
- data/spec/unit/loga/parameter_filter_spec.rb +76 -0
- data/spec/unit/loga/rack/logger_spec.rb +114 -0
- data/spec/unit/loga/rack/request_spec.rb +70 -0
- data/spec/unit/loga/utilities_spec.rb +16 -0
- data/spec/unit/loga_spec.rb +41 -0
- metadata +357 -0
data/lib/loga/event.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Loga
|
|
2
|
+
class Event
|
|
3
|
+
attr_accessor :data, :exception, :message, :timestamp, :type
|
|
4
|
+
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
@data = opts[:data]
|
|
7
|
+
@exception = opts[:exception]
|
|
8
|
+
@message = safe_encode(opts[:message])
|
|
9
|
+
@timestamp = opts[:timestamp]
|
|
10
|
+
@type = opts[:type]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Guard against Encoding::UndefinedConversionError
|
|
16
|
+
# http://stackoverflow.com/questions/13003287/encodingundefinedconversionerror
|
|
17
|
+
def safe_encode(text)
|
|
18
|
+
text.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'rails/rack/logger'
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module Rack
|
|
5
|
+
class Logger
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def call_app(_request, env)
|
|
9
|
+
@app.call(env)
|
|
10
|
+
ensure
|
|
11
|
+
ActiveSupport::LogSubscriber.flush_all!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def compute_tags(_request)
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Loga
|
|
5
|
+
class Formatter < Logger::Formatter
|
|
6
|
+
include TaggedLogging::Formatter
|
|
7
|
+
|
|
8
|
+
GELF_VERSION = '1.1'.freeze
|
|
9
|
+
SYSLOG_LEVEL_MAPPING = {
|
|
10
|
+
'DEBUG' => 7,
|
|
11
|
+
'INFO' => 6,
|
|
12
|
+
'WARN' => 4,
|
|
13
|
+
'ERROR' => 3,
|
|
14
|
+
'FATAL' => 2,
|
|
15
|
+
'UNKNOWN' => 1,
|
|
16
|
+
}.freeze
|
|
17
|
+
DEFAULT_TYPE = 'default'.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(opts)
|
|
20
|
+
@service_name = opts.fetch(:service_name)
|
|
21
|
+
@service_version = opts.fetch(:service_version)
|
|
22
|
+
@host = opts.fetch(:host)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call(severity, time, _progname, message)
|
|
26
|
+
event = build_event(time, message)
|
|
27
|
+
payload = format_additional_fields(event.data)
|
|
28
|
+
|
|
29
|
+
payload[:short_message] = event.message
|
|
30
|
+
payload[:timestamp] = compute_timestamp(event.timestamp)
|
|
31
|
+
payload[:host] = @host
|
|
32
|
+
payload[:level] = compute_level(severity)
|
|
33
|
+
payload[:version] = GELF_VERSION
|
|
34
|
+
|
|
35
|
+
"#{payload.to_json}\n"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_event(time, message)
|
|
41
|
+
event = case message
|
|
42
|
+
when Loga::Event
|
|
43
|
+
message
|
|
44
|
+
else
|
|
45
|
+
Loga::Event.new(message: message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
event.timestamp ||= time
|
|
49
|
+
event.data ||= {}
|
|
50
|
+
event.data.tap do |hash|
|
|
51
|
+
hash.merge! compute_exception(event.exception)
|
|
52
|
+
hash.merge! compute_type(event.type)
|
|
53
|
+
# Overwrite hash with Loga's additional fields
|
|
54
|
+
hash.merge! loga_additional_fields
|
|
55
|
+
end
|
|
56
|
+
event
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def compute_timestamp(timestamp)
|
|
60
|
+
(timestamp.to_f * 1000).floor / 1000.0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def compute_level(severity)
|
|
64
|
+
SYSLOG_LEVEL_MAPPING[severity]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_additional_fields(fields)
|
|
68
|
+
fields.each_with_object({}) do |(main_key, values), hash|
|
|
69
|
+
if values.is_a?(Hash)
|
|
70
|
+
values.each do |sub_key, sub_values|
|
|
71
|
+
hash["_#{main_key}.#{sub_key}"] = sub_values
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
hash["_#{main_key}"] = values
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def compute_exception(exception)
|
|
80
|
+
return {} unless exception
|
|
81
|
+
{
|
|
82
|
+
exception: {
|
|
83
|
+
klass: exception.class.to_s,
|
|
84
|
+
message: exception.message,
|
|
85
|
+
backtrace: exception.backtrace.first(10).join("\n"),
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def compute_type(type)
|
|
91
|
+
type ? { type: type } : {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def loga_additional_fields
|
|
95
|
+
{
|
|
96
|
+
service: {
|
|
97
|
+
name: @service_name,
|
|
98
|
+
version: @service_version,
|
|
99
|
+
},
|
|
100
|
+
tags: current_tags,
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Loga
|
|
2
|
+
class ParameterFilter
|
|
3
|
+
FILTERED = '[FILTERED]'.freeze
|
|
4
|
+
|
|
5
|
+
attr_accessor :filters
|
|
6
|
+
|
|
7
|
+
def initialize(filters)
|
|
8
|
+
@filters = filters
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def filter(params)
|
|
12
|
+
compiled_filters.call(params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def compiled_filters
|
|
18
|
+
@compiled_filters ||= CompiledFilter.compile(filters)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class CompiledFilter
|
|
22
|
+
def self.compile(filters)
|
|
23
|
+
->(params) { params.dup } if filters.empty?
|
|
24
|
+
|
|
25
|
+
regexps = []
|
|
26
|
+
strings = []
|
|
27
|
+
|
|
28
|
+
filters.each do |item|
|
|
29
|
+
if item.is_a?(Regexp)
|
|
30
|
+
regexps << item
|
|
31
|
+
else
|
|
32
|
+
strings << Regexp.escape(item.to_s)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
|
|
37
|
+
new regexps
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :regexps
|
|
41
|
+
|
|
42
|
+
def initialize(regexps)
|
|
43
|
+
@regexps = regexps
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def call(original_params)
|
|
47
|
+
filtered_params = {}
|
|
48
|
+
|
|
49
|
+
original_params.each do |key, value|
|
|
50
|
+
if regexps.any? { |r| key =~ r }
|
|
51
|
+
value = FILTERED
|
|
52
|
+
elsif value.is_a?(Hash)
|
|
53
|
+
value = call(value)
|
|
54
|
+
elsif value.is_a?(Array)
|
|
55
|
+
value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
filtered_params[key] = value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
filtered_params
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Loga
|
|
2
|
+
module Rack
|
|
3
|
+
class Logger
|
|
4
|
+
include Utilities
|
|
5
|
+
|
|
6
|
+
attr_reader :logger, :taggers
|
|
7
|
+
def initialize(app, logger = nil, taggers = nil)
|
|
8
|
+
@app = app
|
|
9
|
+
@logger = logger
|
|
10
|
+
@taggers = taggers || []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(env)
|
|
14
|
+
request = Loga::Rack::Request.new(env)
|
|
15
|
+
env['loga.request.original_path'] = request.path
|
|
16
|
+
|
|
17
|
+
if logger.respond_to?(:tagged)
|
|
18
|
+
logger.tagged(compute_tags(request)) { call_app(request, env) }
|
|
19
|
+
else
|
|
20
|
+
call_app(request, env)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :data, :env, :request, :started_at
|
|
27
|
+
|
|
28
|
+
def call_app(request, env)
|
|
29
|
+
@data = {}
|
|
30
|
+
@env = env
|
|
31
|
+
@request = request
|
|
32
|
+
@started_at = Time.now
|
|
33
|
+
|
|
34
|
+
@app.call(env).tap { |status, _headers, _body| data['status'] = status.to_i }
|
|
35
|
+
ensure
|
|
36
|
+
set_data
|
|
37
|
+
send_message
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_data
|
|
41
|
+
data['method'] = request.request_method
|
|
42
|
+
data['path'] = request.original_path
|
|
43
|
+
data['params'] = request.filtered_parameters
|
|
44
|
+
data['request_id'] = request.uuid
|
|
45
|
+
data['request_ip'] = request.ip
|
|
46
|
+
data['user_agent'] = request.user_agent
|
|
47
|
+
data['duration'] = duration_in_ms(started_at, Time.now)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def send_message
|
|
51
|
+
event = Loga::Event.new(
|
|
52
|
+
data: { request: data },
|
|
53
|
+
exception: fetch_exception,
|
|
54
|
+
message: compute_message,
|
|
55
|
+
timestamp: started_at,
|
|
56
|
+
type: 'request',
|
|
57
|
+
)
|
|
58
|
+
logger.public_send(compute_level, event)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def compute_message
|
|
62
|
+
'%{method} %{filtered_full_path} %{status} in %{duration}ms' % {
|
|
63
|
+
method: request.request_method,
|
|
64
|
+
filtered_full_path: request.filtered_full_path,
|
|
65
|
+
status: data['status'],
|
|
66
|
+
duration: data['duration'],
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def compute_level
|
|
71
|
+
fetch_exception ? :error : :info
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fetch_exception
|
|
75
|
+
framework_exception.tap do |e|
|
|
76
|
+
return filtered_exceptions.include?(e.class.to_s) ? nil : e
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def framework_exception
|
|
81
|
+
env['loga.exception'] || env['action_dispatch.exception'] || env['sinatra.error']
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def filtered_exceptions
|
|
85
|
+
%w(ActionController::RoutingError Sinatra::NotFound)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def compute_tags(request)
|
|
89
|
+
taggers.collect do |tag|
|
|
90
|
+
case tag
|
|
91
|
+
when Proc
|
|
92
|
+
tag.call(request)
|
|
93
|
+
when Symbol
|
|
94
|
+
request.send(tag)
|
|
95
|
+
else
|
|
96
|
+
tag
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'rack/request'
|
|
2
|
+
require 'rack/utils'
|
|
3
|
+
|
|
4
|
+
module Loga
|
|
5
|
+
module Rack
|
|
6
|
+
class Request < ::Rack::Request
|
|
7
|
+
ACTION_DISPATCH_REQUEST_ID = 'action_dispatch.request_id'.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(env)
|
|
10
|
+
super
|
|
11
|
+
@uuid = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def uuid
|
|
15
|
+
@uuid ||= env[ACTION_DISPATCH_REQUEST_ID]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def original_path
|
|
19
|
+
env['loga.request.original_path']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filtered_full_path
|
|
23
|
+
@filtered_full_path ||=
|
|
24
|
+
query_string.empty? ? original_path : "#{original_path}?#{filtered_query_string}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def filtered_parameters
|
|
28
|
+
@filtered_parameters ||= filtered_query_hash.merge(filtered_form_hash)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def filtered_query_hash
|
|
32
|
+
@filtered_query_hash ||= filter_hash(query_hash)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def filtered_form_hash
|
|
36
|
+
@filter_form_hash ||= filter_hash(form_hash)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def query_hash
|
|
42
|
+
params
|
|
43
|
+
env['rack.request.query_hash'] || {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def form_hash
|
|
47
|
+
params
|
|
48
|
+
env['rack.request.form_hash'] || {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def filter_hash(hash)
|
|
52
|
+
parameter_filter.filter(hash)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
KV_RE = '[^&;=]+'
|
|
56
|
+
PAIR_RE = /(#{KV_RE})=(#{KV_RE})/
|
|
57
|
+
def filtered_query_string
|
|
58
|
+
query_string.gsub(PAIR_RE) do |_|
|
|
59
|
+
parameter_filter.filter([[$1, $2]]).first.join('=')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parameter_filter
|
|
64
|
+
@filter_parameters ||=
|
|
65
|
+
ParameterFilter.new(loga_filter_parameters | action_dispatch_filter_params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def loga_filter_parameters
|
|
69
|
+
Loga.configuration.filter_parameters || []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def action_dispatch_filter_params
|
|
73
|
+
env['action_dispatch.parameter_filter'] || []
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Shamelessly copied from ActionDispatch::RequestId
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
require 'active_support/core_ext/string/access'
|
|
4
|
+
require 'active_support/core_ext/object/blank'
|
|
5
|
+
|
|
6
|
+
# rubocop:disable Metrics/LineLength, Lint/AssignmentInCondition, Style/GuardClause
|
|
7
|
+
module Loga
|
|
8
|
+
module Rack
|
|
9
|
+
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
|
|
10
|
+
# ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
|
|
11
|
+
#
|
|
12
|
+
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
|
13
|
+
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
|
|
14
|
+
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
|
|
15
|
+
#
|
|
16
|
+
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
|
|
17
|
+
# from multiple pieces of the stack.
|
|
18
|
+
class RequestId
|
|
19
|
+
def initialize(app)
|
|
20
|
+
@app = app
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
env['action_dispatch.request_id'] = external_request_id(env) || internal_request_id
|
|
25
|
+
@app.call(env).tap do |_status, headers, _body|
|
|
26
|
+
headers['X-Request-Id'] = env['action_dispatch.request_id']
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def external_request_id(env)
|
|
33
|
+
if request_id = env['HTTP_X_REQUEST_ID'].presence
|
|
34
|
+
request_id.gsub(/[^\w\-]/, '').first(255)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def internal_request_id
|
|
39
|
+
SecureRandom.uuid
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
# rubocop:enable Metrics/LineLength, Lint/AssignmentInCondition, Style/GuardClause
|