connectrpc-otel 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2afdf1c6291bd39d2b2cd5c63ca15b5cd9cf60a87484715a931857daf1808342
4
+ data.tar.gz: 5983b85e156b331cb2ed65e235ae813dd69571865b55df69a747598d74e52723
5
+ SHA512:
6
+ metadata.gz: 816c7a986f4ca1df39040b262b57ec3337e2cf80f6404f66697d909eb0ce78cca3ca4fd657858474a272e73188814d4c3fcd0b2ff975125e55b6b96eea807ea0
7
+ data.tar.gz: a82429640248474cf3d9ab41b8805f7e8892109749054cb4d052d314bae4505852cb270b7eca6f975a5123fcb36f1762fbc88a2754692b996016d83aeeaab63d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jim Myhrberg
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,194 @@
1
+ # connectrpc-otel
2
+
3
+ Optional, experimental OpenTelemetry traces for Connect unary/streaming, native
4
+ gRPC and server gRPC-Web RPCs. The only runtime dependencies are
5
+ `connectrpc ~> 0.1.0` and `opentelemetry-api >= 1.11.1, < 2`.
6
+ Core and the HTTP/framework packages do not depend on OpenTelemetry.
7
+
8
+ ```ruby
9
+ require "connectrpc/otel"
10
+
11
+ subscriber = ConnectRPC::OTel::Subscriber.new(
12
+ tracer_provider: application_tracer_provider,
13
+ request_metadata: ["x-request-id"], response_metadata: ["x-region"]
14
+ )
15
+ instrumentation = ConnectRPC::Instrumentation.new(
16
+ subscribers: [subscriber], on_error: ->(error) { application_logger.warn(error.class.name) }
17
+ )
18
+ # Pass instrumentation: instrumentation to both ConnectRPC::Client.new and
19
+ # ConnectRPC::Server.new. Their other options and generated callers are unchanged.
20
+ ```
21
+
22
+ The application owns its SDK, sampling, processors and exporters. Requiring
23
+ `connectrpc/otel` never loads or configures an SDK/exporter, replaces global
24
+ providers/propagators, or creates network clients. The default tracer provider is
25
+ the application's OpenTelemetry provider. An explicit W3C trace-context plus
26
+ baggage propagator is used by default, including when the global propagator is
27
+ no-op. Pass `propagator:` to select another application propagator. Span finish
28
+ invokes application-owned processors synchronously; a synchronous exporter can
29
+ block the calling thread/reactor. This package makes no nonblocking guarantee.
30
+
31
+ See the [supported 0.x contract](../../docs/support.md) for the target support
32
+ profile, matched package versions, compatibility policy and adoption gates.
33
+
34
+ ## Spans and privacy
35
+
36
+ The adapter emits one CLIENT or SERVER span for each configured RPC side.
37
+ Names for declared methods are `fully.qualified.Service/Method`.
38
+ `rpc.system.name` is `connectrpc`, `rpc.method` is the same declared name,
39
+ and `rpc.response.status_code` is `OK` for known success or the lower-case
40
+ Connect error actually observed. Bare preflight/admission responses have no
41
+ invented RPC code. Error rendering may replace an oversized error with
42
+ `resource_exhausted`; an empty 429 has no server wire code, while clients use
43
+ the existing HTTP fallback `unavailable`.
44
+
45
+ The baseline is [Connect semantic conventions v1.44.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.44.0/docs/rpc/connect-rpc.md),
46
+ which are Development. CLIENT treats every non-OK Connect code as an error;
47
+ SERVER treats `unknown`, `deadline_exceeded`, `unimplemented`, `internal`,
48
+ `unavailable`, and `data_loss` as errors. Delivered `invalid_argument`, for
49
+ example, leaves SERVER status UNSET. Independent operation failures and host
50
+ aborts are errors. `error.type` contains bounded codes or local classifications;
51
+ success remains UNSET. There are no automatic exception events, descriptions,
52
+ stacks, payloads, raw URLs/queries, certificate data or arbitrary metadata.
53
+
54
+ Unknown methods use span name `connectrpc` and `rpc.method=_OTHER`. This adapter
55
+ redacts `rpc.method_original` by default. That attribute is conditionally
56
+ required by v1.44.0 for unknown methods, so this privacy default is a specific
57
+ conformance limitation. This is not a claim of full semantic-convention
58
+ compliance. Instrumentation scope `connectrpc-otel` identifies its package
59
+ version and has `connectrpc.semconv.version=1.44.0`. API 1.11.1/SDK 1.13.1 support
60
+ scope attributes, but no tracer schema URL; none is supplied.
61
+
62
+ Metadata capture requires explicit names, at most 32 per direction, each at most
63
+ 128 bytes. Values retain repeated arrays, limited to eight printable ASCII
64
+ entries of 256 bytes each, under `connectrpc.request.metadata.<name>` and
65
+ `connectrpc.response.metadata.<name>`. No wildcards are accepted. Selecting
66
+ sensitive names is the application's responsibility. Baggage propagates but
67
+ is not captured as span attributes. Request injection is staged and validated
68
+ before merging into copied metadata; invalid injection is discarded and
69
+ reported without changing caller metadata or protocol headers.
70
+
71
+ ## Processing and delivery
72
+
73
+ Core observation spans timeout/metadata validation, codecs, compression,
74
+ transport and response validation. Servers retain original failures for trusted
75
+ neutral subscribers separately from normalized/rendered errors. The OTel
76
+ adapter never serializes those exception objects.
77
+
78
+ Custom attributes distinguish `connectrpc.processing.state`,
79
+ `connectrpc.decision` (`processed`, `admission_rejected`, `host_rejected`) and
80
+ `connectrpc.delivery`:
81
+
82
+ - `unobserved`: direct core or Testing transport; no host-delivery evidence.
83
+ - `body_closed`: portable Rack body close only.
84
+ - `response_finished`: Rack's response-finished callback ran. It does not prove
85
+ completion of other callbacks or peer receipt. This extension takes precedence
86
+ over body close.
87
+ - `completed`: native Async/Falcon full-write and cleanup boundary, including
88
+ Falcon HTTP/1 body callbacks. This does not promise peer/application receipt.
89
+ - `aborted`: cancellation, reset, write failure or forced teardown.
90
+
91
+ `connectrpc.processing.duration` is monotonic processing seconds. Measured
92
+ encoded sizes appear as `connectrpc.request.wire_bytes` and
93
+ `connectrpc.response.wire_bytes`; unknown sizes are omitted. For GET, request
94
+ wire bytes measure the query transport payload after percent/base64 decoding
95
+ and before inflation, independently of the empty HTTP body. Zero means measured
96
+ empty; rejected requests before measurement remain unknown. RPC spans never
97
+ capture request URLs, query controls or payloads. Generic HTTP instrumentation
98
+ and host/proxy logs have their own application-owned redaction policies. Successful
99
+ processing followed by delivery abort retains `OK` and ends ERROR.
100
+
101
+ Configure native hosts through their Server. Lifecycle handles are passed
102
+ explicitly into independent operation/setup/handler tasks. Shared connection
103
+ readers never retain one RPC's context. Connections/setup rejected before any
104
+ RPC exists are outside per-RPC observation. Rack consumers must close the body,
105
+ or invoke the promised response-finished callbacks; a noncompliant consumer can
106
+ leave a span unfinished. For native Rails controllers, override private
107
+ `connect_instrumentation` separately from `connect_limits`. Only failures
108
+ escaping Rails execution are captured; a successful `rescue_from` does not
109
+ mark a successful RPC as failed. Configuration failures before Server
110
+ construction remain outside its lifecycle.
111
+
112
+ ## HTTP instrumentation coexistence
113
+
114
+ The SDK-backed suite tests Rack instrumentation 0.31.2 (stable middleware,
115
+ `use_rack_events: false`) and NetHTTP instrumentation 0.29.2, with the application
116
+ installing a W3C global propagator. Native Rails 8.1.4 is tested through its real
117
+ controller pipeline. Generic HTTP tracing remains enabled. The observed tree is:
118
+ application -> RPC CLIENT -> HTTP CLIENT -> RPC SERVER and HTTP SERVER as
119
+ siblings. NetHTTP injects its HTTP CLIENT; both server integrations extract that
120
+ serialized parent. The RPC adapter deliberately does not inspect ambient HTTP
121
+ spans to guess a parent. In-process Testing transport also extracts serialized
122
+ headers, even while the caller's RPC context is ambient. Other instrumentation
123
+ combinations need their own runtime verification.
124
+
125
+ ## Development
126
+
127
+ From the repository root use `mise run otel:setup`, `otel:test`, `otel:check`,
128
+ `otel:build`, `otel:package`, `otel:example`, and `otel:verify`.
129
+ The independent checksummed development bundle includes the real SDK,
130
+ InMemorySpanExporter, HTTP instrumentations and local integration packages.
131
+ Existing runtime closures and locks remain independent. Caches/archives are in
132
+ `vendor/otel`, and coverage is in `coverage/otel/<Ruby>/`. Both pinned Ruby
133
+ 3.4.12 and 4.0.9 CI leaves run `otel:verify`. `scripts/integration.rb` is also a
134
+ runnable generated-client example with a real in-memory SDK exporter.
135
+
136
+ The new lock's API/SDK were released 2026-09-16. New dependencies, including
137
+ native variants, were checked against primary RubyGems release timestamps and
138
+ API SHA256 values before adoption, under the seven-day cooldown. The versioned
139
+ lock preserves the eligible existing cross-package graph. Original project
140
+ work is [MIT licensed](LICENSE), copyright 2026 Jim Myhrberg. All package
141
+ archives pin `allowed_push_host=https://rubygems.org`; these checks never
142
+ publish or release a gem.
143
+
144
+ ## Static signatures
145
+
146
+ This package owns `sig/` and its discovery manifest. Add
147
+ `library "connectrpc-otel"` to a strict Steep target with this gem installed.
148
+ See the [RBS guide](../../docs/rbs.md) for supported APIs, extension contracts,
149
+ partial records, runtime validation and framework-signature limits.
150
+ `mise run rbs:check` checks application callers for this package; strict core
151
+ source checking is a separate target from backend implementation checking.
152
+
153
+ ## Native streaming
154
+
155
+ The core [streaming guide](../../docs/streaming.md) documents all three scoped
156
+ Connect streaming shapes, typed generated clients/handlers, early headers,
157
+ per-message limits and bounded ownership. Native Async/Falcon HTTP/2 supports
158
+ both codecs and identity/gzip over h2c/TLS/mTLS. Falcon additionally serves Connect server and client
159
+ streaming over HTTP/1.1,
160
+ with protobuf/ProtoJSON and identity/gzip over TCP/TLS/mTLS. The standalone
161
+ Async Host and original Async Transport remain H2-only. The explicit
162
+ `ConnectRPC::Async::HTTP1Transport` supports Connect unary, safe GET, server
163
+ streaming and client streaming in an application-owned reactor.
164
+
165
+ The streaming guide defines Falcon's cooperative 500 ms total final-close budget
166
+ and unchanged 100 ms / 256 KiB discard limits. Native Connect streaming clients
167
+ have a separate one-second absolute HTTP tail policy. It applies even with a nil
168
+ RPC deadline and can reject otherwise valid late EOF or slow intermediaries.
169
+
170
+ Streaming keeps one span and fixed lifecycle state per RPC, with no per-message
171
+ events or payload capture. The original processor retains SDK context and peer
172
+ identity through codecs and cleanup; publication, processing and full native
173
+ wire completion remain independent. Late application cleanup failure aborts
174
+ delivery. Falcon H1 records its final-flush expiry before body callbacks and
175
+ lifecycle observation; delayed callbacks consume that same 500 ms budget.
176
+ Post-delivery discard or grace failure preserves an already completed span,
177
+ while socket/admission ownership continues through retirement and processing join.
178
+
179
+ ## Native gRPC observation
180
+
181
+ The actual protocol is fixed before subscriber start. gRPC uses the 1.44.0
182
+ release-candidate conventions: `rpc.system.name=grpc`, uppercase
183
+ `rpc.response.status_code` and code-based `error.type`, including `CANCELLED`.
184
+ Client and Server retain the role-specific error policy above. Allowlisted gRPC
185
+ metadata uses `rpc.request.metadata.<key>` and `rpc.response.metadata.<key>`.
186
+ Connect keeps its existing names and lower-case codes. Unknown gRPC methods use
187
+ span name `grpc` and `rpc.method=_OTHER`, with the same explicit privacy limit.
188
+ Processing and delivery remain independent; successful handler processing cannot
189
+ prove terminal HEADERS delivery. SDK configuration remains application-owned.
190
+
191
+ ## gRPC-Web
192
+
193
+ Web calls have Call.protocol=:grpc_web before subscriber start. The project maps them to gRPC semconv 1.44.0 with rpc.system.name=grpc and bounded connectrpc.protocol=grpc_web. Status codes, metadata prefixes, error roles and original-task activation follow native gRPC.
194
+ See the [Web guide](../../docs/grpc-web.md) for protocol, CORS, bounds and completion rules.
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "version"
4
+
5
+ module ConnectRPC
6
+ module OTel
7
+ SEMCONV_VERSION = "1.44.0"
8
+
9
+ class Subscriber
10
+ SERVER_ERRORS = %w[unknown deadline_exceeded unimplemented internal unavailable data_loss].freeze
11
+ private_constant :SERVER_ERRORS
12
+
13
+ def initialize(tracer_provider: OpenTelemetry.tracer_provider, propagator: nil,
14
+ request_metadata: [], response_metadata: [])
15
+ @tracer = tracer_provider.tracer("connectrpc-otel", VERSION,
16
+ attributes: { "connectrpc.semconv.version" => SEMCONV_VERSION })
17
+ @propagator = propagator || OpenTelemetry::Context::Propagation::CompositeTextMapPropagator.compose_propagators(
18
+ [OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator,
19
+ OpenTelemetry::Baggage::Propagation.text_map_propagator]
20
+ )
21
+ @request_metadata = allowlist(request_metadata)
22
+ @response_metadata = allowlist(response_metadata)
23
+ end
24
+
25
+ def start(call) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
26
+ parent = if call.role == :server
27
+ # Always extract serialized propagation, even for an in-process call.
28
+ @propagator.extract(call.carrier, context: OpenTelemetry::Context.empty, getter: Getter.new)
29
+ else
30
+ OpenTelemetry::Context.current
31
+ end
32
+ name = if call.method
33
+ "#{call.method.service}/#{call.method.name}"
34
+ else
35
+ (%i[grpc grpc_web].include?(call.protocol) ? "grpc" : "connectrpc")
36
+ end
37
+ span = @tracer.start_span(name, kind: call.role, with_parent: parent, attributes: attributes(call, name))
38
+ context = OpenTelemetry::Trace.context_with_span(span, parent_context: parent)
39
+ begin
40
+ @propagator.inject(call.carrier, context: context) if call.role == :client
41
+ Observer.new(span, context, call.role, @request_metadata, @response_metadata, call.protocol)
42
+ rescue Exception # rubocop:disable Lint/RescueException -- release span and preserve fatal exceptions
43
+ span.finish
44
+ raise
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def attributes(call, name)
51
+ result = { "rpc.system.name" => %i[grpc grpc_web].include?(call.protocol) ? "grpc" : "connectrpc",
52
+ "rpc.method" => call.method ? name : "_OTHER" }
53
+ result["connectrpc.protocol"] = "grpc_web" if call.protocol == :grpc_web
54
+ if call.role == :client
55
+ result["server.address"] = call.server_address if call.server_address
56
+ result["server.port"] = call.server_port if call.server_port
57
+ end
58
+ result
59
+ end
60
+
61
+ # Validate the complete allowlist transaction before retaining any configuration.
62
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
+ def allowlist(keys)
64
+ unless keys.is_a?(Array) && keys.size <= 32 && keys.all? do |key|
65
+ key.is_a?(String) && key.bytesize <= 128 && /\A[0-9A-Za-z_.-]+\z/.match?(key) &&
66
+ !Metadata.reserved?(key.downcase)
67
+ end
68
+ raise ArgumentError, "Metadata capture requires at most 32 explicit metadata names of at most 128 bytes"
69
+ end
70
+
71
+ keys.map { |key| key.downcase.freeze }.uniq.freeze
72
+ end
73
+
74
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
+
76
+ class Getter
77
+ def get(carrier, key) # rubocop:disable Metrics/CyclomaticComplexity
78
+ values = Array(carrier[key])
79
+ return unless values.all? { |value| value.is_a?(String) && value.ascii_only? }
80
+ return values.first if key == "traceparent" && values.size == 1
81
+ return if key == "traceparent" || values.empty?
82
+
83
+ values.join(",")
84
+ end
85
+
86
+ def keys(carrier) = carrier.keys
87
+ end
88
+ private_constant :Getter
89
+
90
+ class Observer
91
+ def initialize(span, context, role, request_keys, response_keys, protocol) # rubocop:disable Metrics/ParameterLists
92
+ @protocol = protocol
93
+ @span = span
94
+ @context = context
95
+ @role = role
96
+ @request_keys = request_keys
97
+ @response_keys = response_keys
98
+ end
99
+
100
+ def attach = OpenTelemetry::Context.attach(@context)
101
+ def detach(token) = OpenTelemetry::Context.detach(token)
102
+
103
+ def processing_finished(outcome) # rubocop:disable Metrics/AbcSize
104
+ @span.set_attribute("rpc.response.status_code", status_code(outcome.code)) if outcome.code
105
+ @span.set_attribute("http.response.status_code", outcome.http_status) if outcome.http_status
106
+ @span.set_attribute("connectrpc.processing.duration", outcome.duration)
107
+ @span.set_attribute("connectrpc.request.wire_bytes", outcome.request_bytes) if outcome.request_bytes
108
+ @span.set_attribute("connectrpc.response.wire_bytes", outcome.response_bytes) if outcome.response_bytes
109
+ capture("request", outcome.request_metadata, @request_keys)
110
+ capture("response", outcome.response_metadata, @response_keys)
111
+ error = processing_error(outcome)
112
+ fail_span(status_code(error)) if error
113
+ end
114
+
115
+ def finished(completion) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
116
+ @span.set_attribute("connectrpc.processing.state", completion.processing_state.to_s)
117
+ @span.set_attribute("connectrpc.decision", completion.decision.to_s)
118
+ @span.set_attribute("connectrpc.delivery", completion.delivery.to_s)
119
+ @span.set_attribute("http.response.status_code", completion.http_status) if completion.http_status
120
+ if completion.delivery == :aborted
121
+ code = completion.outcome&.code
122
+ type = if %i[grpc
123
+ grpc_web].include?(@protocol) && code && code != "OK"
124
+ status_code(code)
125
+ else
126
+ "delivery_aborted"
127
+ end
128
+ fail_span(type)
129
+ elsif completion.decision != :processed
130
+ fail_span(completion.decision.to_s)
131
+ end
132
+ ensure
133
+ @span.finish
134
+ end
135
+
136
+ private
137
+
138
+ # Keep role-specific wire codes and independent operation failures in one table.
139
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
140
+ def processing_error(outcome)
141
+ if outcome.code && outcome.code != "OK"
142
+ return outcome.code if @role == :client || SERVER_ERRORS.include?(outcome.code)
143
+
144
+ return nil
145
+ end
146
+ return "operation_failed" if outcome.error && !outcome.error.is_a?(ConnectRPC::Error)
147
+ return "operation_failed" if outcome.code.nil? && outcome.error
148
+ return "preflight_rejected" if outcome.http_status && outcome.http_status != 200 && outcome.code.nil?
149
+
150
+ nil
151
+ end
152
+
153
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
154
+
155
+ def fail_span(type)
156
+ @span.set_attribute("error.type", type)
157
+ @span.status = OpenTelemetry::Trace::Status.error
158
+ end
159
+
160
+ def status_code(code)
161
+ return code unless %i[grpc grpc_web].include?(@protocol)
162
+
163
+ code == "canceled" ? "CANCELLED" : code.upcase
164
+ end
165
+
166
+ def capture(direction, metadata, keys) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
167
+ keys.each do |key|
168
+ values = metadata[key]
169
+ next unless values
170
+
171
+ values = values.first(8).filter_map do |value|
172
+ value.byteslice(0, 256) if value.is_a?(String) && value.ascii_only? && /\A[\x20-\x7e]*\z/.match?(value)
173
+ end
174
+ prefix = %i[grpc grpc_web].include?(@protocol) ? "rpc" : "connectrpc"
175
+ @span.set_attribute("#{prefix}.#{direction}.metadata.#{key}", values) unless values.empty?
176
+ end
177
+ end
178
+ end
179
+ private_constant :Observer
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConnectRPC
4
+ module OTel
5
+ VERSION = "0.1.0" # x-release-please-version
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "connectrpc"
4
+ require_relative "otel/version"
5
+ require "opentelemetry-api"
6
+ require_relative "otel/subscriber"
@@ -0,0 +1,48 @@
1
+ module ConnectRPC
2
+ module OTel
3
+ VERSION: String
4
+ SEMCONV_VERSION: String
5
+ type attributes = Hash[String, String | Integer | Float | bool | Array[String]]
6
+ interface _TraceFlags
7
+ def sampled?: () -> bool
8
+ end
9
+ interface _Tracestate
10
+ def empty?: () -> bool
11
+ def to_s: () -> String
12
+ end
13
+ # The default W3C propagator consumes these API capabilities. Id contents
14
+ # and lengths remain the provider's runtime responsibility.
15
+ interface _SpanContext
16
+ def valid?: () -> bool
17
+ def trace_id: () -> String
18
+ def span_id: () -> String
19
+ def trace_flags: () -> _TraceFlags
20
+ def tracestate: () -> _Tracestate
21
+ end
22
+ interface _Span
23
+ def context: () -> _SpanContext
24
+ def set_attribute: (String, String | Integer | Float | bool | Array[String]) -> void
25
+ def status=: (Object) -> void
26
+ def finish: () -> void
27
+ end
28
+ interface _Tracer
29
+ def start_span: (String, kind: :client | :server, with_parent: Object, attributes: attributes) -> _Span
30
+ end
31
+ interface _TracerProvider
32
+ def tracer: (String, String, attributes: attributes) -> _Tracer
33
+ end
34
+ interface _Getter
35
+ def get: (Hash[String, String | Array[String]], String) -> String?
36
+ def keys: (Hash[String, String | Array[String]]) -> Array[String]
37
+ end
38
+ interface _Propagator
39
+ def extract: (Hash[String, String | Array[String]], context: Object, getter: _Getter) -> Object
40
+ def inject: (Hash[String, String | Array[String]], context: Object) -> void
41
+ end
42
+ class Subscriber
43
+ def initialize: (?tracer_provider: _TracerProvider, ?propagator: _Propagator?, ?request_metadata: Array[String], ?response_metadata: Array[String]) -> void
44
+ # The adapter's observer is private and discovered by the neutral lifecycle.
45
+ def start: (Instrumentation::Call) -> Object?
46
+ end
47
+ end
48
+ end
data/sig/manifest.yaml ADDED
@@ -0,0 +1,2 @@
1
+ dependencies:
2
+ - name: connectrpc
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: connectrpc-otel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - jimeh
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-11-15 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: connectrpc
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: opentelemetry-api
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.11.1
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '2'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.11.1
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
46
+ description: Application-owned OpenTelemetry tracing through the ConnectRPC lifecycle.
47
+ Experimental.
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - LICENSE
53
+ - README.md
54
+ - lib/connectrpc/otel.rb
55
+ - lib/connectrpc/otel/subscriber.rb
56
+ - lib/connectrpc/otel/version.rb
57
+ - sig/connectrpc-otel.rbs
58
+ - sig/manifest.yaml
59
+ homepage: https://github.com/jimeh/connectrpc-ruby
60
+ licenses:
61
+ - MIT
62
+ metadata:
63
+ allowed_push_host: https://rubygems.org
64
+ rubygems_mfa_required: 'true'
65
+ source_code_uri: https://github.com/jimeh/connectrpc-ruby
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '3.4'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.6.9
81
+ specification_version: 4
82
+ summary: Optional OpenTelemetry traces for ConnectRPC unary calls
83
+ test_files: []