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 19 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
57 changes: 57 additions & 0 deletions instrumentation/github.com/gocql/gocql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## `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"
)

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

}
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
```

In addition to using the convenience function, you can also manually set observers:

```go
host := "localhost"
cluster := gocql.NewCluster(host)
cluster.QueryObserver = otelGocql.NewQueryObserver(nil, &OtelConfig{
Tracer: global.Tracer("github.com/gocql/gocql"),
InstrumentQuery: true,
})
session, err := cluster.CreateSession()
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
```

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("github.com/gocql/gocql")` will be used. |
| `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. |

141 changes: 141 additions & 0 deletions instrumentation/github.com/gocql/gocql/cass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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"

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

// cassHostKey is the key for the span attribute describing
// the host of the cassandra instance being queried.
cassHostKey = kv.Key("cassandra.host")

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

// cassPortKey is the key for the span attribute describing
// the port of the cassandra server being queried.
cassPortKey = kv.Key("cassandra.port")

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

// cassKeyspaceKey is the key for the KeyValue pair describing the
// keyspace of the current session.
cassKeyspaceKey = kv.Key("cassandra.keyspace")

// cassStatementKey is the key for the span attribute describing the
// the statement used to query the cassandra database.
// This attribute will only be found on a span for a query.
cassStatementKey = kv.Key("cassandra.stmt")
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

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

// cassErrMsgKey is the key for the span attribute describing
// the error message from an error encountered when executing a query, batch,
// or connection attempt to the cassandra server.
cassErrMsgKey = kv.Key("cassandra.err.msg")
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

// cassRowsReturnedKey is the key for the span attribute describing the number of rows
// returned on a query to the database.
cassRowsReturnedKey = kv.Key("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("cassandra.attempts")

// cassQueryAttemptNumKey is the key for the span attribute describing
// which attempt the current query is as a 0-based index.
cassQueryAttemptNumKey = kv.Key("cassandra.attempt")
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

// Names of the spans for query, batch query, and connect respectively.
cassQueryName = "cassandra.query"
cassBatchQueryName = "cassandra.batch.query"
cassConnectName = "cassandra.connect"
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
)

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

// cassHost returns the cassandra host as a KeyValue pair.
func cassHost(host string) kv.KeyValue {
return cassHostKey.String(host)
}

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

// cassPort returns the port of the cassandra node being queried
// as a KeyValue pair.
func cassPort(port int) kv.KeyValue {
return cassPortKey.Int(port)
}

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

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

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

// 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)
}

// cassQueryAttemptNum returns the 0-based index attempt number of a
// query as a KeyValue pair.
func cassQueryAttemptNum(num int) kv.KeyValue {
return cassQueryAttemptNumKey.Int(num)
}
130 changes: 130 additions & 0 deletions instrumentation/github.com/gocql/gocql/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 {
otelConfig *OtelConfig
queryObserver gocql.QueryObserver
batchObserver gocql.BatchObserver
connectObserver gocql.ConnectObserver
}

// OtelConfig provides OpenTelemetry configuration.
type OtelConfig struct {
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
Tracer trace.Tracer
InstrumentQuery bool
InstrumentBatch bool
InstrumentConnect bool
}

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

// ------------------------------------------ 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 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 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 func(cfg *TracedSessionConfig) {
cfg.connectObserver = observer
}
}

// ------------------------------------------ Otel Options

// WithTracer will set tracer to be the tracer used to create spans
// for query, batch query, and connection instrumentation.
// Defaults to global.Tracer("github.com/gocql/gocql").
func WithTracer(tracer trace.Tracer) TracedSessionOption {
return func(c *TracedSessionConfig) {
c.otelConfig.Tracer = tracer
}
}

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

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

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

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

func configure(options ...TracedSessionOption) *TracedSessionConfig {
config := &TracedSessionConfig{
otelConfig: otelConfiguration(),
}

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

return config
}

func otelConfiguration() *OtelConfig {
return &OtelConfig{
Tracer: global.Tracer("github.com/gocql/gocql"),
InstrumentQuery: true,
InstrumentBatch: true,
InstrumentConnect: true,
}
}
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