Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instrumentation for github.com/gocql/gocql #137

Merged
merged 25 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
73590f3
Implemented integration using observers and added an dockerized example
Jul 12, 2020
577daf4
began adding metric instrumentation
Jul 13, 2020
0a41bc3
added meter unit tests
Jul 14, 2020
f28b50a
example: Update example to export metrics and trace to prometheus and…
reggiemcdonald Jul 14, 2020
1e4bea1
Added context to connect observer to allow for connect spans to have …
Jul 14, 2020
4d82bce
updated example to use a batched query
Jul 14, 2020
5e327be
added additional metrics
Jul 14, 2020
b74f0c1
added unit to latency
Jul 15, 2020
a9b7e2e
added additional labels and attributes
Jul 15, 2020
98440be
added doc
Jul 16, 2020
c04e34e
added package readme
Jul 16, 2020
7101469
added readme to example
Jul 16, 2020
4f12361
fix formatting
Jul 16, 2020
6b6c736
Remove unecessary export of keyvalue pairs
reggiemcdonald Jul 16, 2020
f7cb0c7
fix typos
Jul 16, 2020
846d51b
Export otelconfig
reggiemcdonald Jul 16, 2020
b94fcaa
Fix lint
reggiemcdonald Jul 16, 2020
20fedd9
Delete prometheus.yml
reggiemcdonald Jul 16, 2020
05dd54f
Fix comments
reggiemcdonald Jul 16, 2020
31e2e1f
Addressed pr feedback by refactoring attributes and labels to conform…
Jul 24, 2020
b3da6e0
Addressed PR feedback by removing constructor functions that take a c…
Jul 24, 2020
f12726b
fix: address pr feedback by setting meter name to package name
Jul 29, 2020
e61e750
fix: add spankindclient to spans
Jul 29, 2020
83c5953
Merge branch 'master' into 91-gocql-observer
MrAlias Jul 29, 2020
6a000df
fix: address pr feedback by correcting package name
Jul 30, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions instrumentation/github.com/gocql/gocql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql`

This package provides tracing and metrics to the golang cassandra client `github.com/gocql/gocql` using the `ConnectObserver`, `QueryObserver` and `BatchObserver` interfaces.

To enable tracing in your application:

```go
package main

import (
"context"

"github.com/gocql/gocql"
otelGocql "go.opentelemetry.io/contrib/github.com/gocql/gocql"
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
)

func main() {
// Create a cluster
host := "localhost"
cluster := gocql.NewCluster(host)

// Create a session with tracing
session, err := otelGocql.NewSessionWithTracing(
context.Background(),
cluster,
// Include any options here
)

// Begin using the session

}
```

You can customize instrumentation by passing any of the following options to `NewSessionWithTracing`:

| Function | Description |
| -------- | ----------- |
| `WithQueryObserver(gocql.QueryObserver)` | Specify an additional QueryObserver to be called. |
| `WithBatchObserver(gocql.BatchObserver)` | Specify an additional BatchObserver to be called. |
| `WithConnectObserver(gocql.ConnectObserver)` | Specify an additional ConnectObserver to be called. |
| `WithTracer(trace.Tracer)` | The tracer to be used to create spans for the gocql session. If not specified, `global.Tracer("go.opentelemetry.io/contrib/github.com/gocql/gocql")` will be used. |
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
| `WithQueryInstrumentation(bool)` | To enable/disable tracing and metrics for queries. |
| `WithBatchInstrumentation(bool)` | To enable/disable tracing and metrics for batch queries. |
| `WithConnectInstrumentation(bool)` | To enable/disable tracing and metrics for new connections. |

151 changes: 151 additions & 0 deletions instrumentation/github.com/gocql/gocql/cass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gocql

import (
"go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/standard"
)

const (
// cassVersionKey is the key for the attribute/label describing
// the cql version.
cassVersionKey = kv.Key("db.cassandra.version")

// cassHostIDKey is the key for the attribute/label describing the id
// of the host being queried.
cassHostIDKey = kv.Key("db.cassandra.host.id")

// cassHostStateKey is the key for the attribute/label describing
// the state of the casssandra server hosting the node being queried.
cassHostStateKey = kv.Key("db.cassandra.host.state")

// cassBatchQueriesKey is the key for the attribute describing
// the number of queries contained within the batch statement.
cassBatchQueriesKey = kv.Key("db.cassandra.batch.queries")

// cassErrMsgKey is the key for the attribute/label describing
// the error message from an error encountered when executing a query, batch,
// or connection attempt to the cassandra server.
cassErrMsgKey = kv.Key("db.cassandra.error.message")

// cassRowsReturnedKey is the key for the span attribute describing the number of rows
// returned on a query to the database.
cassRowsReturnedKey = kv.Key("db.cassandra.rows.returned")

// cassQueryAttemptsKey is the key for the span attribute describing the number of attempts
// made for the query in question.
cassQueryAttemptsKey = kv.Key("db.cassandra.attempts")

// Static span names
cassBatchQueryName = "Batch Query"
cassConnectName = "New Connection"
)

// ------------------------------------------ Connection-level Attributes

// cassDBSystem returns the name of the DB system,
// cassandra, as a KeyValue pair (db.system).
func cassDBSystem() kv.KeyValue {
return standard.DBSystemCassandra
}

// cassPeerName returns the hostname of the cassandra
// server as a standard KeyValue pair (net.peer.name).
func cassPeerName(name string) kv.KeyValue {
return standard.NetPeerNameKey.String(name)
}

// cassPeerPort returns the port number of the cassandra
// server as a standard KeyValue pair (net.peer.port).
func cassPeerPort(port int) kv.KeyValue {
return standard.NetPeerPortKey.Int(port)
}

// cassPeerIP returns the IP address of the cassandra
// server as a standard KeyValue pair (net.peer.ip).
func cassPeerIP(ip string) kv.KeyValue {
return standard.NetPeerIPKey.String(ip)
}

// cassVersion returns the cql version as a KeyValue pair.
func cassVersion(version string) kv.KeyValue {
return cassVersionKey.String(version)
}

// cassHostID returns the id of the cassandra host as a KeyValue pair.
func cassHostID(id string) kv.KeyValue {
return cassHostIDKey.String(id)
}

// cassHostState returns the state of the cassandra host as a KeyValue pair.
func cassHostState(state string) kv.KeyValue {
return cassHostStateKey.String(state)
}

// ------------------------------------------ Call-level attributes

// cassStatement returns the statement made to the cassandra database as a
// standard KeyValue pair (db.statement).
func cassStatement(stmt string) kv.KeyValue {
return standard.DBStatementKey.String(stmt)
}

// cassDBOperation returns the batch query operation
// as a standard KeyValue pair (db.operation). This is used in lieu of a
// db.statement, which is not feasible to include in a span for a batch query
// because there can be n different query statements in a batch query.
func cassBatchQueryOperation() kv.KeyValue {
cassBatchQueryOperation := "db.cassandra.batch.query"
return standard.DBOperationKey.String(cassBatchQueryOperation)
}

// cassConnectOperation returns the connect operation
// as a standard KeyValue pair (db.operation). This is used in lieu of a
// db.statement since connection creation does not have a CQL statement.
func cassConnectOperation() kv.KeyValue {
cassConnectOperation := "db.cassandra.connect"
return standard.DBOperationKey.String(cassConnectOperation)
}

// cassKeyspace returns the keyspace of the session as
// a standard KeyValue pair (db.cassandra.keyspace).
func cassKeyspace(keyspace string) kv.KeyValue {
return standard.DBCassandraKeyspaceKey.String(keyspace)
}

// cassBatchQueries returns the number of queries in a batch query
// as a KeyValue pair.
func cassBatchQueries(num int) kv.KeyValue {
return cassBatchQueriesKey.Int(num)
}

// cassErrMsg returns the KeyValue pair of an error message
// encountered when executing a query, batch query, or error.
func cassErrMsg(msg string) kv.KeyValue {
return cassErrMsgKey.String(msg)
}

// cassRowsReturned returns the KeyValue pair of the number of rows
// returned from a query.
func cassRowsReturned(rows int) kv.KeyValue {
return cassRowsReturnedKey.Int(rows)
}

// cassQueryAttempts returns the KeyValue pair of the number of attempts
// made for a query.
func cassQueryAttempts(num int) kv.KeyValue {
return cassQueryAttemptsKey.Int(num)
}
129 changes: 129 additions & 0 deletions instrumentation/github.com/gocql/gocql/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gocql

import (
"github.com/gocql/gocql"

"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/trace"
)

// TracedSessionConfig provides configuration for sessions
// created with NewSessionWithTracing.
type TracedSessionConfig struct {
tracer trace.Tracer
instrumentQuery bool
instrumentBatch bool
instrumentConnect bool
queryObserver gocql.QueryObserver
batchObserver gocql.BatchObserver
connectObserver gocql.ConnectObserver
}

// TracedSessionOption applies a configuration option to
// the given TracedSessionConfig.
type TracedSessionOption interface {
Apply(*TracedSessionConfig)
}

// TracedSessionOptionFunc is a function type that applies
// a particular configuration to the traced session in question.
type TracedSessionOptionFunc func(*TracedSessionConfig)

// Apply will apply the TracedSessionOptionFunc to c, the given
// TracedSessionConfig.
func (o TracedSessionOptionFunc) Apply(c *TracedSessionConfig) {
o(c)
}

// ------------------------------------------ TracedSessionOptions

// WithQueryObserver sets an additional QueryObserver to the session configuration. Use this if
// there is an existing QueryObserver that you would like called. It will be called after the
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
func WithQueryObserver(observer gocql.QueryObserver) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.queryObserver = observer
})
}

// WithBatchObserver sets an additional BatchObserver to the session configuration. Use this if
// there is an existing BatchObserver that you would like called. It will be called after the
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
func WithBatchObserver(observer gocql.BatchObserver) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.batchObserver = observer
})
}

// WithConnectObserver sets an additional ConnectObserver to the session configuration. Use this if
// there is an existing ConnectObserver that you would like called. It will be called after the
// OpenTelemetry implementation, if it is not nil. Defaults to nil.
func WithConnectObserver(observer gocql.ConnectObserver) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.connectObserver = observer
})
}

// WithTracer will set tracer to be the tracer used to create spans
// for query, batch query, and connection instrumentation.
// Defaults to global.Tracer("go.opentelemetry.io/contrib/github.com/gocql/gocql").
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
func WithTracer(tracer trace.Tracer) TracedSessionOption {
return TracedSessionOptionFunc(func(c *TracedSessionConfig) {
c.tracer = tracer
})
}

// WithQueryInstrumentation will enable and disable instrumentation of
// queries. Defaults to enabled.
func WithQueryInstrumentation(enabled bool) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.instrumentQuery = enabled
})
}

// WithBatchInstrumentation will enable and disable insturmentation of
// batch queries. Defaults to enabled.
func WithBatchInstrumentation(enabled bool) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.instrumentBatch = enabled
})
}

// WithConnectInstrumentation will enable and disable instrumentation of
// connection attempts. Defaults to enabled.
func WithConnectInstrumentation(enabled bool) TracedSessionOption {
return TracedSessionOptionFunc(func(cfg *TracedSessionConfig) {
cfg.instrumentConnect = enabled
})
}

// ------------------------------------------ Private Functions

func configure(options ...TracedSessionOption) *TracedSessionConfig {
config := &TracedSessionConfig{
tracer: global.Tracer("go.opentelemetry.io/contrib/github.com/gocql/gocql"),
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
instrumentQuery: true,
instrumentBatch: true,
instrumentConnect: true,
}

for _, apply := range options {
apply.Apply(config)
}

return config
}
18 changes: 18 additions & 0 deletions instrumentation/github.com/gocql/gocql/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package gocql provides functions to instrument the gocql/gocql package
// (https://github.com/gocql/gocql).
//
package gocql // import "go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql"
18 changes: 18 additions & 0 deletions instrumentation/github.com/gocql/gocql/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Integration Example

### To run the example:
1. `cd` into the example directory.
2. Run `docker-compose up`.
3. Wait for cassandra to listen for cql clients with the following message in the logs:

```
Server.java:159 - Starting listening for CQL clients on /0.0.0.0:9042 (unencrypted)...
```

4. Run the example using `go run .`.

5. You can view the spans in the browser at `localhost:9411` and the metrics at `localhost:2222`.

### When you're done:
1. `ctrl+c` to stop the example program.
2. `docker-compose down` to stop cassandra and zipkin.
Loading