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 the database/sql package #88

Merged
merged 52 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9b99e84
Initial instrumentation for database/sql
MrAlias Sep 1, 2021
090633b
Add withClientSpan method to config
MrAlias Sep 1, 2021
3bcfd79
Add trace.go
MrAlias Sep 1, 2021
82e8c38
Use withClientSpan and fallbacks to non-ctx methods
MrAlias Sep 1, 2021
c304e60
Move span names to internal package
MrAlias Sep 1, 2021
b07c297
Add initial integartion test
MrAlias Sep 1, 2021
db8aafb
Add config unit tests
MrAlias Sep 2, 2021
c9a60b4
Target integration tests for spans only
MrAlias Sep 2, 2021
b94da8d
Add integration testing for stmt, rows, and tx
MrAlias Sep 2, 2021
9bab4ee
Add comment for why all spans are clients
MrAlias Sep 3, 2021
a69a5e5
Move config to internal package
MrAlias Sep 3, 2021
c7ba4f8
Revert "Move config to internal package"
MrAlias Sep 3, 2021
9a0bf18
Rename withClientSpan to withSpan
MrAlias Sep 3, 2021
8c91fb7
Add DSN parsing
MrAlias Sep 7, 2021
7f8db95
Update span name to comply with OTel
MrAlias Sep 7, 2021
7afedc2
Update test with new options pattern
MrAlias Sep 7, 2021
18bd66e
Add instrumentation registration function
MrAlias Sep 8, 2021
f13d6dd
Update go.opentelemetry.io/otel* to v1.0.0-RC3
MrAlias Sep 8, 2021
98bfd66
Update int test with base attrs
MrAlias Sep 8, 2021
d491d8c
Document the moniker package
MrAlias Sep 8, 2021
9148a2c
Add package documentation for the test package
MrAlias Sep 8, 2021
47a0372
Merge branch 'main' into splunksql
MrAlias Sep 8, 2021
609043a
Run go mod tidy for splunksql
MrAlias Sep 8, 2021
0c5306e
Add copyright notice to all source files
MrAlias Sep 8, 2021
78b533c
Comment exported objects in the moniker pkg
MrAlias Sep 8, 2021
8f801e5
Fix exported comments in the dbsystem pkg
MrAlias Sep 8, 2021
3a6c749
Add comment for the Option type
MrAlias Sep 8, 2021
6760e4b
Skip staticcheck for backwards compatible iface support
MrAlias Sep 8, 2021
4fa513c
Quite lint errors in suite
MrAlias Sep 8, 2021
564371f
Fix lint issues with the test package
MrAlias Sep 8, 2021
70052e7
Rename test files
MrAlias Sep 8, 2021
f3cd853
Nolint in test package
MrAlias Sep 8, 2021
78b59a0
No gocritic lint of value receivers
MrAlias Sep 8, 2021
bc34352
Remove unused parameters and add nolint comments
MrAlias Sep 8, 2021
872d359
make gendependabot
MrAlias Sep 8, 2021
8499a23
Add config tests
MrAlias Sep 8, 2021
a3ea4cf
Add unit tests for splunksql
MrAlias Sep 8, 2021
e2f4b8a
Add remaining unit tests
MrAlias Sep 9, 2021
c7f82b2
Fix dependabot config
MrAlias Sep 9, 2021
9bae0bd
Add splunksql to README
MrAlias Sep 9, 2021
af998bf
Merge branch 'main' into splunksql
MrAlias Sep 10, 2021
ec7b466
Merge branch 'main' into splunksql
MrAlias Sep 13, 2021
a88d3da
Fix go.sum entries
MrAlias Sep 13, 2021
9377a57
Merge branch 'main' into splunksql
MrAlias Sep 14, 2021
5ec344b
Consolidate mock types to single file for test pkg
MrAlias Sep 16, 2021
509ede3
Merge branch 'splunksql' of github.com:MrAlias/splunk-otel-go into sp…
MrAlias Sep 16, 2021
244b998
Use assertion to test span kind
MrAlias Sep 17, 2021
2e11e37
Send configuration errors to generic OTel error handler
MrAlias Sep 17, 2021
ff98596
Move dbsystem package into splunksql
MrAlias Sep 17, 2021
d468394
Move transport pkg into splunksql
MrAlias Sep 17, 2021
7b6c868
Remove rows instrumentation
MrAlias Sep 17, 2021
5e5e09e
Comment nolint comments
MrAlias Sep 17, 2021
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
6 changes: 5 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ updates:
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/build"
directory: "/instrumentation/database/sql/splunksql"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/instrumentation/database/sql/splunksql/test"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Supported libraries are listed

Additional recommended Splunk specific instrumentations:

- [`splunksql`](./instrumentation/database/sql/splunksql)
pellared marked this conversation as resolved.
Show resolved Hide resolved
- [`splunkhttp`](./instrumentation/net/http/splunkhttp)

## Manual Instrumentation
Expand Down
267 changes: 267 additions & 0 deletions instrumentation/database/sql/splunksql/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// Copyright Splunk Inc.
//
// 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 splunksql

import (
"context"
"fmt"
"net"
"net/url"
"strconv"
"strings"

splunkotel "github.com/signalfx/splunk-otel-go"
"github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql/internal/moniker"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)

// instrumentationName is the instrumentation library identifier for a Tracer.
const instrumentationName = "github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql"

// traceConfig contains tracing configuration options.
type traceConfig struct {
TracerProvider trace.TracerProvider

DBName string
Attributes []attribute.KeyValue
}

func newTraceConfig(options ...Option) traceConfig {
var c traceConfig
for _, o := range options {
if o != nil {
o.apply(&c)
}
}
if c.TracerProvider == nil {
c.TracerProvider = otel.GetTracerProvider()
}
return c
}

// tracer returns an OTel tracer from the appropriate TracerProvider.
//
// If the passed context contains a span, the TracerProvider that created the
// tracer that created that span will be used. Otherwise, the TracerProvider
// from c is used.
func (c traceConfig) tracer(ctx context.Context) trace.Tracer {
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
return span.TracerProvider().Tracer(
instrumentationName,
trace.WithInstrumentationVersion(splunkotel.Version()),
)
}
return c.TracerProvider.Tracer(
instrumentationName,
trace.WithInstrumentationVersion(splunkotel.Version()),
)
}

// withSpan wraps the function f with a span.
func (c traceConfig) withSpan(ctx context.Context, m moniker.Span, f func(context.Context) error, opts ...trace.SpanStartOption) error {
opts = append([]trace.SpanStartOption{trace.WithAttributes(c.Attributes...)}, opts...)
// From the specification: span kind MUST always be CLIENT.
opts = append(opts, trace.WithSpanKind(trace.SpanKindClient))

var (
err error
span trace.Span
)
ctx, span = c.tracer(ctx).Start(ctx, c.spanName(m), opts...)
defer func() {
handleErr(span, err)
span.End()
}()

err = f(ctx)
return err
}

// spanName returns the OpenTelemetry compliant span name.
func (c traceConfig) spanName(m moniker.Span) string {
// From the OpenTelemetry semantic conventions
// (https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1/specification/trace/semantic_conventions/database.md):
//
// > The **span name** SHOULD be set to a low cardinality value representing the statement executed on the database.
// > It MAY be a stored procedure name (without arguments), DB statement without variable arguments, operation name, etc.
// > Since SQL statements may have very high cardinality even without arguments, SQL spans SHOULD be named the
// > following way, unless the statement is known to be of low cardinality:
// > `<db.operation> <db.name>.<db.sql.table>`, provided that `db.operation` and `db.sql.table` are available.
// > If `db.sql.table` is not available due to its semantics, the span SHOULD be named `<db.operation> <db.name>`.
// > It is not recommended to attempt any client-side parsing of `db.statement` just to get these properties,
// > they should only be used if the library being instrumented already provides them.
// > When it's otherwise impossible to get any meaningful span name, `db.name` or the tech-specific database name MAY be used.
//
// The database/sql package does not provide the database operation nor
// the SQL table the operation is being performed on during a call. It
// would require client-side parsing of the statement to determine these
// properties. Therefore, the database name is used if it is known.
if c.DBName != "" {
return c.DBName
}

// The database name is not known. Fallback to the known client-side
// operation being performed. This will comply with the low cardinality
// recommendation of the specification.
return m.String()
}

// Option applies options to a tracing configuration.
type Option interface {
apply(*traceConfig)
}

type optionFunc func(*traceConfig)

func (o optionFunc) apply(c *traceConfig) {
o(c)
}

// WithTracerProvider returns an Option that sets the TracerProvider used with
// this instrumentation library.
func WithTracerProvider(tp trace.TracerProvider) Option {
return optionFunc(func(c *traceConfig) {
c.TracerProvider = tp
})
}

// WithAttributes returns an Option that appends attr to the attributes set
// for every span created with this instrumentation library.
func WithAttributes(attr []attribute.KeyValue) Option {
return optionFunc(func(c *traceConfig) {
c.Attributes = append(c.Attributes, attr...)
})
}

// withRegistrationConfig returns an Option that sets database attributes
// required and recommended by the OpenTelemetry semantic conventions based on
// the information instrumentation registered.
func withRegistrationConfig(regCfg InstrumentationConfig, dsn string) Option {
var connCfg ConnectionConfig
if regCfg.DSNParser != nil {
var err error
connCfg, err = regCfg.DSNParser(dsn)
otel.Handle(err)
} else {
// Fallback. This is a best effort attempt if we do not know how to
// explicitly parse the DSN.
connCfg, _ = urlDSNParse(dsn)
}

attrs, err := connCfg.Attributes()
otel.Handle(err)
attrs = append(attrs, regCfg.DBSystem.Attribute())

return optionFunc(func(c *traceConfig) {
c.DBName = connCfg.Name
c.Attributes = append(c.Attributes, attrs...)
})
}

// ConnectionConfig are the relevant settings parsed from a database
// connection.
type ConnectionConfig struct {
// Name of the database being accessed.
Name string
// ConnectionString is the sanitized connection string (all credentials
// have been redacted) used to connect to the database.
ConnectionString string
// User is the username used to access the database.
User string
// Host is the IP or hostname of the database.
Host string
// Port is the port the database is lisening on.
Port int
// NetTransport is the transport protocol used to connect to the database.
NetTransport NetTransport
}

// Attributes returns the connection settings as attributes compliant with
// OpenTelemetry semantic coventions. If the settings do not conform to
// OpenTelemetry requirements an error is returned with a partial list of
// attributes that do conform.
func (c ConnectionConfig) Attributes() ([]attribute.KeyValue, error) { // nolint: gocritic
var attrs []attribute.KeyValue
var errs []string
if c.Name != "" {
attrs = append(attrs, semconv.DBNameKey.String(c.Name))
}
if c.ConnectionString != "" {
attrs = append(attrs, semconv.DBConnectionStringKey.String(c.ConnectionString))
}
if c.User != "" {
attrs = append(attrs, semconv.DBUserKey.String(c.User))
}
if c.Host != "" {
if ip := net.ParseIP(c.Host); ip != nil {
attrs = append(attrs, semconv.NetPeerIPKey.String(ip.String()))
} else {
attrs = append(attrs, semconv.NetPeerNameKey.String(c.Host))
}
} else {
errs = append(errs, "missing required peer IP or hostname")
}
if c.Port > 0 {
attrs = append(attrs, semconv.NetPeerPortKey.Int(c.Port))
}
attrs = append(attrs, c.NetTransport.Attribute())

var err error
if len(errs) > 0 {
err = fmt.Errorf("invalid connection config: %s", strings.Join(errs, ", "))
}
return attrs, err
}

func urlDSNParse(dataSourceName string) (ConnectionConfig, error) {
var connCfg ConnectionConfig
u, err := url.Parse(dataSourceName)
if err != nil {
return connCfg, err
}

connCfg.Host = u.Hostname()
if p, err := strconv.Atoi(u.Port()); err == nil {
connCfg.Port = p
}

if u.User != nil {
connCfg.User = u.User.Username()
if _, ok := u.User.Password(); ok {
// Redact password.
u.User = url.User(u.User.Username())
}
}

connCfg.ConnectionString = u.String()

return connCfg, nil
}

// DSNParser processes a driver-specific data source name into
// connection-level attributes conforming with the OpenTelemetry semantic
// conventions.
type DSNParser func(dataSourceName string) (ConnectionConfig, error)

// InstrumentationConfig is the setup configuration for the instrumentation of
// a database driver.
type InstrumentationConfig struct {
// DBSystem is the database system being registered.
DBSystem DBSystem
DSNParser DSNParser
}
Loading