Skip to content

Commit 7066c08

Browse files
Add instrumentation for github.com/gocql/gocql (#137)
* Implemented integration using observers and added an dockerized example * began adding metric instrumentation * added meter unit tests * example: Update example to export metrics and trace to prometheus and zipkin * Added context to connect observer to allow for connect spans to have parent spans * updated example to use a batched query * added additional metrics * added unit to latency * added additional labels and attributes * added doc * added package readme * added readme to example * fix formatting * Remove unecessary export of keyvalue pairs * fix typos * Export otelconfig * Fix lint * Delete prometheus.yml * Fix comments * Addressed pr feedback by refactoring attributes and labels to conform to semantic conventions * Addressed PR feedback by removing constructor functions that take a config struct * fix: address pr feedback by setting meter name to package name * fix: add spankindclient to spans * fix: address pr feedback by correcting package name Co-authored-by: Tyler Yahn <[email protected]>
1 parent 5a13a42 commit 7066c08

File tree

15 files changed

+1879
-0
lines changed

15 files changed

+1879
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql`
2+
3+
This package provides tracing and metrics to the golang cassandra client `github.com/gocql/gocql` using the `ConnectObserver`, `QueryObserver` and `BatchObserver` interfaces.
4+
5+
To enable tracing in your application:
6+
7+
```go
8+
package main
9+
10+
import (
11+
"context"
12+
13+
"github.com/gocql/gocql"
14+
otelGocql "go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql"
15+
)
16+
17+
func main() {
18+
// Create a cluster
19+
host := "localhost"
20+
cluster := gocql.NewCluster(host)
21+
22+
// Create a session with tracing
23+
session, err := otelGocql.NewSessionWithTracing(
24+
context.Background(),
25+
cluster,
26+
// Include any options here
27+
)
28+
29+
// Begin using the session
30+
31+
}
32+
```
33+
34+
You can customize instrumentation by passing any of the following options to `NewSessionWithTracing`:
35+
36+
| Function | Description |
37+
| -------- | ----------- |
38+
| `WithQueryObserver(gocql.QueryObserver)` | Specify an additional QueryObserver to be called. |
39+
| `WithBatchObserver(gocql.BatchObserver)` | Specify an additional BatchObserver to be called. |
40+
| `WithConnectObserver(gocql.ConnectObserver)` | Specify an additional ConnectObserver to be called. |
41+
| `WithTracer(trace.Tracer)` | The tracer to be used to create spans for the gocql session. If not specified, `global.Tracer("go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql")` will be used. |
42+
| `WithQueryInstrumentation(bool)` | To enable/disable tracing and metrics for queries. |
43+
| `WithBatchInstrumentation(bool)` | To enable/disable tracing and metrics for batch queries. |
44+
| `WithConnectInstrumentation(bool)` | To enable/disable tracing and metrics for new connections. |
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gocql
16+
17+
import (
18+
"go.opentelemetry.io/otel/api/kv"
19+
"go.opentelemetry.io/otel/api/standard"
20+
)
21+
22+
const (
23+
// cassVersionKey is the key for the attribute/label describing
24+
// the cql version.
25+
cassVersionKey = kv.Key("db.cassandra.version")
26+
27+
// cassHostIDKey is the key for the attribute/label describing the id
28+
// of the host being queried.
29+
cassHostIDKey = kv.Key("db.cassandra.host.id")
30+
31+
// cassHostStateKey is the key for the attribute/label describing
32+
// the state of the casssandra server hosting the node being queried.
33+
cassHostStateKey = kv.Key("db.cassandra.host.state")
34+
35+
// cassBatchQueriesKey is the key for the attribute describing
36+
// the number of queries contained within the batch statement.
37+
cassBatchQueriesKey = kv.Key("db.cassandra.batch.queries")
38+
39+
// cassErrMsgKey is the key for the attribute/label describing
40+
// the error message from an error encountered when executing a query, batch,
41+
// or connection attempt to the cassandra server.
42+
cassErrMsgKey = kv.Key("db.cassandra.error.message")
43+
44+
// cassRowsReturnedKey is the key for the span attribute describing the number of rows
45+
// returned on a query to the database.
46+
cassRowsReturnedKey = kv.Key("db.cassandra.rows.returned")
47+
48+
// cassQueryAttemptsKey is the key for the span attribute describing the number of attempts
49+
// made for the query in question.
50+
cassQueryAttemptsKey = kv.Key("db.cassandra.attempts")
51+
52+
// Static span names
53+
cassBatchQueryName = "Batch Query"
54+
cassConnectName = "New Connection"
55+
56+
// instrumentationName is the name of the instrumentation package.
57+
instrumentationName = "go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql"
58+
)
59+
60+
// ------------------------------------------ Connection-level Attributes
61+
62+
// cassDBSystem returns the name of the DB system,
63+
// cassandra, as a KeyValue pair (db.system).
64+
func cassDBSystem() kv.KeyValue {
65+
return standard.DBSystemCassandra
66+
}
67+
68+
// cassPeerName returns the hostname of the cassandra
69+
// server as a standard KeyValue pair (net.peer.name).
70+
func cassPeerName(name string) kv.KeyValue {
71+
return standard.NetPeerNameKey.String(name)
72+
}
73+
74+
// cassPeerPort returns the port number of the cassandra
75+
// server as a standard KeyValue pair (net.peer.port).
76+
func cassPeerPort(port int) kv.KeyValue {
77+
return standard.NetPeerPortKey.Int(port)
78+
}
79+
80+
// cassPeerIP returns the IP address of the cassandra
81+
// server as a standard KeyValue pair (net.peer.ip).
82+
func cassPeerIP(ip string) kv.KeyValue {
83+
return standard.NetPeerIPKey.String(ip)
84+
}
85+
86+
// cassVersion returns the cql version as a KeyValue pair.
87+
func cassVersion(version string) kv.KeyValue {
88+
return cassVersionKey.String(version)
89+
}
90+
91+
// cassHostID returns the id of the cassandra host as a KeyValue pair.
92+
func cassHostID(id string) kv.KeyValue {
93+
return cassHostIDKey.String(id)
94+
}
95+
96+
// cassHostState returns the state of the cassandra host as a KeyValue pair.
97+
func cassHostState(state string) kv.KeyValue {
98+
return cassHostStateKey.String(state)
99+
}
100+
101+
// ------------------------------------------ Call-level attributes
102+
103+
// cassStatement returns the statement made to the cassandra database as a
104+
// standard KeyValue pair (db.statement).
105+
func cassStatement(stmt string) kv.KeyValue {
106+
return standard.DBStatementKey.String(stmt)
107+
}
108+
109+
// cassDBOperation returns the batch query operation
110+
// as a standard KeyValue pair (db.operation). This is used in lieu of a
111+
// db.statement, which is not feasible to include in a span for a batch query
112+
// because there can be n different query statements in a batch query.
113+
func cassBatchQueryOperation() kv.KeyValue {
114+
cassBatchQueryOperation := "db.cassandra.batch.query"
115+
return standard.DBOperationKey.String(cassBatchQueryOperation)
116+
}
117+
118+
// cassConnectOperation returns the connect operation
119+
// as a standard KeyValue pair (db.operation). This is used in lieu of a
120+
// db.statement since connection creation does not have a CQL statement.
121+
func cassConnectOperation() kv.KeyValue {
122+
cassConnectOperation := "db.cassandra.connect"
123+
return standard.DBOperationKey.String(cassConnectOperation)
124+
}
125+
126+
// cassKeyspace returns the keyspace of the session as
127+
// a standard KeyValue pair (db.cassandra.keyspace).
128+
func cassKeyspace(keyspace string) kv.KeyValue {
129+
return standard.DBCassandraKeyspaceKey.String(keyspace)
130+
}
131+
132+
// cassBatchQueries returns the number of queries in a batch query
133+
// as a KeyValue pair.
134+
func cassBatchQueries(num int) kv.KeyValue {
135+
return cassBatchQueriesKey.Int(num)
136+
}
137+
138+
// cassErrMsg returns the KeyValue pair of an error message
139+
// encountered when executing a query, batch query, or error.
140+
func cassErrMsg(msg string) kv.KeyValue {
141+
return cassErrMsgKey.String(msg)
142+
}
143+
144+
// cassRowsReturned returns the KeyValue pair of the number of rows
145+
// returned from a query.
146+
func cassRowsReturned(rows int) kv.KeyValue {
147+
return cassRowsReturnedKey.Int(rows)
148+
}
149+
150+
// cassQueryAttempts returns the KeyValue pair of the number of attempts
151+
// made for a query.
152+
func cassQueryAttempts(num int) kv.KeyValue {
153+
return cassQueryAttemptsKey.Int(num)
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gocql
16+
17+
import (
18+
"github.com/gocql/gocql"
19+
20+
"go.opentelemetry.io/otel/api/global"
21+
"go.opentelemetry.io/otel/api/trace"
22+
)
23+
24+
// TracedSessionConfig provides configuration for sessions
25+
// created with NewSessionWithTracing.
26+
type TracedSessionConfig struct {
27+
tracer trace.Tracer
28+
instrumentQuery bool
29+
instrumentBatch bool
30+
instrumentConnect bool
31+
queryObserver gocql.QueryObserver
32+
batchObserver gocql.BatchObserver
33+
connectObserver gocql.ConnectObserver
34+
}
35+
36+
// TracedSessionOption applies a configuration option to
37+
// the given TracedSessionConfig.
38+
type TracedSessionOption interface {
39+
Apply(*TracedSessionConfig)
40+
}
41+
42+
// TracedSessionOptionFunc is a function type that applies
43+
// a particular configuration to the traced session in question.
44+
type TracedSessionOptionFunc func(*TracedSessionConfig)
45+
46+
// Apply will apply the TracedSessionOptionFunc to c, the given
47+
// TracedSessionConfig.
48+
func (o TracedSessionOptionFunc) Apply(c *TracedSessionConfig) {
49+
o(c)
50+
}
51+
52+
// ------------------------------------------ TracedSessionOptions
53+
54+
// WithQueryObserver sets an additional QueryObserver to the session configuration. Use this if
55+
// there is an existing QueryObserver that you would like called. It will be called after the
56+
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
57+
func WithQueryObserver(observer gocql.QueryObserver) TracedSessionOption {
58+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
59+
cfg.queryObserver = observer
60+
})
61+
}
62+
63+
// WithBatchObserver sets an additional BatchObserver to the session configuration. Use this if
64+
// there is an existing BatchObserver that you would like called. It will be called after the
65+
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
66+
func WithBatchObserver(observer gocql.BatchObserver) TracedSessionOption {
67+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
68+
cfg.batchObserver = observer
69+
})
70+
}
71+
72+
// WithConnectObserver sets an additional ConnectObserver to the session configuration. Use this if
73+
// there is an existing ConnectObserver that you would like called. It will be called after the
74+
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
75+
func WithConnectObserver(observer gocql.ConnectObserver) TracedSessionOption {
76+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
77+
cfg.connectObserver = observer
78+
})
79+
}
80+
81+
// WithTracer will set tracer to be the tracer used to create spans
82+
// for query, batch query, and connection instrumentation.
83+
// Defaults to global.Tracer("go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql").
84+
func WithTracer(tracer trace.Tracer) TracedSessionOption {
85+
return TracedSessionOptionFunc(func(c *TracedSessionConfig) {
86+
c.tracer = tracer
87+
})
88+
}
89+
90+
// WithQueryInstrumentation will enable and disable instrumentation of
91+
// queries. Defaults to enabled.
92+
func WithQueryInstrumentation(enabled bool) TracedSessionOption {
93+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
94+
cfg.instrumentQuery = enabled
95+
})
96+
}
97+
98+
// WithBatchInstrumentation will enable and disable insturmentation of
99+
// batch queries. Defaults to enabled.
100+
func WithBatchInstrumentation(enabled bool) TracedSessionOption {
101+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
102+
cfg.instrumentBatch = enabled
103+
})
104+
}
105+
106+
// WithConnectInstrumentation will enable and disable instrumentation of
107+
// connection attempts. Defaults to enabled.
108+
func WithConnectInstrumentation(enabled bool) TracedSessionOption {
109+
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
110+
cfg.instrumentConnect = enabled
111+
})
112+
}
113+
114+
// ------------------------------------------ Private Functions
115+
116+
func configure(options ...TracedSessionOption) *TracedSessionConfig {
117+
config := &TracedSessionConfig{
118+
tracer: global.Tracer(instrumentationName),
119+
instrumentQuery: true,
120+
instrumentBatch: true,
121+
instrumentConnect: true,
122+
}
123+
124+
for _, apply := range options {
125+
apply.Apply(config)
126+
}
127+
128+
return config
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package gocql provides functions to instrument the gocql/gocql package
16+
// (https://github.com/gocql/gocql).
17+
//
18+
package gocql // import "go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## Integration Example
2+
3+
### To run the example:
4+
1. `cd` into the example directory.
5+
2. Run `docker-compose up`.
6+
3. Wait for cassandra to listen for cql clients with the following message in the logs:
7+
8+
```
9+
Server.java:159 - Starting listening for CQL clients on /0.0.0.0:9042 (unencrypted)...
10+
```
11+
12+
4. Run the example using `go run .`.
13+
14+
5. You can view the spans in the browser at `localhost:9411` and the metrics at `localhost:2222`.
15+
16+
### When you're done:
17+
1. `ctrl+c` to stop the example program.
18+
2. `docker-compose down` to stop cassandra and zipkin.

0 commit comments

Comments
 (0)