obtrace-sdk-ruby 1.0.1 → 1.0.2
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 +4 -4
- data/lib/obtrace_sdk/client.rb +170 -97
- data/lib/obtrace_sdk/context.rb +18 -0
- data/lib/obtrace_sdk/framework.rb +15 -0
- data/lib/obtrace_sdk/http_instrumentation.rb +122 -0
- data/lib/obtrace_sdk/logger_capture.rb +68 -0
- data/lib/obtrace_sdk/middleware.rb +64 -38
- data/lib/obtrace_sdk/otlp.rb +127 -0
- data/lib/obtrace_sdk/rails.rb +8 -8
- data/lib/obtrace_sdk/types.rb +6 -2
- data/lib/obtrace_sdk/version.rb +1 -1
- data/lib/obtrace_sdk.rb +7 -4
- metadata +11 -63
- data/lib/obtrace_sdk/otel_setup.rb +0 -71
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7468e55374f6c051c0def7f03f87796ebab9d4d69ff05132965e66b57bdbadb
|
|
4
|
+
data.tar.gz: a1d5629df790ebe12837155bb1bf94a9c61edd4ac737450ca4a3a24466dc144f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77d495287ed0a0f010b57c08e860cec323a138f8a7c23e46051a9bd23abc8b8cc5c4dd6d717c08fefb8aac49d6285d57329846c830bd5a30dcaf3b4cb84bfbbe
|
|
7
|
+
data.tar.gz: 49fb1d19befa93b2d97485d70d1bf3106e64d340c3e26eb96f7c035e63adfbdff963da1b05787e2b06caedde5f8c7d61ab23fb4282378c29e3766b4ef12b6312
|
data/lib/obtrace_sdk/client.rb
CHANGED
|
@@ -1,135 +1,208 @@
|
|
|
1
|
-
require "net/http"
|
|
2
1
|
require "json"
|
|
2
|
+
require "net/http"
|
|
3
3
|
require "uri"
|
|
4
|
-
|
|
4
|
+
require "thread"
|
|
5
|
+
require_relative "context"
|
|
6
|
+
require_relative "otlp"
|
|
7
|
+
require_relative "http_instrumentation"
|
|
8
|
+
require_relative "logger_capture"
|
|
5
9
|
|
|
6
10
|
module ObtraceSDK
|
|
7
11
|
class Client
|
|
8
|
-
@@initialized = false
|
|
9
|
-
|
|
10
|
-
attr_reader :tracer, :meter, :handshake_ok
|
|
11
|
-
|
|
12
12
|
def initialize(cfg)
|
|
13
|
-
if @@initialized
|
|
14
|
-
warn("[obtrace-sdk-ruby] already initialized, skipping duplicate init")
|
|
15
|
-
return
|
|
16
|
-
end
|
|
17
|
-
|
|
18
13
|
raise ArgumentError, "api_key, ingest_base_url and service_name are required" if cfg.api_key.to_s.empty? || cfg.ingest_base_url.to_s.empty? || cfg.service_name.to_s.empty?
|
|
19
14
|
|
|
20
|
-
@@initialized = true
|
|
21
15
|
@cfg = cfg
|
|
22
|
-
@
|
|
23
|
-
@
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
@
|
|
16
|
+
@queue = []
|
|
17
|
+
@lock = Mutex.new
|
|
18
|
+
@http = nil
|
|
19
|
+
@http_uri = nil
|
|
20
|
+
@circuit_failures = 0
|
|
21
|
+
@circuit_open_until = Time.at(0)
|
|
22
|
+
@seen_exceptions = {}
|
|
23
|
+
@seen_lock = Mutex.new
|
|
24
|
+
|
|
25
|
+
install_exception_tracepoint
|
|
26
|
+
HttpInstrumentation.install(self) if @cfg.auto_instrument_http
|
|
27
|
+
LoggerCapture.install(self) if @cfg.auto_capture_logs
|
|
28
|
+
at_exit { capture_fatal; shutdown }
|
|
29
|
+
end
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
31
|
+
def log(level, message, context = nil)
|
|
32
|
+
enqueue("/otlp/v1/logs", Otlp.logs_payload(@cfg, level, truncate(message, 32768), context))
|
|
33
|
+
end
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
def metric(name, value, unit = "1", context = nil)
|
|
36
|
+
warn("[obtrace-sdk-ruby] non-canonical metric name: #{name}") if @cfg.validate_semantic_metrics && @cfg.debug && !SemanticMetrics.semantic_metric?(name)
|
|
37
|
+
enqueue("/otlp/v1/metrics", Otlp.metric_payload(@cfg, truncate(name, 1024), value, unit, context))
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
runtime: "ruby",
|
|
47
|
-
runtime_version: RUBY_VERSION,
|
|
48
|
-
})
|
|
49
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
50
|
-
http.use_ssl = uri.scheme == "https"
|
|
51
|
-
http.open_timeout = 5
|
|
52
|
-
http.read_timeout = 5
|
|
53
|
-
req = Net::HTTP::Post.new(uri)
|
|
54
|
-
req["Content-Type"] = "application/json"
|
|
55
|
-
req["Authorization"] = "Bearer #{@cfg.api_key}"
|
|
56
|
-
req.body = payload
|
|
57
|
-
resp = http.request(req)
|
|
58
|
-
if resp.code.to_i == 200
|
|
59
|
-
@handshake_ok = true
|
|
60
|
-
warn("[obtrace-sdk-ruby] init handshake OK") if @cfg.debug
|
|
61
|
-
elsif @cfg.debug
|
|
62
|
-
warn("[obtrace-sdk-ruby] init handshake failed: #{resp.code}")
|
|
40
|
+
def span(name, trace_id: nil, span_id: nil, start_unix_nano: nil, end_unix_nano: nil, status_code: nil, status_message: "", attrs: nil)
|
|
41
|
+
trace_id ||= Context.random_hex(16)
|
|
42
|
+
span_id ||= Context.random_hex(8)
|
|
43
|
+
start_ns = start_unix_nano || Otlp.now_unix_nano
|
|
44
|
+
end_ns = end_unix_nano || Otlp.now_unix_nano
|
|
45
|
+
|
|
46
|
+
name = truncate(name, 32768)
|
|
47
|
+
if attrs
|
|
48
|
+
attrs = attrs.transform_values { |v| v.is_a?(String) ? truncate(v, 4096) : v }
|
|
63
49
|
end
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
|
|
51
|
+
enqueue("/otlp/v1/traces", Otlp.span_payload(@cfg, name, trace_id, span_id, start_ns, end_ns, status_code, status_message, attrs))
|
|
52
|
+
{ trace_id: trace_id, span_id: span_id }
|
|
66
53
|
end
|
|
67
54
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
def inject_propagation(headers = {}, trace_id: nil, span_id: nil, session_id: nil)
|
|
56
|
+
Context.ensure_propagation_headers(headers, trace_id: trace_id, span_id: span_id, session_id: session_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def flush
|
|
60
|
+
batch = []
|
|
61
|
+
@lock.synchronize do
|
|
62
|
+
return if Time.now < @circuit_open_until
|
|
63
|
+
half_open = @circuit_failures >= 5
|
|
64
|
+
if half_open
|
|
65
|
+
return if @queue.empty?
|
|
66
|
+
batch = [@queue.shift]
|
|
67
|
+
else
|
|
68
|
+
batch = @queue.dup
|
|
69
|
+
@queue.clear
|
|
70
|
+
end
|
|
72
71
|
end
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
batch.each do |item|
|
|
73
|
+
success = send_item(item)
|
|
74
|
+
@lock.synchronize do
|
|
75
|
+
if success
|
|
76
|
+
if @circuit_failures > 0
|
|
77
|
+
warn("[obtrace-sdk-ruby] circuit breaker closed") if @cfg.debug
|
|
78
|
+
@circuit_failures = 0
|
|
79
|
+
@circuit_open_until = Time.at(0)
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
@circuit_failures += 1
|
|
83
|
+
if @circuit_failures >= 5
|
|
84
|
+
@circuit_open_until = Time.now + 30
|
|
85
|
+
warn("[obtrace-sdk-ruby] circuit breaker opened") if @cfg.debug
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
81
89
|
end
|
|
82
90
|
end
|
|
83
91
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@
|
|
92
|
+
def shutdown
|
|
93
|
+
@tracepoint&.disable
|
|
94
|
+
flush
|
|
95
|
+
@lock.synchronize do
|
|
96
|
+
if @http
|
|
97
|
+
@http.finish rescue nil
|
|
98
|
+
@http = nil
|
|
99
|
+
@http_uri = nil
|
|
91
100
|
end
|
|
92
|
-
return
|
|
93
101
|
end
|
|
102
|
+
end
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def install_exception_tracepoint
|
|
107
|
+
client = self
|
|
108
|
+
@tracepoint = TracePoint.new(:raise) do |tp|
|
|
109
|
+
ex = tp.raised_exception
|
|
110
|
+
eid = ex.object_id
|
|
111
|
+
seen = client.instance_variable_get(:@seen_lock).synchronize do
|
|
112
|
+
cache = client.instance_variable_get(:@seen_exceptions)
|
|
113
|
+
next true if cache[eid]
|
|
114
|
+
cache[eid] = true
|
|
115
|
+
cache.shift if cache.size > 200
|
|
116
|
+
false
|
|
117
|
+
end
|
|
118
|
+
unless seen
|
|
119
|
+
bt = (ex.backtrace || []).first(10).join("\n")
|
|
120
|
+
client.log("error", "#{ex.class}: #{ex.message}", {
|
|
121
|
+
"exception.type" => ex.class.to_s,
|
|
122
|
+
"exception.message" => ex.message.to_s,
|
|
123
|
+
"exception.stacktrace" => bt,
|
|
124
|
+
"code.filepath" => tp.path.to_s,
|
|
125
|
+
"code.lineno" => tp.lineno.to_s,
|
|
126
|
+
"auto.source" => "tracepoint"
|
|
127
|
+
})
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
@tracepoint.enable
|
|
131
|
+
end
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
def capture_fatal
|
|
134
|
+
return unless $!
|
|
135
|
+
ex = $!
|
|
136
|
+
bt = (ex.backtrace || []).first(10).join("\n")
|
|
137
|
+
log("fatal", "#{ex.class}: #{ex.message}", {
|
|
138
|
+
"exception.type" => ex.class.to_s,
|
|
139
|
+
"exception.message" => ex.message.to_s,
|
|
140
|
+
"exception.stacktrace" => bt,
|
|
141
|
+
"auto.source" => "at_exit"
|
|
142
|
+
})
|
|
100
143
|
end
|
|
101
144
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end
|
|
145
|
+
def truncate(s, max)
|
|
146
|
+
return s if s.length <= max
|
|
147
|
+
s[0, max] + "...[truncated]"
|
|
148
|
+
end
|
|
107
149
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
150
|
+
def enqueue(endpoint, payload)
|
|
151
|
+
@lock.synchronize do
|
|
152
|
+
if @queue.length >= @cfg.max_queue_size
|
|
153
|
+
@queue.shift
|
|
154
|
+
warn("[obtrace-sdk-ruby] queue full, dropping oldest item") if @cfg.debug
|
|
111
155
|
end
|
|
112
|
-
|
|
113
|
-
@tracer.in_span(name.to_s, attributes: span_attrs) { |_s| }
|
|
156
|
+
@queue << { endpoint: endpoint, payload: payload.dup.freeze }
|
|
114
157
|
end
|
|
115
158
|
end
|
|
116
159
|
|
|
117
|
-
def
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
@tracer.in_span("error", attributes: attrs) do |s|
|
|
124
|
-
s.record_exception(exception)
|
|
125
|
-
s.status = OpenTelemetry::Trace::Status.error(exception.message.to_s)
|
|
160
|
+
def connection_for(uri)
|
|
161
|
+
if @http && @http_uri && @http_uri.host == uri.host && @http_uri.port == uri.port
|
|
162
|
+
begin
|
|
163
|
+
return @http if @http.started?
|
|
164
|
+
rescue StandardError
|
|
165
|
+
end
|
|
126
166
|
end
|
|
167
|
+
|
|
168
|
+
@http.finish rescue nil if @http
|
|
169
|
+
@http = Net::HTTP.new(uri.host, uri.port)
|
|
170
|
+
@http.use_ssl = uri.scheme == "https"
|
|
171
|
+
@http.read_timeout = @cfg.request_timeout_sec
|
|
172
|
+
@http.start
|
|
173
|
+
@http_uri = uri
|
|
174
|
+
@http
|
|
127
175
|
end
|
|
128
176
|
|
|
129
|
-
|
|
177
|
+
def send_item(item)
|
|
178
|
+
uri = URI.parse("#{@cfg.ingest_base_url.to_s.sub(%r{/$}, "")}#{item[:endpoint]}")
|
|
179
|
+
http = connection_for(uri)
|
|
180
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
|
181
|
+
req["Authorization"] = "Bearer #{@cfg.api_key}"
|
|
182
|
+
req["Content-Type"] = "application/json"
|
|
183
|
+
(@cfg.default_headers || {}).each { |k, v| req[k] = v.to_s }
|
|
184
|
+
req.body = JSON.generate(item[:payload])
|
|
130
185
|
|
|
131
|
-
|
|
132
|
-
|
|
186
|
+
retries = 0
|
|
187
|
+
begin
|
|
188
|
+
res = http.request(req)
|
|
189
|
+
if res.code.to_i >= 300
|
|
190
|
+
warn("[obtrace-sdk-ruby] status=#{res.code} endpoint=#{item[:endpoint]} body=#{res.body}") if @cfg.debug
|
|
191
|
+
return false
|
|
192
|
+
end
|
|
193
|
+
true
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
if retries < 2
|
|
196
|
+
retries += 1
|
|
197
|
+
sleep 1
|
|
198
|
+
@http.finish rescue nil
|
|
199
|
+
@http = nil
|
|
200
|
+
http = connection_for(uri)
|
|
201
|
+
retry
|
|
202
|
+
end
|
|
203
|
+
warn("[obtrace-sdk-ruby] send failed endpoint=#{item[:endpoint]} err=#{e.message}") if @cfg.debug
|
|
204
|
+
false
|
|
205
|
+
end
|
|
133
206
|
end
|
|
134
207
|
end
|
|
135
208
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module ObtraceSDK
|
|
4
|
+
module Context
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def random_hex(bytes)
|
|
8
|
+
SecureRandom.hex(bytes)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ensure_propagation_headers(headers = {}, trace_id: nil, span_id: nil, session_id: nil)
|
|
12
|
+
out = (headers || {}).dup
|
|
13
|
+
out["traceparent"] ||= "00-#{trace_id || random_hex(16)}-#{span_id || random_hex(8)}-01"
|
|
14
|
+
out["x-obtrace-session-id"] ||= session_id if session_id && !session_id.empty?
|
|
15
|
+
out
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ObtraceSDK
|
|
2
|
+
module Framework
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# Rack-compatible middleware baseline used by Rails.
|
|
6
|
+
def rack_middleware(client, app)
|
|
7
|
+
lambda do |env|
|
|
8
|
+
client.log("info", "request.start", { method: env["REQUEST_METHOD"], path: env["PATH_INFO"] })
|
|
9
|
+
status, headers, body = app.call(env)
|
|
10
|
+
client.log("info", "request.finish", { status: status.to_i })
|
|
11
|
+
[status, headers, body]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
module ObtraceSDK
|
|
4
|
+
module HttpInstrumentation
|
|
5
|
+
@client = nil
|
|
6
|
+
@installed = false
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def install(client)
|
|
11
|
+
return if @installed
|
|
12
|
+
@client = client
|
|
13
|
+
@installed = true
|
|
14
|
+
|
|
15
|
+
Net::HTTP.prepend(NetHttpPatch)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def client
|
|
19
|
+
@client
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def installed?
|
|
23
|
+
@installed
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reset!
|
|
27
|
+
@client = nil
|
|
28
|
+
@installed = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module NetHttpPatch
|
|
32
|
+
def request(req, body = nil, &block)
|
|
33
|
+
client = ObtraceSDK::HttpInstrumentation.client
|
|
34
|
+
unless client
|
|
35
|
+
return super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
uri = URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}:#{port}#{req.path}")
|
|
39
|
+
|
|
40
|
+
if uri.host && ObtraceSDK::HttpInstrumentation.own_endpoint?(uri)
|
|
41
|
+
return super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
trace_id = ObtraceSDK::Context.random_hex(16)
|
|
45
|
+
span_id = ObtraceSDK::Context.random_hex(8)
|
|
46
|
+
|
|
47
|
+
req["traceparent"] ||= "00-#{trace_id}-#{span_id}-01"
|
|
48
|
+
|
|
49
|
+
start_ns = ObtraceSDK::Otlp.now_unix_nano
|
|
50
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
51
|
+
status_code = nil
|
|
52
|
+
error = nil
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
response = super
|
|
56
|
+
status_code = response.code.to_i
|
|
57
|
+
response
|
|
58
|
+
rescue => e
|
|
59
|
+
error = e
|
|
60
|
+
raise
|
|
61
|
+
ensure
|
|
62
|
+
end_ns = ObtraceSDK::Otlp.now_unix_nano
|
|
63
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
|
|
64
|
+
|
|
65
|
+
attrs = {
|
|
66
|
+
"http.method" => req.method,
|
|
67
|
+
"http.url" => uri.to_s,
|
|
68
|
+
"http.host" => uri.host.to_s,
|
|
69
|
+
"net.peer.port" => uri.port.to_s,
|
|
70
|
+
"http.duration_ms" => duration_ms,
|
|
71
|
+
"auto.source" => "http_instrumentation"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if status_code
|
|
75
|
+
attrs["http.status_code"] = status_code
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if error
|
|
79
|
+
attrs["error"] = true
|
|
80
|
+
attrs["error.type"] = error.class.to_s
|
|
81
|
+
attrs["error.message"] = error.message.to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
span_status = if error
|
|
85
|
+
status_code = 500
|
|
86
|
+
500
|
|
87
|
+
elsif status_code && status_code >= 400
|
|
88
|
+
status_code
|
|
89
|
+
else
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
client.span(
|
|
94
|
+
"HTTP #{req.method}",
|
|
95
|
+
trace_id: trace_id,
|
|
96
|
+
span_id: span_id,
|
|
97
|
+
start_unix_nano: start_ns,
|
|
98
|
+
end_unix_nano: end_ns,
|
|
99
|
+
status_code: span_status,
|
|
100
|
+
status_message: error ? error.message : "",
|
|
101
|
+
attrs: attrs
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
client.log(
|
|
105
|
+
status_code && status_code >= 400 ? "warn" : "info",
|
|
106
|
+
"HTTP #{req.method} #{uri.host}#{uri.path} #{status_code || 'ERR'}",
|
|
107
|
+
attrs
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.own_endpoint?(uri)
|
|
114
|
+
return false unless @client
|
|
115
|
+
cfg = @client.instance_variable_get(:@cfg)
|
|
116
|
+
return false unless cfg
|
|
117
|
+
ingest_uri = URI.parse(cfg.ingest_base_url.to_s) rescue nil
|
|
118
|
+
return false unless ingest_uri
|
|
119
|
+
uri.host == ingest_uri.host && uri.port == ingest_uri.port
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
|
|
3
|
+
module ObtraceSDK
|
|
4
|
+
module LoggerCapture
|
|
5
|
+
@client = nil
|
|
6
|
+
@installed = false
|
|
7
|
+
|
|
8
|
+
SEVERITY_MAP = {
|
|
9
|
+
0 => "debug",
|
|
10
|
+
1 => "info",
|
|
11
|
+
2 => "warn",
|
|
12
|
+
3 => "error",
|
|
13
|
+
4 => "fatal",
|
|
14
|
+
5 => "unknown"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def install(client)
|
|
20
|
+
return if @installed
|
|
21
|
+
@client = client
|
|
22
|
+
@installed = true
|
|
23
|
+
|
|
24
|
+
::Logger.prepend(LoggerPatch)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def client
|
|
28
|
+
@client
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def installed?
|
|
32
|
+
@installed
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reset!
|
|
36
|
+
@client = nil
|
|
37
|
+
@installed = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module LoggerPatch
|
|
41
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
42
|
+
result = super
|
|
43
|
+
|
|
44
|
+
client = ObtraceSDK::LoggerCapture.client
|
|
45
|
+
if client
|
|
46
|
+
if message.nil?
|
|
47
|
+
if block
|
|
48
|
+
msg = block.call
|
|
49
|
+
else
|
|
50
|
+
msg = progname
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
msg = message
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if msg && !msg.to_s.empty?
|
|
57
|
+
level = ObtraceSDK::LoggerCapture::SEVERITY_MAP[severity] || "unknown"
|
|
58
|
+
attrs = { "auto.source" => "logger_capture" }
|
|
59
|
+
attrs["logger.progname"] = progname.to_s if progname && message
|
|
60
|
+
client.log(level, msg.to_s, attrs)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -3,58 +3,84 @@ module ObtraceSDK
|
|
|
3
3
|
def initialize(app, client)
|
|
4
4
|
@app = app
|
|
5
5
|
@client = client
|
|
6
|
-
@tracer = client.tracer
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
def call(env)
|
|
10
|
-
|
|
9
|
+
trace_id = extract_trace_id(env) || Context.random_hex(16)
|
|
10
|
+
span_id = Context.random_hex(8)
|
|
11
|
+
start_ns = Otlp.now_unix_nano
|
|
12
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
status = nil
|
|
15
|
+
error = nil
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
begin
|
|
18
|
+
status, headers, body = @app.call(env)
|
|
19
|
+
[status, headers, body]
|
|
20
|
+
rescue => e
|
|
21
|
+
error = e
|
|
22
|
+
raise
|
|
23
|
+
ensure
|
|
24
|
+
end_ns = Otlp.now_unix_nano
|
|
25
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
|
|
26
|
+
status_code = error ? 500 : status.to_i
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
attrs = {
|
|
29
|
+
"http.method" => env["REQUEST_METHOD"],
|
|
30
|
+
"http.target" => env["PATH_INFO"],
|
|
31
|
+
"http.host" => env["HTTP_HOST"].to_s,
|
|
32
|
+
"http.scheme" => env["rack.url_scheme"].to_s,
|
|
33
|
+
"http.status_code" => status_code,
|
|
34
|
+
"http.duration_ms" => duration_ms,
|
|
35
|
+
"http.user_agent" => env["HTTP_USER_AGENT"].to_s,
|
|
36
|
+
"net.peer.ip" => (env["HTTP_X_FORWARDED_FOR"] || env["REMOTE_ADDR"]).to_s
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if error
|
|
40
|
+
attrs["error"] = true
|
|
41
|
+
attrs["error.type"] = error.class.to_s
|
|
42
|
+
attrs["error.message"] = error.message.to_s
|
|
43
|
+
end
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
begin
|
|
30
|
-
status, headers, body = @app.call(env)
|
|
31
|
-
s.set_attribute("http.status_code", status.to_i)
|
|
32
|
-
if status.to_i >= 500
|
|
33
|
-
s.status = OpenTelemetry::Trace::Status.error("HTTP #{status}")
|
|
34
|
-
end
|
|
35
|
-
[status, headers, body]
|
|
36
|
-
rescue => e
|
|
37
|
-
s.record_exception(e)
|
|
38
|
-
s.status = OpenTelemetry::Trace::Status.error(e.message.to_s)
|
|
39
|
-
s.set_attribute("http.status_code", 500)
|
|
40
|
-
raise
|
|
41
|
-
end
|
|
45
|
+
if env["QUERY_STRING"] && !env["QUERY_STRING"].empty?
|
|
46
|
+
attrs["http.query"] = env["QUERY_STRING"]
|
|
42
47
|
end
|
|
48
|
+
|
|
49
|
+
@client.span(
|
|
50
|
+
"#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}",
|
|
51
|
+
trace_id: trace_id,
|
|
52
|
+
span_id: span_id,
|
|
53
|
+
start_unix_nano: start_ns,
|
|
54
|
+
end_unix_nano: end_ns,
|
|
55
|
+
status_code: status_code >= 400 ? status_code : nil,
|
|
56
|
+
status_message: error ? error.message : "",
|
|
57
|
+
attrs: attrs
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
level = if status_code >= 500
|
|
61
|
+
"error"
|
|
62
|
+
elsif status_code >= 400
|
|
63
|
+
"warn"
|
|
64
|
+
else
|
|
65
|
+
"info"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@client.log(
|
|
69
|
+
level,
|
|
70
|
+
"#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]} #{status_code} #{duration_ms}ms",
|
|
71
|
+
attrs
|
|
72
|
+
)
|
|
43
73
|
end
|
|
44
74
|
end
|
|
45
75
|
|
|
46
76
|
private
|
|
47
77
|
|
|
48
|
-
def
|
|
78
|
+
def extract_trace_id(env)
|
|
49
79
|
traceparent = env["HTTP_TRACEPARENT"]
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
getter: OpenTelemetry::Common::Propagation.rack_env_getter
|
|
55
|
-
)
|
|
56
|
-
rescue
|
|
57
|
-
OpenTelemetry::Context.current
|
|
80
|
+
return nil unless traceparent
|
|
81
|
+
parts = traceparent.split("-")
|
|
82
|
+
return nil unless parts.length >= 3
|
|
83
|
+
parts[1]
|
|
58
84
|
end
|
|
59
85
|
end
|
|
60
86
|
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module ObtraceSDK
|
|
2
|
+
module Otlp
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def attrs(hash)
|
|
6
|
+
return [] if hash.nil?
|
|
7
|
+
hash.map do |k, v|
|
|
8
|
+
value =
|
|
9
|
+
case v
|
|
10
|
+
when TrueClass, FalseClass
|
|
11
|
+
{ "boolValue" => v }
|
|
12
|
+
when Numeric
|
|
13
|
+
{ "doubleValue" => v.to_f }
|
|
14
|
+
else
|
|
15
|
+
{ "stringValue" => v.to_s }
|
|
16
|
+
end
|
|
17
|
+
{ "key" => k.to_s, "value" => value }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resource(cfg)
|
|
22
|
+
base = {
|
|
23
|
+
"service.name" => cfg.service_name,
|
|
24
|
+
"service.version" => cfg.service_version,
|
|
25
|
+
"deployment.environment" => cfg.env || "dev",
|
|
26
|
+
"runtime.name" => "ruby"
|
|
27
|
+
}
|
|
28
|
+
base["obtrace.tenant_id"] = cfg.tenant_id if cfg.tenant_id
|
|
29
|
+
base["obtrace.project_id"] = cfg.project_id if cfg.project_id
|
|
30
|
+
base["obtrace.app_id"] = cfg.app_id if cfg.app_id
|
|
31
|
+
base["obtrace.env"] = cfg.env if cfg.env
|
|
32
|
+
attrs(base)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def now_unix_nano
|
|
36
|
+
(Time.now.to_f * 1_000_000_000).to_i.to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def logs_payload(cfg, level, message, context = nil)
|
|
40
|
+
context_attrs = { "obtrace.log.level" => level }
|
|
41
|
+
if context
|
|
42
|
+
context.each { |k, v| context_attrs["obtrace.attr.#{k}"] = v }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
"resourceLogs" => [
|
|
47
|
+
{
|
|
48
|
+
"resource" => { "attributes" => resource(cfg) },
|
|
49
|
+
"scopeLogs" => [
|
|
50
|
+
{
|
|
51
|
+
"scope" => { "name" => "obtrace-sdk-ruby", "version" => "1.0.0" },
|
|
52
|
+
"logRecords" => [
|
|
53
|
+
{
|
|
54
|
+
"timeUnixNano" => now_unix_nano,
|
|
55
|
+
"severityText" => level.to_s.upcase,
|
|
56
|
+
"body" => { "stringValue" => message.to_s },
|
|
57
|
+
"attributes" => attrs(context_attrs)
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def metric_payload(cfg, name, value, unit = "1", context = nil)
|
|
68
|
+
{
|
|
69
|
+
"resourceMetrics" => [
|
|
70
|
+
{
|
|
71
|
+
"resource" => { "attributes" => resource(cfg) },
|
|
72
|
+
"scopeMetrics" => [
|
|
73
|
+
{
|
|
74
|
+
"scope" => { "name" => "obtrace-sdk-ruby", "version" => "1.0.0" },
|
|
75
|
+
"metrics" => [
|
|
76
|
+
{
|
|
77
|
+
"name" => name.to_s,
|
|
78
|
+
"unit" => unit.to_s,
|
|
79
|
+
"gauge" => {
|
|
80
|
+
"dataPoints" => [
|
|
81
|
+
{
|
|
82
|
+
"timeUnixNano" => now_unix_nano,
|
|
83
|
+
"asDouble" => value.to_f,
|
|
84
|
+
"attributes" => attrs(context || {})
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def span_payload(cfg, name, trace_id, span_id, start_unix_nano, end_unix_nano, status_code = nil, status_message = "", attrs_hash = nil)
|
|
98
|
+
{
|
|
99
|
+
"resourceSpans" => [
|
|
100
|
+
{
|
|
101
|
+
"resource" => { "attributes" => resource(cfg) },
|
|
102
|
+
"scopeSpans" => [
|
|
103
|
+
{
|
|
104
|
+
"scope" => { "name" => "obtrace-sdk-ruby", "version" => "1.0.0" },
|
|
105
|
+
"spans" => [
|
|
106
|
+
{
|
|
107
|
+
"traceId" => trace_id,
|
|
108
|
+
"spanId" => span_id,
|
|
109
|
+
"name" => name.to_s,
|
|
110
|
+
"kind" => 3,
|
|
111
|
+
"startTimeUnixNano" => start_unix_nano,
|
|
112
|
+
"endTimeUnixNano" => end_unix_nano,
|
|
113
|
+
"attributes" => attrs(attrs_hash || {}),
|
|
114
|
+
"status" => {
|
|
115
|
+
"code" => status_code && status_code.to_i >= 400 ? 2 : 1,
|
|
116
|
+
"message" => status_message.to_s
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/obtrace_sdk/rails.rb
CHANGED
|
@@ -2,10 +2,10 @@ require "obtrace_sdk"
|
|
|
2
2
|
|
|
3
3
|
module ObtraceSDK
|
|
4
4
|
class Railtie < ::Rails::Railtie
|
|
5
|
-
|
|
6
|
-
api_key =
|
|
7
|
-
ingest_url =
|
|
8
|
-
service_name =
|
|
5
|
+
initializer "obtrace_sdk.configure" do |app|
|
|
6
|
+
api_key = credentials_fetch("obtrace_api_key") || ENV["OBTRACE_API_KEY"]
|
|
7
|
+
ingest_url = credentials_fetch("obtrace_ingest_url") || ENV["OBTRACE_INGEST_BASE_URL"] || "https://inject.obtrace.ai"
|
|
8
|
+
service_name = credentials_fetch("obtrace_service_name") || ENV["OBTRACE_SERVICE_NAME"] || ::Rails.application.class.module_parent_name.underscore rescue "rails-app"
|
|
9
9
|
|
|
10
10
|
next unless api_key
|
|
11
11
|
|
|
@@ -14,9 +14,9 @@ module ObtraceSDK
|
|
|
14
14
|
ingest_base_url: ingest_url,
|
|
15
15
|
service_name: service_name,
|
|
16
16
|
env: ::Rails.env.to_s,
|
|
17
|
-
tenant_id:
|
|
18
|
-
project_id:
|
|
19
|
-
app_id:
|
|
17
|
+
tenant_id: credentials_fetch("obtrace_tenant_id") || ENV["OBTRACE_TENANT_ID"],
|
|
18
|
+
project_id: credentials_fetch("obtrace_project_id") || ENV["OBTRACE_PROJECT_ID"],
|
|
19
|
+
app_id: credentials_fetch("obtrace_app_id") || ENV["OBTRACE_APP_ID"],
|
|
20
20
|
debug: ENV["OBTRACE_DEBUG"] == "true"
|
|
21
21
|
)
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ module ObtraceSDK
|
|
|
26
26
|
app.middleware.insert(0, ObtraceSDK::Middleware, client)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def credentials_fetch(key)
|
|
30
30
|
::Rails.application.credentials.send(key) rescue nil
|
|
31
31
|
end
|
|
32
32
|
end
|
data/lib/obtrace_sdk/types.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
module ObtraceSDK
|
|
2
2
|
class Config
|
|
3
3
|
attr_accessor :api_key, :ingest_base_url, :tenant_id, :project_id, :app_id, :env
|
|
4
|
-
attr_accessor :service_name, :service_version, :request_timeout_sec
|
|
4
|
+
attr_accessor :service_name, :service_version, :max_queue_size, :request_timeout_sec
|
|
5
5
|
attr_accessor :default_headers, :debug, :validate_semantic_metrics
|
|
6
|
+
attr_accessor :auto_instrument_http, :auto_capture_logs
|
|
6
7
|
|
|
7
|
-
def initialize(api_key:, ingest_base_url:, service_name:, tenant_id: nil, project_id: nil, app_id: nil, env: "dev", service_version: "1.0.0", request_timeout_sec: 5, default_headers: {}, validate_semantic_metrics: false, debug: false)
|
|
8
|
+
def initialize(api_key:, ingest_base_url:, service_name:, tenant_id: nil, project_id: nil, app_id: nil, env: "dev", service_version: "1.0.0", max_queue_size: 1000, request_timeout_sec: 5, default_headers: {}, validate_semantic_metrics: false, debug: false, auto_instrument_http: true, auto_capture_logs: true)
|
|
8
9
|
@api_key = api_key
|
|
9
10
|
@ingest_base_url = ingest_base_url
|
|
10
11
|
@tenant_id = tenant_id
|
|
@@ -13,10 +14,13 @@ module ObtraceSDK
|
|
|
13
14
|
@env = env
|
|
14
15
|
@service_name = service_name
|
|
15
16
|
@service_version = service_version
|
|
17
|
+
@max_queue_size = max_queue_size
|
|
16
18
|
@request_timeout_sec = request_timeout_sec
|
|
17
19
|
@default_headers = default_headers
|
|
18
20
|
@validate_semantic_metrics = validate_semantic_metrics
|
|
19
21
|
@debug = debug
|
|
22
|
+
@auto_instrument_http = auto_instrument_http
|
|
23
|
+
@auto_capture_logs = auto_capture_logs
|
|
20
24
|
end
|
|
21
25
|
end
|
|
22
26
|
end
|
data/lib/obtrace_sdk/version.rb
CHANGED
data/lib/obtrace_sdk.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
require_relative "obtrace_sdk/version"
|
|
2
1
|
require_relative "obtrace_sdk/types"
|
|
3
|
-
require_relative "obtrace_sdk/
|
|
4
|
-
require_relative "obtrace_sdk/
|
|
5
|
-
require_relative "obtrace_sdk/
|
|
2
|
+
require_relative "obtrace_sdk/context"
|
|
3
|
+
require_relative "obtrace_sdk/otlp"
|
|
4
|
+
require_relative "obtrace_sdk/http_instrumentation"
|
|
5
|
+
require_relative "obtrace_sdk/logger_capture"
|
|
6
6
|
require_relative "obtrace_sdk/middleware"
|
|
7
|
+
require_relative "obtrace_sdk/client"
|
|
8
|
+
require_relative "obtrace_sdk/framework"
|
|
9
|
+
require_relative "obtrace_sdk/semantic_metrics"
|
metadata
CHANGED
|
@@ -1,79 +1,23 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: obtrace-sdk-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Obtrace
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.3'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.3'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: opentelemetry-api
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.3'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.3'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: opentelemetry-exporter-otlp
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0.26'
|
|
48
|
-
type: :runtime
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0.26'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: opentelemetry-instrumentation-net_http
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: opentelemetry-instrumentation-rack
|
|
14
|
+
name: net-http
|
|
71
15
|
requirement: !ruby/object:Gem::Requirement
|
|
72
16
|
requirements:
|
|
73
17
|
- - ">="
|
|
74
18
|
- !ruby/object:Gem::Version
|
|
75
19
|
version: '0'
|
|
76
|
-
type: :
|
|
20
|
+
type: :runtime
|
|
77
21
|
prerelease: false
|
|
78
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
23
|
requirements:
|
|
@@ -81,13 +25,13 @@ dependencies:
|
|
|
81
25
|
- !ruby/object:Gem::Version
|
|
82
26
|
version: '0'
|
|
83
27
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
28
|
+
name: json
|
|
85
29
|
requirement: !ruby/object:Gem::Requirement
|
|
86
30
|
requirements:
|
|
87
31
|
- - ">="
|
|
88
32
|
- !ruby/object:Gem::Version
|
|
89
33
|
version: '0'
|
|
90
|
-
type: :
|
|
34
|
+
type: :runtime
|
|
91
35
|
prerelease: false
|
|
92
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
37
|
requirements:
|
|
@@ -106,8 +50,12 @@ files:
|
|
|
106
50
|
- README.md
|
|
107
51
|
- lib/obtrace_sdk.rb
|
|
108
52
|
- lib/obtrace_sdk/client.rb
|
|
53
|
+
- lib/obtrace_sdk/context.rb
|
|
54
|
+
- lib/obtrace_sdk/framework.rb
|
|
55
|
+
- lib/obtrace_sdk/http_instrumentation.rb
|
|
56
|
+
- lib/obtrace_sdk/logger_capture.rb
|
|
109
57
|
- lib/obtrace_sdk/middleware.rb
|
|
110
|
-
- lib/obtrace_sdk/
|
|
58
|
+
- lib/obtrace_sdk/otlp.rb
|
|
111
59
|
- lib/obtrace_sdk/rails.rb
|
|
112
60
|
- lib/obtrace_sdk/semantic_metrics.rb
|
|
113
61
|
- lib/obtrace_sdk/types.rb
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
require "opentelemetry/sdk"
|
|
2
|
-
require "opentelemetry-exporter-otlp"
|
|
3
|
-
|
|
4
|
-
module ObtraceSDK
|
|
5
|
-
module OtelSetup
|
|
6
|
-
module_function
|
|
7
|
-
|
|
8
|
-
def configure(cfg)
|
|
9
|
-
endpoint = "#{cfg.ingest_base_url.to_s.sub(%r{/$}, "")}/otlp/v1/traces"
|
|
10
|
-
|
|
11
|
-
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
12
|
-
endpoint: endpoint,
|
|
13
|
-
headers: {
|
|
14
|
-
"Authorization" => "Bearer #{cfg.api_key}"
|
|
15
|
-
}.merge(cfg.default_headers || {}),
|
|
16
|
-
timeout: cfg.request_timeout_sec
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
resource_attrs = {
|
|
20
|
-
"service.name" => cfg.service_name,
|
|
21
|
-
"service.version" => cfg.service_version,
|
|
22
|
-
"deployment.environment" => cfg.env || "dev",
|
|
23
|
-
"runtime.name" => "ruby"
|
|
24
|
-
}
|
|
25
|
-
resource_attrs["obtrace.tenant_id"] = cfg.tenant_id if cfg.tenant_id
|
|
26
|
-
resource_attrs["obtrace.project_id"] = cfg.project_id if cfg.project_id
|
|
27
|
-
resource_attrs["obtrace.app_id"] = cfg.app_id if cfg.app_id
|
|
28
|
-
resource_attrs["obtrace.env"] = cfg.env if cfg.env
|
|
29
|
-
|
|
30
|
-
OpenTelemetry::SDK.configure do |c|
|
|
31
|
-
c.resource = OpenTelemetry::SDK::Resources::Resource.create(resource_attrs)
|
|
32
|
-
c.add_span_processor(
|
|
33
|
-
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
|
|
34
|
-
)
|
|
35
|
-
auto_detect_instrumentations(c)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
OpenTelemetry.propagation = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator
|
|
39
|
-
|
|
40
|
-
OpenTelemetry.tracer_provider
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def auto_detect_instrumentations(config)
|
|
44
|
-
instrumentations = [
|
|
45
|
-
["OpenTelemetry::Instrumentation::Net::HTTP", "opentelemetry-instrumentation-net_http"],
|
|
46
|
-
["OpenTelemetry::Instrumentation::Rack", "opentelemetry-instrumentation-rack"],
|
|
47
|
-
["OpenTelemetry::Instrumentation::Rails", "opentelemetry-instrumentation-rails"],
|
|
48
|
-
["OpenTelemetry::Instrumentation::PG", "opentelemetry-instrumentation-pg"],
|
|
49
|
-
["OpenTelemetry::Instrumentation::Redis", "opentelemetry-instrumentation-redis"],
|
|
50
|
-
["OpenTelemetry::Instrumentation::Sidekiq", "opentelemetry-instrumentation-sidekiq"],
|
|
51
|
-
["OpenTelemetry::Instrumentation::Faraday", "opentelemetry-instrumentation-faraday"],
|
|
52
|
-
["OpenTelemetry::Instrumentation::ActiveRecord", "opentelemetry-instrumentation-active_record"]
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
threads = instrumentations.map do |class_name, gem_name|
|
|
56
|
-
Thread.new(class_name, gem_name) do |cn, gn|
|
|
57
|
-
begin
|
|
58
|
-
require gn.gsub("-", "/")
|
|
59
|
-
rescue LoadError
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
threads.each(&:join)
|
|
64
|
-
|
|
65
|
-
instrumentations.each do |class_name, gem_name|
|
|
66
|
-
klass = Object.const_get(class_name) rescue next
|
|
67
|
-
config.use(class_name) if klass
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|