Skip to content

Commit

Permalink
feat: add observer constructor (#251)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrwiersma authored Mar 28, 2024
1 parent 3be94cf commit 96322cd
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 26 deletions.
31 changes: 7 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,13 @@ Here is an example of how one might use it:

```go
func yourAction(c *cli.Context) error {
obsvr, err := newObserver(c)
obsvr, err := observe.NewFromCLI(c, "my-service", &observe.Options{
LogTimestamps: true,
StatsRuntime: true,
TracingAttrs: []attribute.KeyValue{
semconv.ServiceVersionKey.String("1.0.0"),
},
})
if err != nil {
return err
}
Expand All @@ -215,29 +221,6 @@ func yourAction(c *cli.Context) error {

return nil
}

func newObserver(c *cli.Context) (*observe.Observer, error) {
log, err := cmd.NewLogger(c)
if err != nil {
return nil, err
}

stats, err := cmd.NewStatter(c, log)
if err != nil {
return nil, err
}

tracer, err := cmd.NewTracer(c, log,
semconv.ServiceNameKey.String("my-service"),
semconv.ServiceVersionKey.String("1.0.0"),
)
if err != nil {
return nil, err
}
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }

return observe.New(log, stats, tracer, tracerCancel), nil
}
```

It also exposes `NewFake` which allows you to pass fake loggers, tracers and statters in your tests easily.
11 changes: 10 additions & 1 deletion observe/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ Example usage:
return nil, err
}
prof, err := cmd.NewProfiler(c, "my-service", log)
if err != nil {
return nil, err
}
profStop := func() {}
if prof != nil {
profStop = func() { _ = prof.Stop() }
}
tracer, err := cmd.NewTracer(c, log,
semconv.ServiceNameKey.String("my-service"),
semconv.ServiceVersionKey.String("1.0.0"),
Expand All @@ -23,7 +32,7 @@ Example usage:
}
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }
return observe.New(log, stats, tracer, tracerCancel), nil
return observe.New(log, stats, tracer, tracerCancel, profStop), nil
}
*/
package observe
30 changes: 29 additions & 1 deletion observe/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hamba/cmd/v2"
"github.com/hamba/cmd/v2/observe"
"github.com/urfave/cli/v2"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

Expand All @@ -24,6 +25,15 @@ func ExampleNew() {
return
}

prof, err := cmd.NewProfiler(c, "my-service", log)
if err != nil {
return
}
profStop := func() {}
if prof != nil {
profStop = func() { _ = prof.Stop() }
}

tracer, err := cmd.NewTracer(c, log,
semconv.ServiceNameKey.String("my-service"),
semconv.ServiceVersionKey.String("1.0.0"),
Expand All @@ -34,7 +44,25 @@ func ExampleNew() {
}
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }

obsrv := observe.New(log, stats, tracer, tracerCancel)
obsrv := observe.New(log, stats, tracer, tracerCancel, profStop)

_ = obsrv
}

func ExampleNewFromCLI() {
var c *cli.Context // Get this from your action.

obsrv, err := observe.NewFromCLI(c, "my-service", &observe.Options{
LogTimestamps: true,
StatsRuntime: true,
TracingAttrs: []attribute.KeyValue{
semconv.ServiceVersionKey.String("1.0.0"),
},
})
if err != nil {
// Handle error.
return
}

_ = obsrv
}
96 changes: 96 additions & 0 deletions observe/observer.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
package observe

import (
"context"
"io"
"time"

otelpyroscope "github.com/grafana/otel-profiling-go"
"github.com/hamba/cmd/v2"
"github.com/hamba/logger/v2"
lctx "github.com/hamba/logger/v2/ctx"
"github.com/hamba/statter/v2"
"github.com/hamba/statter/v2/runtime"
"github.com/hamba/statter/v2/tags"
"github.com/urfave/cli/v2"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
)

// Options optionally configures an observer.
type Options struct {
LogTimeFormat string
LogTimestamps bool
LogCtx []logger.Field

StatsRuntime bool
StatsTags []statter.Tag

TracingAttrs []attribute.KeyValue
}

// Observer contains observability primitives.
type Observer struct {
Log *logger.Logger
Expand All @@ -29,6 +50,81 @@ func New(log *logger.Logger, stats *statter.Statter, traceProv trace.TracerProvi
}
}

// NewFromCLI returns an observer with the given observability primitives.
//
//nolint:cyclop // Splitting this will not make it simpler.
func NewFromCLI(cliCtx *cli.Context, svc string, opts *Options) (*Observer, error) {
var closeFns []func()

if opts == nil {
opts = &Options{}
}

// Logger.
log, err := cmd.NewLogger(cliCtx)
if err != nil {
return nil, err
}
if opts.LogTimeFormat != "" {
logger.TimeFormat = opts.LogTimeFormat
}
if opts.LogTimestamps {
closeFns = append(closeFns, log.WithTimestamp())
}
opts.LogCtx = append([]logger.Field{lctx.Str("svc", svc)}, opts.LogCtx...)
log = log.With(opts.LogCtx...)

// Statter.
stats, err := cmd.NewStatter(cliCtx, log)
if err != nil {
for _, fn := range closeFns {
fn()
}
return nil, err
}
closeFns = append(closeFns, func() { _ = stats.Close() })
if opts.StatsRuntime {
go runtime.Collect(stats)
}
opts.StatsTags = append([]statter.Tag{tags.Str("svc", svc)}, opts.StatsTags...)
stats = stats.With("", opts.StatsTags...)

// Profiler.
prof, err := cmd.NewProfiler(cliCtx, svc, log)
if err != nil {
for _, fn := range closeFns {
fn()
}
return nil, err
}
if prof != nil {
closeFns = append(closeFns, func() { _ = prof.Stop() })
}

// Tracer.
opts.TracingAttrs = append(opts.TracingAttrs, semconv.ServiceNameKey.String(svc))
tracer, err := cmd.NewTracer(cliCtx, log, opts.TracingAttrs...)
if err != nil {
for _, fn := range closeFns {
fn()
}
return nil, err
}
closeFns = append(closeFns, func() { _ = tracer.Shutdown(context.Background()) })

var tp trace.TracerProvider = tracer
if prof != nil && tracer != nil {
tp = otelpyroscope.NewTracerProvider(tp)
}

return &Observer{
Log: log,
Stats: stats,
TraceProv: tp,
closeFns: closeFns,
}, nil
}

// Tracer returns a tracer with the given name and options.
// If no trace provider has been set, this function will panic.
func (o *Observer) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
Expand Down

0 comments on commit 96322cd

Please sign in to comment.