Skip to content

Commit

Permalink
Add telemetry.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmalloc committed Mar 2, 2024
1 parent 2fc7477 commit 58aaf61
Show file tree
Hide file tree
Showing 15 changed files with 1,241 additions and 9 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ The format is based on [Keep a Changelog], and this project adheres to
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html

## [0.2.1] - 2024-03-02

### Added

- Added `journal.WithTelemetry()` and `kv.WithTelemetry()`, which add logging,
tracing and metrics to an existing journal or key-value store, respectively.

## [0.2.0] - 2024-03-02

### Changed
Expand All @@ -36,6 +43,7 @@ The format is based on [Keep a Changelog], and this project adheres to
[Unreleased]: https://github.com/dogmatiq/persistencekit
[0.1.0]: https://github.com/dogmatiq/persistencekit/releases/tag/v0.1.0
[0.2.0]: https://github.com/dogmatiq/persistencekit/releases/tag/v0.2.0
[0.2.1]: https://github.com/dogmatiq/persistencekit/releases/tag/v0.2.1

<!-- version template
## [0.0.1] - YYYY-MM-DD
Expand Down
11 changes: 8 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.17.4
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.30.1
github.com/aws/aws-sdk-go-v2/service/sso v1.20.1
github.com/dogmatiq/enginekit v0.0.0-20230922231755-43a18006bf1c
github.com/dogmatiq/spruce v0.1.0
github.com/dogmatiq/sqltest v0.3.0
github.com/google/go-cmp v0.6.0
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
)

require (
Expand Down Expand Up @@ -37,10 +43,9 @@ require (
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/onsi/gomega v1.27.10 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
)
32 changes: 26 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dogmatiq/dapper v0.5.0 h1:fO5pSla6FTMO3lP1DrK1/eApUqhHDHzRNn4uuS5+znA=
github.com/dogmatiq/dapper v0.5.0/go.mod h1:JG6I76c95MDSTBBc58xSGFH7WJ4nrdOeAk+Nn62RKsM=
github.com/dogmatiq/enginekit v0.0.0-20230922231755-43a18006bf1c h1:4E5+xPH+qbDnahZ+rYRGhWssB9NXTmwH1FOkinqB0yc=
github.com/dogmatiq/enginekit v0.0.0-20230922231755-43a18006bf1c/go.mod h1:kF8TSuf9H+1/9jWDKcOEy4xAFuI++cAxdB3UF90yROE=
github.com/dogmatiq/spruce v0.1.0 h1:xIcWPJA33Et+qIC1RORjP+8gSNtErdcTr0eEbtFk2oU=
github.com/dogmatiq/spruce v0.1.0/go.mod h1:0+zqOtlidouuzQr2k2Od7RRFR7Sk4p373kVyTtH6ovw=
github.com/dogmatiq/sqltest v0.3.0 h1:DCwyLWfVk/ZHsqq5Itq3H/Lqsh/CIQ6nIRwI4YLywFc=
github.com/dogmatiq/sqltest v0.3.0/go.mod h1:a8Da8NhU4m3lq5Sybhiv+ZQowSnGHWTIJHFNInVtffg=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down Expand Up @@ -175,6 +185,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
Expand All @@ -194,8 +210,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand All @@ -205,8 +223,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -226,8 +244,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand All @@ -252,6 +270,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
Expand Down
128 changes: 128 additions & 0 deletions internal/telemetry/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package telemetry

import (
"fmt"
"log/slog"
"math"
"reflect"

"go.opentelemetry.io/otel/attribute"
"golang.org/x/exp/constraints"
)

// Attr is a telemetry attribute.
type Attr struct {
typ attrType
key string
str string
num uint64
sli any
}

// String returns a string attribute.
func String[T ~string](k string, v T) Attr {
return Attr{
typ: attrTypeString,
key: k,
str: string(v),
}
}

// Stringer returns a string attribute. The value is the result of calling
// v.String().
func Stringer(k string, v fmt.Stringer) Attr {
return String(k, v.String())
}

// Type returns a string attribute set to the name of T.
func Type[T any](k string, v T) Attr {
t := reflect.TypeOf(v)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return String(k, t.String())
}

// Bool returns a boolean attribute.
func Bool[T ~bool](k string, v T) Attr {
var n uint64
if v {
n = 1
}

return Attr{
typ: attrTypeBool,
key: k,
num: n,
}
}

// Int returns an int64 attribute.
func Int[T constraints.Integer](k string, v T) Attr {
return Attr{
typ: attrTypeInt64,
key: k,
num: uint64(v),
}
}

// If conditionally includes an attribute.
func If(cond bool, attr Attr) Attr {
if cond {
return attr
}
return Attr{}
}

// Float returns a float64 attribute.
func Float[T constraints.Float](k string, v T) Attr {
return Attr{
typ: attrTypeFloat64,
key: k,
num: math.Float64bits(float64(v)),
}
}

func (a Attr) otel() (attribute.KeyValue, bool) {
switch a.typ {
case attrTypeNone:
return attribute.KeyValue{}, false
case attrTypeString:
return attribute.String(a.key, a.str), true
case attrTypeBool:
return attribute.Bool(a.key, a.num != 0), true
case attrTypeInt64:
return attribute.Int64(a.key, int64(a.num)), true
case attrTypeFloat64:
return attribute.Float64(a.key, math.Float64frombits(a.num)), true
default:
panic("unknown attribute type")
}
}

func (a Attr) slog() (slog.Attr, bool) {
switch a.typ {
case attrTypeNone:
return slog.Attr{}, false
case attrTypeString:
return slog.String(a.key, a.str), true
case attrTypeBool:
return slog.Bool(a.key, a.num != 0), true
case attrTypeInt64:
return slog.Int64(a.key, int64(a.num)), true
case attrTypeFloat64:
return slog.Float64(a.key, math.Float64frombits(a.num)), true
default:
panic("unknown attribute type")
}
}

type attrType uint8

const (
attrTypeNone attrType = iota
attrTypeString
attrTypeBool
attrTypeInt64
attrTypeFloat64
)
22 changes: 22 additions & 0 deletions internal/telemetry/convention.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package telemetry

import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)

var (
// ReadDirection is an attribute that indicates a read operation.
ReadDirection = metric.WithAttributeSet(
attribute.NewSet(
attribute.String("direction", "read"),
),
)

// WriteDirection is an attribute that indicates a write operation.
WriteDirection = metric.WithAttributeSet(
attribute.NewSet(
attribute.String("direction", "write"),
),
)
)
2 changes: 2 additions & 0 deletions internal/telemetry/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package telemetry is an internal API for observability/instrumentation.
package telemetry
23 changes: 23 additions & 0 deletions internal/telemetry/handle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package telemetry

import (
"fmt"
"sync/atomic"

"github.com/dogmatiq/enginekit/protobuf/uuidpb"
)

var handleCounter atomic.Uint64

// HandleID returns a unique identifier for an open instance of a journal or
// keyspace.
//
// It includes a counter component for easy visual identification by humans, and
// a UUID component for global correlation in observability tools.
func HandleID() string {
return fmt.Sprintf(
"#%d %s",
handleCounter.Add(1),
uuidpb.Generate().String(),
)
}
46 changes: 46 additions & 0 deletions internal/telemetry/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package telemetry

import (
"log/slog"
"slices"

"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)

// Provider provides Recorder instances scoped to particular subsystems.
type Provider struct {
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
Logger *slog.Logger
Attrs []Attr
}

// Recorder returns a new Recorder instance.
//
// pkg is the path to the Go package that is performing the instrumentation. If
// it is an internal package, use the package path of the public parent package
// instead.
//
// name is the one-word name of the subsystem that the recorder is for, for
// example "journal" or "aggregate".
func (p *Provider) Recorder(pkg, name string, attrs ...Attr) *Recorder {
r := &Recorder{
name: "io.dogmatiq.persistencekit." + name,
attrs: append(
slices.Clone(p.Attrs),
attrs...,
),
tracer: p.TracerProvider.Tracer(pkg, tracerVersion),
meter: p.MeterProvider.Meter(pkg, meterVersion),
logger: p.Logger,
}

r.errors = r.Int64Counter(
"errors",
metric.WithDescription("The number of errors that have occurred."),
metric.WithUnit("{error}"),
)

return r
}
Loading

0 comments on commit 58aaf61

Please sign in to comment.