Skip to content

Commit 37a50d7

Browse files
committed
feat: Instrument PG connect (#1763)
* feat: add instrumentation for connecting to PG
1 parent 24a4a7c commit 37a50d7

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

instrumentation/pg/lib/opentelemetry/instrumentation/pg/instrumentation.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def require_dependencies
4040

4141
def patch_client
4242
::PG::Connection.prepend(Patches::Connection)
43+
::PG::Connection.singleton_class.prepend(Patches::ClassMethods)
4344
end
4445
end
4546
end

instrumentation/pg/lib/opentelemetry/instrumentation/pg/patches/connection.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,53 @@ module OpenTelemetry
1212
module Instrumentation
1313
module PG
1414
module Patches
15+
# Module to prepend to PG::Connection singleton class for connection initialization
16+
module ClassMethods
17+
def new(*args, &block)
18+
tracer = PG::Instrumentation.instance.tracer
19+
config = PG::Instrumentation.instance.config
20+
21+
tracer.in_span('connect', kind: :client) do |span|
22+
conn = super(*args) do |yielded_conn|g
23+
set_connection_attributes(span, yielded_conn, config)
24+
block.call(yielded_conn) if block
25+
end
26+
27+
unless block
28+
set_connection_attributes(span, conn, config)
29+
end
30+
31+
conn
32+
end
33+
end
34+
35+
private
36+
37+
def set_connection_attributes(span, conn, config)
38+
attributes = {
39+
'db.system' => 'postgresql',
40+
'db.name' => conn.db,
41+
'db.user' => conn.user
42+
}
43+
attributes['peer.service'] = config[:peer_service] if config[:peer_service]
44+
45+
h = conn.host
46+
if h&.start_with?('/')
47+
attributes['net.sock.family'] = 'unix'
48+
attributes['net.peer.name'] = h
49+
else
50+
attributes['net.transport'] = 'ip_tcp'
51+
attributes['net.peer.name'] = h
52+
attributes['net.peer.port'] = conn.port if defined?(::PG::DEF_PGPORT)
53+
end
54+
55+
attributes.merge!(OpenTelemetry::Instrumentation::PG.attributes)
56+
attributes.compact!
57+
58+
span.add_attributes(attributes)
59+
end
60+
end
61+
1562
# Module to prepend to PG::Connection for instrumentation
1663
module Connection # rubocop:disable Metrics/ModuleLength
1764
# Capture the first word (including letters, digits, underscores, & '.', ) that follows common table commands

instrumentation/pg/test/opentelemetry/instrumentation/pg/instrumentation_test.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,114 @@
6464
_(exporter.finished_spans.size).must_equal 0
6565
end
6666

67+
describe 'connection initialization' do
68+
it 'creates a connect span when establishing a connection' do
69+
conn = PG::Connection.open(
70+
host: host,
71+
port: port,
72+
user: user,
73+
dbname: dbname,
74+
password: password
75+
)
76+
conn.close
77+
78+
_(exporter.finished_spans.size).must_equal 1
79+
connect_span = exporter.finished_spans.first
80+
_(connect_span.name).must_equal 'connect'
81+
_(connect_span.kind).must_equal :client
82+
_(connect_span.attributes['db.system']).must_equal 'postgresql'
83+
_(connect_span.attributes['db.name']).must_equal dbname
84+
_(connect_span.attributes['db.user']).must_equal user
85+
_(connect_span.attributes['net.peer.name']).must_equal host
86+
_(connect_span.attributes['net.transport']).must_equal 'ip_tcp'
87+
end
88+
89+
it 'creates a connect span using PG::Connection.new' do
90+
conn = PG::Connection.new(
91+
host: host,
92+
port: port,
93+
user: user,
94+
dbname: dbname,
95+
password: password
96+
)
97+
conn.close
98+
99+
connect_span = exporter.finished_spans.first
100+
_(connect_span.name).must_equal 'connect'
101+
_(connect_span.kind).must_equal :client
102+
_(connect_span.attributes['db.system']).must_equal 'postgresql'
103+
_(connect_span.attributes['db.name']).must_equal dbname
104+
end
105+
106+
it 'creates a connect span using PG.connect' do
107+
conn = PG.connect(
108+
host: host,
109+
port: port,
110+
user: user,
111+
dbname: dbname,
112+
password: password
113+
)
114+
conn.close
115+
116+
connect_span = exporter.finished_spans.first
117+
_(connect_span.name).must_equal 'connect'
118+
_(connect_span.kind).must_equal :client
119+
_(connect_span.attributes['db.system']).must_equal 'postgresql'
120+
end
121+
122+
it 'accepts peer service name from config for connection' do
123+
instrumentation.instance_variable_set(:@installed, false)
124+
instrumentation.install(peer_service: 'readonly:postgres')
125+
126+
conn = PG::Connection.open(
127+
host: host,
128+
port: port,
129+
user: user,
130+
dbname: dbname,
131+
password: password
132+
)
133+
conn.close
134+
135+
connect_span = exporter.finished_spans.first
136+
_(connect_span.attributes['peer.service']).must_equal 'readonly:postgres'
137+
end
138+
139+
it 'sets unix socket attributes when connecting via socket' do
140+
skip 'socket test requires no host/port' if host || port
141+
142+
conn = PG::Connection.open(
143+
user: user,
144+
dbname: dbname,
145+
password: password
146+
)
147+
conn.close
148+
149+
connect_span = exporter.finished_spans.first
150+
_(connect_span.name).must_equal 'connect'
151+
_(connect_span.attributes['db.system']).must_equal 'postgresql'
152+
_(connect_span.attributes['net.sock.family']).must_equal 'unix'
153+
_(connect_span.attributes['net.peer.name']).must_match %r{^/}
154+
end
155+
156+
it 'records connection errors' do
157+
expect do
158+
PG::Connection.open(
159+
host: host,
160+
port: port,
161+
user: 'invalid_user',
162+
dbname: dbname,
163+
password: 'wrong_password'
164+
)
165+
end.must_raise PG::ConnectionBad
166+
167+
connect_span = exporter.finished_spans.first
168+
_(connect_span.name).must_equal 'connect'
169+
_(connect_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR
170+
_(connect_span.events.first.name).must_equal 'exception'
171+
_(connect_span.events.first.attributes['exception.type']).must_equal 'PG::ConnectionBad'
172+
end
173+
end
174+
67175
it 'accepts peer service name from config' do
68176
instrumentation.instance_variable_set(:@installed, false)
69177
instrumentation.install(peer_service: 'readonly:postgres')

0 commit comments

Comments
 (0)