otel_beacon 0.1.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/CHANGELOG.md +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +258 -0
- data/UPGRADING.md +60 -0
- data/lib/otel_beacon/breadcrumb.rb +30 -0
- data/lib/otel_beacon/client.rb +296 -0
- data/lib/otel_beacon/context.rb +75 -0
- data/lib/otel_beacon/integrations/sidekiq.rb +62 -0
- data/lib/otel_beacon/log_subscriber.rb +30 -0
- data/lib/otel_beacon/rails/controller_methods.rb +59 -0
- data/lib/otel_beacon/rails/job_methods.rb +49 -0
- data/lib/otel_beacon/rails/logger.rb +120 -0
- data/lib/otel_beacon/rails/mailer_methods.rb +37 -0
- data/lib/otel_beacon/rails/railtie.rb +52 -0
- data/lib/otel_beacon/runtime_context.rb +102 -0
- data/lib/otel_beacon/sanitizer.rb +50 -0
- data/lib/otel_beacon/scope.rb +37 -0
- data/lib/otel_beacon/version.rb +5 -0
- data/lib/otel_beacon.rb +169 -0
- metadata +162 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Context
|
|
5
|
+
class << self
|
|
6
|
+
def current
|
|
7
|
+
Thread.current[:otel_beacon_context] ||= default_context
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def reset!
|
|
11
|
+
Thread.current[:otel_beacon_context] = default_context
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def user
|
|
15
|
+
current[:user]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tags
|
|
19
|
+
current[:tags]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def extra
|
|
23
|
+
current[:extra]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def breadcrumbs
|
|
27
|
+
current[:breadcrumbs]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def contexts
|
|
31
|
+
current[:contexts]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fingerprint
|
|
35
|
+
current[:fingerprint]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fingerprint=(value)
|
|
39
|
+
current[:fingerprint] = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def push_scope
|
|
43
|
+
stack << deep_dup(current)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pop_scope
|
|
47
|
+
if stack.any?
|
|
48
|
+
Thread.current[:otel_beacon_context] = stack.pop
|
|
49
|
+
else
|
|
50
|
+
reset!
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def stack
|
|
55
|
+
Thread.current[:otel_beacon_scope_stack] ||= []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def default_context
|
|
61
|
+
{ user: {}, tags: {}, extra: {}, breadcrumbs: [], contexts: {}, fingerprint: nil }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def deep_dup(hash)
|
|
65
|
+
hash.transform_values do |v|
|
|
66
|
+
case v
|
|
67
|
+
when Hash then v.dup
|
|
68
|
+
when Array then v.dup
|
|
69
|
+
else v
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Integrations
|
|
5
|
+
class SidekiqMiddleware
|
|
6
|
+
def call(worker, job, queue)
|
|
7
|
+
OtelBeacon.reset_context!
|
|
8
|
+
OtelBeacon.set_tags(
|
|
9
|
+
job_class: worker.class.name,
|
|
10
|
+
queue: queue,
|
|
11
|
+
job_id: job["jid"]
|
|
12
|
+
)
|
|
13
|
+
OtelBeacon.set_context(:sidekiq,
|
|
14
|
+
queue: queue,
|
|
15
|
+
retry_count: job["retry_count"] || 0,
|
|
16
|
+
created_at: job["created_at"],
|
|
17
|
+
enqueued_at: job["enqueued_at"])
|
|
18
|
+
OtelBeacon.add_breadcrumb(:job, "#{worker.class.name} started", queue: queue, jid: job["jid"])
|
|
19
|
+
|
|
20
|
+
yield
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
OtelBeacon.capture_exception(e, extra: {
|
|
23
|
+
job_class: worker.class.name,
|
|
24
|
+
job_id: job["jid"],
|
|
25
|
+
queue: queue,
|
|
26
|
+
args: filtered_args(job["args"])
|
|
27
|
+
})
|
|
28
|
+
raise
|
|
29
|
+
ensure
|
|
30
|
+
OtelBeacon.flush_context_to_span
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def filtered_args(args)
|
|
36
|
+
return [] unless args.is_a?(Array)
|
|
37
|
+
|
|
38
|
+
args.first(5).map do |arg|
|
|
39
|
+
case arg
|
|
40
|
+
when Hash then OtelBeacon::Sanitizer.sanitize_params(arg)
|
|
41
|
+
when String then arg.length > 100 ? "#{arg[0..100]}..." : arg
|
|
42
|
+
else arg.inspect[0..100]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
rescue StandardError
|
|
46
|
+
[ "[unable to serialize]" ]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module Sidekiq
|
|
51
|
+
def self.setup
|
|
52
|
+
return unless defined?(::Sidekiq)
|
|
53
|
+
|
|
54
|
+
::Sidekiq.configure_server do |config|
|
|
55
|
+
config.server_middleware do |chain|
|
|
56
|
+
chain.add OtelBeacon::Integrations::SidekiqMiddleware
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
5
|
+
def capture_exception(event)
|
|
6
|
+
error do
|
|
7
|
+
exception = event.payload[:exception]
|
|
8
|
+
"Captured exception: #{exception.class} - #{exception.message}"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def capture_message(event)
|
|
13
|
+
info do
|
|
14
|
+
"Captured message: #{event.payload[:message]} (level: #{event.payload[:level]})"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_breadcrumb(event)
|
|
19
|
+
debug do
|
|
20
|
+
"Added breadcrumb: #{event.payload[:category]} - #{event.payload[:message]}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def context_error(event)
|
|
25
|
+
error do
|
|
26
|
+
"Context error: #{event.payload[:error]}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Rails
|
|
5
|
+
module ControllerMethods
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
before_action :otel_beacon_setup_context
|
|
10
|
+
around_action :otel_beacon_capture_breadcrumbs
|
|
11
|
+
rescue_from StandardError, with: :otel_beacon_capture_and_reraise
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def otel_beacon_setup_context
|
|
17
|
+
OtelBeacon.reset_context!
|
|
18
|
+
|
|
19
|
+
user = OtelBeacon.config.resolve_current_user(self)
|
|
20
|
+
if user
|
|
21
|
+
attrs = OtelBeacon.config.extract_user_attributes(user)
|
|
22
|
+
OtelBeacon.set_user(**attrs) if attrs.any?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
OtelBeacon.set_tags(
|
|
26
|
+
controller: controller_name,
|
|
27
|
+
action: action_name,
|
|
28
|
+
environment: ::Rails.env.to_s
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
OtelBeacon::RuntimeContext.set_device_context(request)
|
|
32
|
+
OtelBeacon::RuntimeContext.set_browser_context(request)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def otel_beacon_capture_breadcrumbs
|
|
36
|
+
OtelBeacon.add_breadcrumb(:controller, "#{controller_name}##{action_name}", params: otel_beacon_filtered_params)
|
|
37
|
+
yield
|
|
38
|
+
ensure
|
|
39
|
+
OtelBeacon.flush_context_to_span
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def otel_beacon_capture_and_reraise(exception)
|
|
43
|
+
OtelBeacon.capture_exception(
|
|
44
|
+
exception,
|
|
45
|
+
request: request,
|
|
46
|
+
params: params,
|
|
47
|
+
extra: { controller: controller_name, action: action_name, format: request.format.to_s }
|
|
48
|
+
)
|
|
49
|
+
raise exception
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def otel_beacon_filtered_params
|
|
53
|
+
params.to_unsafe_h.except(:controller, :action, :format)
|
|
54
|
+
rescue StandardError
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Rails
|
|
5
|
+
module JobMethods
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
around_perform :otel_beacon_capture_job
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def otel_beacon_capture_job
|
|
15
|
+
OtelBeacon.reset_context!
|
|
16
|
+
OtelBeacon.set_tags(
|
|
17
|
+
job_class: self.class.name,
|
|
18
|
+
queue: queue_name || "default",
|
|
19
|
+
job_id: job_id
|
|
20
|
+
)
|
|
21
|
+
OtelBeacon.add_breadcrumb(:job, "#{self.class.name} started", queue: queue_name, job_id: job_id)
|
|
22
|
+
|
|
23
|
+
yield
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
OtelBeacon.capture_exception(e, extra: {
|
|
26
|
+
job_class: self.class.name,
|
|
27
|
+
job_id: job_id,
|
|
28
|
+
queue: queue_name,
|
|
29
|
+
arguments: filtered_arguments
|
|
30
|
+
})
|
|
31
|
+
raise
|
|
32
|
+
ensure
|
|
33
|
+
OtelBeacon.flush_context_to_span
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def filtered_arguments
|
|
37
|
+
arguments.first(5).map do |arg|
|
|
38
|
+
case arg
|
|
39
|
+
when Hash then OtelBeacon::Sanitizer.sanitize_params(arg)
|
|
40
|
+
when String then arg.length > 100 ? "#{arg[0..100]}..." : arg
|
|
41
|
+
else arg.inspect[0..100]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
rescue StandardError
|
|
45
|
+
[ "[unable to serialize]" ]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "delegate"
|
|
5
|
+
|
|
6
|
+
module OtelBeacon
|
|
7
|
+
module Rails
|
|
8
|
+
class Logger < SimpleDelegator
|
|
9
|
+
SEVERITY_MAP = {
|
|
10
|
+
::Logger::DEBUG => "DEBUG",
|
|
11
|
+
::Logger::INFO => "INFO",
|
|
12
|
+
::Logger::WARN => "WARN",
|
|
13
|
+
::Logger::ERROR => "ERROR",
|
|
14
|
+
::Logger::FATAL => "FATAL",
|
|
15
|
+
::Logger::UNKNOWN => "UNKNOWN"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def initialize(original_logger)
|
|
19
|
+
super(original_logger)
|
|
20
|
+
@original_logger = original_logger
|
|
21
|
+
@otel_logger = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add(severity, message = nil, progname = nil)
|
|
25
|
+
message = yield if block_given?
|
|
26
|
+
message ||= progname
|
|
27
|
+
result = @original_logger.add(severity, message, progname)
|
|
28
|
+
emit_to_otel(severity, message) if message
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def level
|
|
33
|
+
@original_logger.level
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def level=(value)
|
|
37
|
+
@original_logger.level = value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def debug(message = nil)
|
|
41
|
+
message = yield if block_given?
|
|
42
|
+
@original_logger.debug(message)
|
|
43
|
+
emit_to_otel(::Logger::DEBUG, message) if message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def info(message = nil)
|
|
47
|
+
message = yield if block_given?
|
|
48
|
+
@original_logger.info(message)
|
|
49
|
+
emit_to_otel(::Logger::INFO, message) if message
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def warn(message = nil)
|
|
53
|
+
message = yield if block_given?
|
|
54
|
+
@original_logger.warn(message)
|
|
55
|
+
emit_to_otel(::Logger::WARN, message) if message
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def error(message = nil)
|
|
59
|
+
message = yield if block_given?
|
|
60
|
+
@original_logger.error(message)
|
|
61
|
+
emit_to_otel(::Logger::ERROR, message) if message
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fatal(message = nil)
|
|
65
|
+
message = yield if block_given?
|
|
66
|
+
@original_logger.fatal(message)
|
|
67
|
+
emit_to_otel(::Logger::FATAL, message) if message
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def emit_to_otel(severity, message)
|
|
73
|
+
return unless otel_configured?
|
|
74
|
+
|
|
75
|
+
attributes = {
|
|
76
|
+
"log.source" => "rails",
|
|
77
|
+
"service.name" => OtelBeacon.config.service_name
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
current_span = OpenTelemetry::Trace.current_span
|
|
81
|
+
if current_span.context.valid?
|
|
82
|
+
attributes["trace_id"] = current_span.context.hex_trace_id
|
|
83
|
+
attributes["span_id"] = current_span.context.hex_span_id
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
otel_logger.on_emit(
|
|
87
|
+
severity_text: SEVERITY_MAP[severity] || "INFO",
|
|
88
|
+
severity_number: severity_to_number(severity),
|
|
89
|
+
body: message.to_s,
|
|
90
|
+
attributes: attributes,
|
|
91
|
+
context: OpenTelemetry::Context.current
|
|
92
|
+
)
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
@original_logger&.debug("OtelBeacon log emit failed: #{e.message}")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def otel_configured?
|
|
98
|
+
OpenTelemetry.logger_provider&.respond_to?(:logger)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def otel_logger
|
|
102
|
+
@otel_logger ||= OpenTelemetry.logger_provider.logger(
|
|
103
|
+
name: OtelBeacon.config.service_name,
|
|
104
|
+
version: OtelBeacon::VERSION
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def severity_to_number(severity)
|
|
109
|
+
case severity
|
|
110
|
+
when ::Logger::DEBUG then 5
|
|
111
|
+
when ::Logger::INFO then 9
|
|
112
|
+
when ::Logger::WARN then 13
|
|
113
|
+
when ::Logger::ERROR then 17
|
|
114
|
+
when ::Logger::FATAL then 21
|
|
115
|
+
else 0
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Rails
|
|
5
|
+
module MailerMethods
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
around_action :otel_beacon_capture_mailer
|
|
10
|
+
rescue_from StandardError, with: :otel_beacon_mailer_exception
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def otel_beacon_capture_mailer
|
|
16
|
+
OtelBeacon.reset_context!
|
|
17
|
+
OtelBeacon.set_tags(
|
|
18
|
+
mailer_class: self.class.name,
|
|
19
|
+
mailer_action: action_name
|
|
20
|
+
)
|
|
21
|
+
OtelBeacon.add_breadcrumb(:mailer, "#{self.class.name}##{action_name}")
|
|
22
|
+
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
OtelBeacon.flush_context_to_span
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def otel_beacon_mailer_exception(exception)
|
|
29
|
+
OtelBeacon.capture_exception(exception, extra: {
|
|
30
|
+
mailer_class: self.class.name,
|
|
31
|
+
mailer_action: action_name
|
|
32
|
+
})
|
|
33
|
+
raise exception
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Rails
|
|
5
|
+
class Railtie < ::Rails::Railtie
|
|
6
|
+
config.otel_beacon = ActiveSupport::OrderedOptions.new
|
|
7
|
+
|
|
8
|
+
initializer "otel_beacon.config", before: :run_prepare_callbacks do |app|
|
|
9
|
+
app.config.otel_beacon.each do |key, value|
|
|
10
|
+
OtelBeacon.public_send("#{key}=", value)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
initializer "otel_beacon.app_executor", before: :run_prepare_callbacks do |_app|
|
|
15
|
+
OtelBeacon.on_thread_error = ->(exception) { ::Rails.error.report(exception, handled: false) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "otel_beacon.logger", before: :run_prepare_callbacks do
|
|
19
|
+
ActiveSupport.on_load(:otel_beacon) do
|
|
20
|
+
self.logger = ::Rails.logger
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
OtelBeacon::LogSubscriber.attach_to :otel_beacon
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
initializer "otel_beacon.environment", before: :run_prepare_callbacks do
|
|
27
|
+
OtelBeacon.environment = ::Rails.env.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
initializer "otel_beacon.integrations", after: :load_config_initializers do
|
|
31
|
+
OtelBeacon.set_runtime_context
|
|
32
|
+
|
|
33
|
+
ActiveSupport.on_load(:action_controller) do
|
|
34
|
+
include OtelBeacon::Rails::ControllerMethods
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ActiveSupport.on_load(:active_job) do
|
|
38
|
+
include OtelBeacon::Rails::JobMethods
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
42
|
+
include OtelBeacon::Rails::MailerMethods
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if defined?(::Sidekiq)
|
|
46
|
+
require_relative "../integrations/sidekiq"
|
|
47
|
+
OtelBeacon::Integrations::Sidekiq.setup
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module RuntimeContext
|
|
5
|
+
class << self
|
|
6
|
+
def set_defaults
|
|
7
|
+
set_runtime_context
|
|
8
|
+
set_os_context
|
|
9
|
+
set_app_context
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def set_runtime_context
|
|
13
|
+
OtelBeacon.set_context(:runtime,
|
|
14
|
+
name: "ruby",
|
|
15
|
+
version: RUBY_VERSION,
|
|
16
|
+
platform: RUBY_PLATFORM,
|
|
17
|
+
engine: RUBY_ENGINE,
|
|
18
|
+
engine_version: RUBY_ENGINE_VERSION)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_os_context
|
|
22
|
+
OtelBeacon.set_context(:os,
|
|
23
|
+
name: os_name,
|
|
24
|
+
version: os_version,
|
|
25
|
+
kernel_version: `uname -r`.strip)
|
|
26
|
+
rescue StandardError
|
|
27
|
+
OtelBeacon.set_context(:os, name: RUBY_PLATFORM)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def set_app_context
|
|
31
|
+
ctx = {
|
|
32
|
+
name: OtelBeacon.config.service_name,
|
|
33
|
+
version: OtelBeacon.config.service_version,
|
|
34
|
+
environment: OtelBeacon.config.environment.to_s
|
|
35
|
+
}
|
|
36
|
+
ctx[:rails_version] = ::Rails.version if defined?(::Rails)
|
|
37
|
+
OtelBeacon.set_context(:app, **ctx)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_device_context(request)
|
|
41
|
+
return unless request
|
|
42
|
+
|
|
43
|
+
OtelBeacon.set_context(:device,
|
|
44
|
+
user_agent: request.user_agent,
|
|
45
|
+
ip_address: request.remote_ip)
|
|
46
|
+
rescue StandardError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_browser_context(request)
|
|
50
|
+
return unless request
|
|
51
|
+
|
|
52
|
+
ua = request.user_agent.to_s
|
|
53
|
+
OtelBeacon.set_context(:browser,
|
|
54
|
+
user_agent: ua,
|
|
55
|
+
name: parse_browser_name(ua),
|
|
56
|
+
version: parse_browser_version(ua))
|
|
57
|
+
rescue StandardError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def os_name
|
|
63
|
+
case RUBY_PLATFORM
|
|
64
|
+
when /darwin/ then "macOS"
|
|
65
|
+
when /linux/ then "Linux"
|
|
66
|
+
when /win|mingw/ then "Windows"
|
|
67
|
+
when /freebsd/ then "FreeBSD"
|
|
68
|
+
else RUBY_PLATFORM
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def os_version
|
|
73
|
+
case RUBY_PLATFORM
|
|
74
|
+
when /darwin/
|
|
75
|
+
`sw_vers -productVersion`.strip
|
|
76
|
+
when /linux/
|
|
77
|
+
File.read("/etc/os-release").match(/VERSION_ID="?([^"\n]+)"?/)&.[](1) || "unknown"
|
|
78
|
+
else
|
|
79
|
+
"unknown"
|
|
80
|
+
end
|
|
81
|
+
rescue StandardError
|
|
82
|
+
"unknown"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_browser_name(ua)
|
|
86
|
+
case ua
|
|
87
|
+
when /Chrome/i then "Chrome"
|
|
88
|
+
when /Firefox/i then "Firefox"
|
|
89
|
+
when /Safari/i then "Safari"
|
|
90
|
+
when /Edge/i then "Edge"
|
|
91
|
+
when /MSIE|Trident/i then "Internet Explorer"
|
|
92
|
+
else "Unknown"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_browser_version(ua)
|
|
97
|
+
match = ua.match(%r{(?:Chrome|Firefox|Safari|Edge|MSIE|rv:)[/ ]?([\d.]+)}i)
|
|
98
|
+
match ? match[1] : "unknown"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
module Sanitizer
|
|
5
|
+
class << self
|
|
6
|
+
def sanitize_params(params, depth = 0)
|
|
7
|
+
return {} if params.nil?
|
|
8
|
+
return "[TRUNCATED]" if depth > 5
|
|
9
|
+
|
|
10
|
+
case params
|
|
11
|
+
when Hash
|
|
12
|
+
params.each_with_object({}) do |(k, v), h|
|
|
13
|
+
h[k] = sensitive_field?(k) ? "[FILTERED]" : sanitize_params(v, depth + 1)
|
|
14
|
+
end
|
|
15
|
+
when Array
|
|
16
|
+
params.first(100).map { |v| sanitize_params(v, depth + 1) }
|
|
17
|
+
when String
|
|
18
|
+
params.length > 1000 ? "#{params[0..1000]}...[TRUNCATED]" : params
|
|
19
|
+
else
|
|
20
|
+
params
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def extract_params(params)
|
|
25
|
+
raw = begin
|
|
26
|
+
params.to_unsafe_h
|
|
27
|
+
rescue StandardError
|
|
28
|
+
begin
|
|
29
|
+
params.to_h
|
|
30
|
+
rescue StandardError
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
sanitize_params(raw)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def truncate_string(string, max_length: 500)
|
|
38
|
+
return string if string.length <= max_length
|
|
39
|
+
|
|
40
|
+
"#{string[0...max_length]}..."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def sensitive_field?(key)
|
|
46
|
+
key.to_s.downcase.match?(OtelBeacon.config.sanitize_pattern)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OtelBeacon
|
|
4
|
+
class Scope
|
|
5
|
+
def set_user(id: nil, email: nil, username: nil, ip_address: nil, **extra)
|
|
6
|
+
Context.current[:user] = { id: id, email: email, username: username, ip_address: ip_address }.compact.merge(extra)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def set_tags(**tags)
|
|
10
|
+
Context.tags.merge!(tags.transform_values(&:to_s))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def set_tag(key, value)
|
|
14
|
+
Context.tags[key.to_sym] = value.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def set_extra(**extra)
|
|
18
|
+
Context.extra.merge!(extra)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_context(name, **data)
|
|
22
|
+
Context.contexts[name.to_sym] = data
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_fingerprint(*fingerprint)
|
|
26
|
+
Context.fingerprint = fingerprint.flatten
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_breadcrumb(category, message, level: :info, **data)
|
|
30
|
+
Client.add_breadcrumb(category, message, level: level, **data)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def clear
|
|
34
|
+
Context.reset!
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|