tarantool-opentelemetry 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: 442ed8384e3de8f11710f2f653fb3d91967eb0c980264ca8bd7dd0a1f94f5231
4
+ data.tar.gz: 1a9cf8df40e0f0a4b892132205953221c903e5b119b5b3b040e5a305b395fef7
5
+ SHA512:
6
+ metadata.gz: 82ed9d4aa063391c54c75a1bd428c556a1212cae27d6235fd128df7b2e1a7a81ac8182c7505cb640b9d829b47fd401ee21a78c09e56d553256f2382de6640ea3
7
+ data.tar.gz: 60b959f146d5902900e69af6082fb7847308e7b62112e4466b4dd11417724924f7d2da90e3bcf10a61836bbfb8c97e5d8bad58b3b25c7a1ea12de74dcb2bbd1e
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-17)
4
+
5
+ First release.
6
+
7
+ - `Tarantool::OpenTelemetry::Instrumenter`: an `instrumenter:` for `Tarantool::Client` and `Pool` that opens one
8
+ client span per request and lifecycle event, with `db.*` and `server.*` attributes; chains another
9
+ instrumenter; `db_statement: :include` adds the SQL text.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Starlexxx
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,83 @@
1
+ # tarantool-opentelemetry
2
+
3
+ OpenTelemetry spans for [tarantool-client](../tarantool-client). The client reports every request and
4
+ lifecycle event to an `instrumenter:`; this gem is one that opens a client span per event, so a Tarantool
5
+ call shows up in a trace next to the HTTP and SQL spans of the application.
6
+
7
+ ## Installation
8
+
9
+ Requirements: Ruby >= 3.2, `opentelemetry-api` 1.x; an SDK and exporter configured by the application as
10
+ usual (`opentelemetry-sdk`, `opentelemetry-exporter-otlp`).
11
+
12
+ ```ruby
13
+ gem 'tarantool-opentelemetry'
14
+ ```
15
+
16
+ ```ruby
17
+ require 'tarantool/opentelemetry'
18
+
19
+ client = Tarantool::Client.new('tcp://app:secret@db1:3301', instrumenter: Tarantool::OpenTelemetry::Instrumenter.new)
20
+ pool = Tarantool::Client::Pool.new(uris, instrumenter: Tarantool::OpenTelemetry::Instrumenter.new)
21
+ ```
22
+
23
+ `Pool` hands the instrumenter to every member. tarantool-crud and tarantool-model talk through the client, so
24
+ their calls are traced too.
25
+
26
+ ## Options
27
+
28
+ ```ruby
29
+ Tarantool::OpenTelemetry::Instrumenter.new(
30
+ instrumenter: ActiveSupport::Notifications, # chained: receives every event, inside the span
31
+ tracer: OpenTelemetry.tracer_provider.tracer('my-app'), # default: tracer 'tarantool-client', its version
32
+ db_statement: :include # default :omit; :include adds the SQL text
33
+ )
34
+ ```
35
+
36
+ The chained instrumenter gets the same event name, the same payload object and the original block, so
37
+ tarantool-rails' log subscriber or a custom subscriber keeps working under the span.
38
+
39
+ ## Spans
40
+
41
+ One span per event, kind client, the client's block run inside it (a span current in the caller becomes
42
+ the parent).
43
+
44
+ | Event | Span name | Attributes |
45
+ |---|---|---|
46
+ | `request.tarantool` | `tarantool.select users`, `tarantool.call crud.get`, `tarantool.execute`, `tarantool.ping`; `tarantool` when a space, index or field name failed to resolve and no request was built | `db.system` `tarantool`, `db.operation`, `db.tarantool.space`, `db.tarantool.index`, `db.tarantool.function`, `db.tarantool.statement_id`, `db.tarantool.schema_reloaded`, `db.statement` (only with `db_statement: :include`), `server.address`, `server.port` |
47
+ | `disconnect.tarantool` | `tarantool.disconnect` | `tarantool.reason` |
48
+ | `reconnect.tarantool` | `tarantool.reconnect` | `tarantool.attempts` |
49
+ | `reconnect_failed.tarantool` | `tarantool.reconnect_failed` | `tarantool.attempts` |
50
+ | `fork.tarantool` | `tarantool.fork` | |
51
+ | `role_change.tarantool` | `tarantool.role_change` | `tarantool.read_only` |
52
+ | `shutdown.tarantool` | `tarantool.shutdown` | `tarantool.pending` |
53
+
54
+ Every span has `db.system` and, when the payload carries the instance address, `server.address` and
55
+ `server.port` (a unix socket path goes to `server.address` alone). An attribute is set only when the payload
56
+ has the key; Symbols become Strings, ids stay Integers.
57
+
58
+ A request that raises gets the exception recorded on the span once, the status set to error with the
59
+ exception's message, and the exception re-raised. Keys, tuples, call arguments, SQL bindings and credentials
60
+ never appear: the client does not put them in the payload. `db.statement` is opt-in because the SQL text can
61
+ carry literals.
62
+
63
+ ## Development
64
+
65
+ ```sh
66
+ cd tarantool-opentelemetry
67
+ bundle exec rspec # unit, no server
68
+ INTEGRATION=1 TARANTOOL_URI=tcp://app:secret@localhost:3301 bundle exec rspec # against a client container
69
+ ```
70
+
71
+ The integration specs need one of the tarantool-client containers (`podman compose -f
72
+ tarantool-client/docker-compose.yml up -d --wait tarantool-2.11 tarantool-3`, ports 3301 and 3302).
73
+
74
+ ## Public API
75
+
76
+ `Tarantool::OpenTelemetry::Instrumenter`.
77
+
78
+ Everything else, including anything tagged `@api private` in its source comment, is an implementation detail
79
+ and may change in any release.
80
+
81
+ ## License
82
+
83
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ module OpenTelemetry
5
+ # Span name and attributes of one client event. Only keys present in the payload become attributes, and the
6
+ # payload never carries keys, tuples, arguments or credentials; the SQL text is added only when asked for.
7
+ # @api private
8
+ module Attributes
9
+ REQUEST = 'request.tarantool'
10
+ DB_KEYS = {
11
+ request: 'db.operation', space: 'db.tarantool.space', index: 'db.tarantool.index',
12
+ function: 'db.tarantool.function', statement_id: 'db.tarantool.statement_id',
13
+ schema_reloaded: 'db.tarantool.schema_reloaded'
14
+ }.freeze
15
+ EVENT_KEYS = {
16
+ reason: 'tarantool.reason', attempts: 'tarantool.attempts', read_only: 'tarantool.read_only',
17
+ pending: 'tarantool.pending'
18
+ }.freeze
19
+ private_constant :REQUEST, :DB_KEYS, :EVENT_KEYS
20
+
21
+ module_function
22
+
23
+ def name(event, payload)
24
+ return "tarantool.#{event.delete_suffix('.tarantool')}" unless event == REQUEST
25
+ return 'tarantool' unless payload.key?(:request)
26
+
27
+ ['tarantool', payload[:request], payload[:space] || payload[:function]].compact.join(' ').sub(' ', '.')
28
+ end
29
+
30
+ def for(event, payload, db_statement:)
31
+ attributes = { 'db.system' => 'tarantool' }
32
+ keys = event == REQUEST ? DB_KEYS : EVENT_KEYS
33
+ keys.each { |key, attribute| attributes[attribute] = value(payload[key]) if payload.key?(key) }
34
+ attributes['db.statement'] = payload[:sql] if db_statement == :include && payload.key?(:sql)
35
+
36
+ attributes.merge(server(payload[:address]))
37
+ end
38
+
39
+ def value(value) = value.is_a?(Symbol) ? value.to_s : value
40
+
41
+ # host:port or [ipv6]:port; anything without a port is a unix socket path.
42
+ def server(address)
43
+ return {} if address.nil?
44
+
45
+ host, port = address.match(/\A\[?(.+?)\]?:(\d+)\z/)&.captures
46
+ return { 'server.address' => address } unless port
47
+
48
+ { 'server.address' => host, 'server.port' => port.to_i }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ module OpenTelemetry
5
+ # An instrumenter for Tarantool::Client and Pool: one client span per event, the block run inside it. Another
6
+ # instrumenter (ActiveSupport::Notifications, a logger wrapper) can be chained; it runs inside the span too.
7
+ class Instrumenter
8
+ DB_STATEMENT = %i[omit include].freeze
9
+ private_constant :DB_STATEMENT
10
+
11
+ def initialize(instrumenter: nil, tracer: nil, db_statement: :omit)
12
+ unless DB_STATEMENT.include?(db_statement)
13
+ raise ArgumentError, "db_statement must be :omit or :include, got #{db_statement.inspect}"
14
+ end
15
+
16
+ @instrumenter = instrumenter
17
+ @tracer = tracer || ::OpenTelemetry.tracer_provider.tracer('tarantool-client', Tarantool::Client::VERSION)
18
+ @db_statement = db_statement
19
+ end
20
+
21
+ # The client fills the payload (request, space, index, ...) inside the block, once names are resolved, so the
22
+ # name and attributes are set when the block is done. Finishing here rather than through Tracer#in_span records
23
+ # an exception once, with its message as the status description.
24
+ def instrument(name, payload, &block)
25
+ span = @tracer.start_span(Attributes.name(name, payload), kind: :client)
26
+ ::OpenTelemetry::Trace.with_span(span) { run(name, payload, &block) }
27
+ rescue StandardError => e
28
+ span&.record_exception(e)
29
+ span&.status = ::OpenTelemetry::Trace::Status.error(e.message)
30
+ raise
31
+ ensure
32
+ describe(span, name, payload) if span
33
+ span&.finish
34
+ end
35
+
36
+ private
37
+
38
+ def describe(span, name, payload)
39
+ span.name = Attributes.name(name, payload)
40
+ span.add_attributes(Attributes.for(name, payload, db_statement: @db_statement))
41
+ end
42
+
43
+ def run(name, payload, &)
44
+ @instrumenter ? @instrumenter.instrument(name, payload, &) : yield
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tarantool
4
+ module OpenTelemetry
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry'
4
+ require 'tarantool/client'
5
+
6
+ require_relative 'opentelemetry/version'
7
+ require_relative 'opentelemetry/attributes'
8
+ require_relative 'opentelemetry/instrumenter'
9
+
10
+ module Tarantool
11
+ # OpenTelemetry spans for the events a Tarantool::Client emits through its instrumenter.
12
+ module OpenTelemetry
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tarantool-opentelemetry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Starlexxx
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opentelemetry-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tarantool-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ description: An instrumenter for tarantool-client that turns every request and lifecycle
42
+ event into an OpenTelemetry client span, with db.* attributes and without keys,
43
+ tuples or credentials.
44
+ email:
45
+ - apavlovskiy088@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - LICENSE
52
+ - README.md
53
+ - lib/tarantool/opentelemetry.rb
54
+ - lib/tarantool/opentelemetry/attributes.rb
55
+ - lib/tarantool/opentelemetry/instrumenter.rb
56
+ - lib/tarantool/opentelemetry/version.rb
57
+ homepage: https://github.com/Starlexxx/tarantool-ruby
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ source_code_uri: https://github.com/Starlexxx/tarantool-ruby/tree/master/tarantool-opentelemetry
62
+ changelog_uri: https://github.com/Starlexxx/tarantool-ruby/blob/master/tarantool-opentelemetry/CHANGELOG.md
63
+ bug_tracker_uri: https://github.com/Starlexxx/tarantool-ruby/issues
64
+ homepage_uri: https://github.com/Starlexxx/tarantool-ruby
65
+ rubygems_mfa_required: 'true'
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '3.2'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.5.22
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: OpenTelemetry spans for tarantool-client
85
+ test_files: []