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 +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/lib/tarantool/opentelemetry/attributes.rb +52 -0
- data/lib/tarantool/opentelemetry/instrumenter.rb +48 -0
- data/lib/tarantool/opentelemetry/version.rb +7 -0
- data/lib/tarantool/opentelemetry.rb +14 -0
- metadata +85 -0
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,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: []
|