obtrace-sdk-ruby 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 +7 -0
- data/LICENSE +21 -0
- data/README.md +113 -0
- data/lib/obtrace_sdk/client.rb +208 -0
- 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 +86 -0
- data/lib/obtrace_sdk/otlp.rb +127 -0
- data/lib/obtrace_sdk/rails.rb +37 -0
- data/lib/obtrace_sdk/semantic_metrics.rb +55 -0
- data/lib/obtrace_sdk/types.rb +26 -0
- data/lib/obtrace_sdk/version.rb +3 -0
- data/lib/obtrace_sdk.rb +9 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c7468e55374f6c051c0def7f03f87796ebab9d4d69ff05132965e66b57bdbadb
|
|
4
|
+
data.tar.gz: a1d5629df790ebe12837155bb1bf94a9c61edd4ac737450ca4a3a24466dc144f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 77d495287ed0a0f010b57c08e860cec323a138f8a7c23e46051a9bd23abc8b8cc5c4dd6d717c08fefb8aac49d6285d57329846c830bd5a30dcaf3b4cb84bfbbe
|
|
7
|
+
data.tar.gz: 49fb1d19befa93b2d97485d70d1bf3106e64d340c3e26eb96f7c035e63adfbdff963da1b05787e2b06caedde5f8c7d61ab23fb4282378c29e3766b4ef12b6312
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Obtrace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# obtrace-sdk-ruby
|
|
2
|
+
|
|
3
|
+
Ruby backend SDK for Obtrace telemetry transport and instrumentation.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
- OTLP logs/traces/metrics transport
|
|
7
|
+
- Context propagation
|
|
8
|
+
- Rack/Rails middleware baseline
|
|
9
|
+
|
|
10
|
+
## Design Principle
|
|
11
|
+
SDK is thin/dumb.
|
|
12
|
+
- No business logic authority in client SDK.
|
|
13
|
+
- Policy and product logic are server-side.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# when published as gem
|
|
19
|
+
gem install obtrace-sdk-ruby
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Current workspace usage:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require_relative "lib/obtrace_sdk"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Required:
|
|
31
|
+
- `api_key`
|
|
32
|
+
- `ingest_base_url`
|
|
33
|
+
- `service_name`
|
|
34
|
+
|
|
35
|
+
Optional (auto-resolved from API key on the server side):
|
|
36
|
+
- `tenant_id`
|
|
37
|
+
- `project_id`
|
|
38
|
+
- `app_id`
|
|
39
|
+
- `env`
|
|
40
|
+
- `service_version`
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
### Simplified setup
|
|
45
|
+
|
|
46
|
+
The API key resolves `tenant_id`, `project_id`, `app_id`, and `env` automatically on the server side, so only three fields are needed:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "obtrace_sdk"
|
|
50
|
+
|
|
51
|
+
cfg = ObtraceSDK::Config.new(
|
|
52
|
+
api_key: "obt_live_...",
|
|
53
|
+
ingest_base_url: "https://ingest.obtrace.io",
|
|
54
|
+
service_name: "my-service"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
client = ObtraceSDK::Client.new(cfg)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Full configuration
|
|
61
|
+
|
|
62
|
+
For advanced use cases you can override the resolved values explicitly:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require_relative "lib/obtrace_sdk"
|
|
66
|
+
|
|
67
|
+
cfg = ObtraceSDK::Config.new(
|
|
68
|
+
api_key: "<API_KEY>",
|
|
69
|
+
ingest_base_url: "https://inject.obtrace.ai",
|
|
70
|
+
service_name: "ruby-api"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
client = ObtraceSDK::Client.new(cfg)
|
|
74
|
+
client.log("info", "started")
|
|
75
|
+
client.metric(ObtraceSDK::SemanticMetrics::RUNTIME_CPU_UTILIZATION, 0.41)
|
|
76
|
+
client.span("checkout.charge", attrs: {
|
|
77
|
+
"feature.name" => "checkout",
|
|
78
|
+
"payment.provider" => "stripe"
|
|
79
|
+
})
|
|
80
|
+
client.flush
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Canonical metrics and custom spans
|
|
84
|
+
|
|
85
|
+
- Use `ObtraceSDK::SemanticMetrics::*` for globally normalized metric names.
|
|
86
|
+
- Custom spans use `client.span("name", attrs: {...})`.
|
|
87
|
+
- Keep free-form metric names only for application-specific signals outside the shared catalog.
|
|
88
|
+
|
|
89
|
+
## Frameworks
|
|
90
|
+
|
|
91
|
+
- Rack-compatible middleware baseline for Rails usage
|
|
92
|
+
- Reference docs:
|
|
93
|
+
- `docs/frameworks.md`
|
|
94
|
+
|
|
95
|
+
## Production Hardening
|
|
96
|
+
|
|
97
|
+
1. Keep API keys in environment/secret managers.
|
|
98
|
+
2. Separate keys per environment.
|
|
99
|
+
3. Use graceful shutdown hooks to flush queue before exit.
|
|
100
|
+
4. Validate telemetry flow after deploy.
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
- Missing telemetry: verify endpoint reachability and auth key.
|
|
105
|
+
- Missing correlation: ensure propagation headers are injected.
|
|
106
|
+
- Debug transport with `debug: true` in config.
|
|
107
|
+
|
|
108
|
+
## Documentation
|
|
109
|
+
- Docs index: `docs/index.md`
|
|
110
|
+
- LLM context file: `llm.txt`
|
|
111
|
+
- MCP metadata: `mcp.json`
|
|
112
|
+
|
|
113
|
+
## Reference
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "uri"
|
|
4
|
+
require "thread"
|
|
5
|
+
require_relative "context"
|
|
6
|
+
require_relative "otlp"
|
|
7
|
+
require_relative "http_instrumentation"
|
|
8
|
+
require_relative "logger_capture"
|
|
9
|
+
|
|
10
|
+
module ObtraceSDK
|
|
11
|
+
class Client
|
|
12
|
+
def initialize(cfg)
|
|
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?
|
|
14
|
+
|
|
15
|
+
@cfg = cfg
|
|
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
|
|
30
|
+
|
|
31
|
+
def log(level, message, context = nil)
|
|
32
|
+
enqueue("/otlp/v1/logs", Otlp.logs_payload(@cfg, level, truncate(message, 32768), context))
|
|
33
|
+
end
|
|
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))
|
|
38
|
+
end
|
|
39
|
+
|
|
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 }
|
|
49
|
+
end
|
|
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 }
|
|
53
|
+
end
|
|
54
|
+
|
|
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
|
|
71
|
+
end
|
|
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
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
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
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
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
|
|
132
|
+
|
|
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
|
+
})
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def truncate(s, max)
|
|
146
|
+
return s if s.length <= max
|
|
147
|
+
s[0, max] + "...[truncated]"
|
|
148
|
+
end
|
|
149
|
+
|
|
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
|
|
155
|
+
end
|
|
156
|
+
@queue << { endpoint: endpoint, payload: payload.dup.freeze }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
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
|
|
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
|
|
175
|
+
end
|
|
176
|
+
|
|
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])
|
|
185
|
+
|
|
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
|
|
206
|
+
end
|
|
207
|
+
end
|
|
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
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module ObtraceSDK
|
|
2
|
+
class Middleware
|
|
3
|
+
def initialize(app, client)
|
|
4
|
+
@app = app
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
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)
|
|
13
|
+
|
|
14
|
+
status = nil
|
|
15
|
+
error = nil
|
|
16
|
+
|
|
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
|
|
27
|
+
|
|
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
|
|
44
|
+
|
|
45
|
+
if env["QUERY_STRING"] && !env["QUERY_STRING"].empty?
|
|
46
|
+
attrs["http.query"] = env["QUERY_STRING"]
|
|
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
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def extract_trace_id(env)
|
|
79
|
+
traceparent = env["HTTP_TRACEPARENT"]
|
|
80
|
+
return nil unless traceparent
|
|
81
|
+
parts = traceparent.split("-")
|
|
82
|
+
return nil unless parts.length >= 3
|
|
83
|
+
parts[1]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
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
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "obtrace_sdk"
|
|
2
|
+
|
|
3
|
+
module ObtraceSDK
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
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
|
+
|
|
10
|
+
next unless api_key
|
|
11
|
+
|
|
12
|
+
cfg = ObtraceSDK::Config.new(
|
|
13
|
+
api_key: api_key,
|
|
14
|
+
ingest_base_url: ingest_url,
|
|
15
|
+
service_name: service_name,
|
|
16
|
+
env: ::Rails.env.to_s,
|
|
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
|
+
debug: ENV["OBTRACE_DEBUG"] == "true"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
client = ObtraceSDK::Client.new(cfg)
|
|
24
|
+
ObtraceSDK.instance_variable_set(:@rails_client, client)
|
|
25
|
+
|
|
26
|
+
app.middleware.insert(0, ObtraceSDK::Middleware, client)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def credentials_fetch(key)
|
|
30
|
+
::Rails.application.credentials.send(key) rescue nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.rails_client
|
|
35
|
+
@rails_client
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module ObtraceSDK
|
|
2
|
+
module SemanticMetrics
|
|
3
|
+
THROUGHPUT = "http_requests_total"
|
|
4
|
+
ERROR_RATE = "http_5xx_total"
|
|
5
|
+
LATENCY_P95 = "latency_p95"
|
|
6
|
+
RUNTIME_CPU_UTILIZATION = "runtime.cpu.utilization"
|
|
7
|
+
RUNTIME_MEMORY_USAGE = "runtime.memory.usage"
|
|
8
|
+
RUNTIME_THREAD_COUNT = "runtime.thread.count"
|
|
9
|
+
RUNTIME_GC_PAUSE = "runtime.gc.pause"
|
|
10
|
+
RUNTIME_EVENTLOOP_LAG = "runtime.eventloop.lag"
|
|
11
|
+
CLUSTER_CPU_UTILIZATION = "cluster.cpu.utilization"
|
|
12
|
+
CLUSTER_MEMORY_USAGE = "cluster.memory.usage"
|
|
13
|
+
CLUSTER_NODE_COUNT = "cluster.node.count"
|
|
14
|
+
CLUSTER_POD_COUNT = "cluster.pod.count"
|
|
15
|
+
DB_OPERATION_LATENCY = "db.operation.latency"
|
|
16
|
+
DB_CLIENT_ERRORS = "db.client.errors"
|
|
17
|
+
DB_CONNECTIONS_USAGE = "db.connections.usage"
|
|
18
|
+
MESSAGING_CONSUMER_LAG = "messaging.consumer.lag"
|
|
19
|
+
WEB_VITAL_LCP = "web.vital.lcp"
|
|
20
|
+
WEB_VITAL_FCP = "web.vital.fcp"
|
|
21
|
+
WEB_VITAL_INP = "web.vital.inp"
|
|
22
|
+
WEB_VITAL_CLS = "web.vital.cls"
|
|
23
|
+
WEB_VITAL_TTFB = "web.vital.ttfb"
|
|
24
|
+
USER_ACTIONS = "obtrace.sim.web.react.actions"
|
|
25
|
+
|
|
26
|
+
ALL = [
|
|
27
|
+
THROUGHPUT,
|
|
28
|
+
ERROR_RATE,
|
|
29
|
+
LATENCY_P95,
|
|
30
|
+
RUNTIME_CPU_UTILIZATION,
|
|
31
|
+
RUNTIME_MEMORY_USAGE,
|
|
32
|
+
RUNTIME_THREAD_COUNT,
|
|
33
|
+
RUNTIME_GC_PAUSE,
|
|
34
|
+
RUNTIME_EVENTLOOP_LAG,
|
|
35
|
+
CLUSTER_CPU_UTILIZATION,
|
|
36
|
+
CLUSTER_MEMORY_USAGE,
|
|
37
|
+
CLUSTER_NODE_COUNT,
|
|
38
|
+
CLUSTER_POD_COUNT,
|
|
39
|
+
DB_OPERATION_LATENCY,
|
|
40
|
+
DB_CLIENT_ERRORS,
|
|
41
|
+
DB_CONNECTIONS_USAGE,
|
|
42
|
+
MESSAGING_CONSUMER_LAG,
|
|
43
|
+
WEB_VITAL_LCP,
|
|
44
|
+
WEB_VITAL_FCP,
|
|
45
|
+
WEB_VITAL_INP,
|
|
46
|
+
WEB_VITAL_CLS,
|
|
47
|
+
WEB_VITAL_TTFB,
|
|
48
|
+
USER_ACTIONS
|
|
49
|
+
].freeze
|
|
50
|
+
|
|
51
|
+
def self.semantic_metric?(name)
|
|
52
|
+
ALL.include?(name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ObtraceSDK
|
|
2
|
+
class Config
|
|
3
|
+
attr_accessor :api_key, :ingest_base_url, :tenant_id, :project_id, :app_id, :env
|
|
4
|
+
attr_accessor :service_name, :service_version, :max_queue_size, :request_timeout_sec
|
|
5
|
+
attr_accessor :default_headers, :debug, :validate_semantic_metrics
|
|
6
|
+
attr_accessor :auto_instrument_http, :auto_capture_logs
|
|
7
|
+
|
|
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)
|
|
9
|
+
@api_key = api_key
|
|
10
|
+
@ingest_base_url = ingest_base_url
|
|
11
|
+
@tenant_id = tenant_id
|
|
12
|
+
@project_id = project_id
|
|
13
|
+
@app_id = app_id
|
|
14
|
+
@env = env
|
|
15
|
+
@service_name = service_name
|
|
16
|
+
@service_version = service_version
|
|
17
|
+
@max_queue_size = max_queue_size
|
|
18
|
+
@request_timeout_sec = request_timeout_sec
|
|
19
|
+
@default_headers = default_headers
|
|
20
|
+
@validate_semantic_metrics = validate_semantic_metrics
|
|
21
|
+
@debug = debug
|
|
22
|
+
@auto_instrument_http = auto_instrument_http
|
|
23
|
+
@auto_capture_logs = auto_capture_logs
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/obtrace_sdk.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require_relative "obtrace_sdk/types"
|
|
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
|
+
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
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: obtrace-sdk-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Obtrace
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: net-http
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: json
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description: Ruby SDK for Obtrace observability platform. Captures logs, traces, and
|
|
42
|
+
metrics via OTLP.
|
|
43
|
+
email:
|
|
44
|
+
- dev@obtrace.ai
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- LICENSE
|
|
50
|
+
- README.md
|
|
51
|
+
- lib/obtrace_sdk.rb
|
|
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
|
|
57
|
+
- lib/obtrace_sdk/middleware.rb
|
|
58
|
+
- lib/obtrace_sdk/otlp.rb
|
|
59
|
+
- lib/obtrace_sdk/rails.rb
|
|
60
|
+
- lib/obtrace_sdk/semantic_metrics.rb
|
|
61
|
+
- lib/obtrace_sdk/types.rb
|
|
62
|
+
- lib/obtrace_sdk/version.rb
|
|
63
|
+
homepage: https://github.com/obtraceai/obtrace-sdk-ruby
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
homepage_uri: https://github.com/obtraceai/obtrace-sdk-ruby
|
|
68
|
+
source_code_uri: https://github.com/obtraceai/obtrace-sdk-ruby
|
|
69
|
+
changelog_uri: https://github.com/obtraceai/obtrace-sdk-ruby/releases
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '3.1'
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.4.20
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Obtrace Ruby SDK — observability for Ruby applications
|
|
89
|
+
test_files: []
|