Skip to content

Commit 96322cd

Browse files
authored
feat: add observer constructor (#251)
1 parent 3be94cf commit 96322cd

File tree

4 files changed

+142
-26
lines changed

4 files changed

+142
-26
lines changed

README.md

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,13 @@ Here is an example of how one might use it:
205205

206206
```go
207207
func yourAction(c *cli.Context) error {
208-
obsvr, err := newObserver(c)
208+
obsvr, err := observe.NewFromCLI(c, "my-service", &observe.Options{
209+
LogTimestamps: true,
210+
StatsRuntime: true,
211+
TracingAttrs: []attribute.KeyValue{
212+
semconv.ServiceVersionKey.String("1.0.0"),
213+
},
214+
})
209215
if err != nil {
210216
return err
211217
}
@@ -215,29 +221,6 @@ func yourAction(c *cli.Context) error {
215221

216222
return nil
217223
}
218-
219-
func newObserver(c *cli.Context) (*observe.Observer, error) {
220-
log, err := cmd.NewLogger(c)
221-
if err != nil {
222-
return nil, err
223-
}
224-
225-
stats, err := cmd.NewStatter(c, log)
226-
if err != nil {
227-
return nil, err
228-
}
229-
230-
tracer, err := cmd.NewTracer(c, log,
231-
semconv.ServiceNameKey.String("my-service"),
232-
semconv.ServiceVersionKey.String("1.0.0"),
233-
)
234-
if err != nil {
235-
return nil, err
236-
}
237-
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }
238-
239-
return observe.New(log, stats, tracer, tracerCancel), nil
240-
}
241224
```
242225

243226
It also exposes `NewFake` which allows you to pass fake loggers, tracers and statters in your tests easily.

observe/doc.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ Example usage:
1414
return nil, err
1515
}
1616
17+
prof, err := cmd.NewProfiler(c, "my-service", log)
18+
if err != nil {
19+
return nil, err
20+
}
21+
profStop := func() {}
22+
if prof != nil {
23+
profStop = func() { _ = prof.Stop() }
24+
}
25+
1726
tracer, err := cmd.NewTracer(c, log,
1827
semconv.ServiceNameKey.String("my-service"),
1928
semconv.ServiceVersionKey.String("1.0.0"),
@@ -23,7 +32,7 @@ Example usage:
2332
}
2433
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }
2534
26-
return observe.New(log, stats, tracer, tracerCancel), nil
35+
return observe.New(log, stats, tracer, tracerCancel, profStop), nil
2736
}
2837
*/
2938
package observe

observe/example_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/hamba/cmd/v2"
77
"github.com/hamba/cmd/v2/observe"
88
"github.com/urfave/cli/v2"
9+
"go.opentelemetry.io/otel/attribute"
910
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
1011
)
1112

@@ -24,6 +25,15 @@ func ExampleNew() {
2425
return
2526
}
2627

28+
prof, err := cmd.NewProfiler(c, "my-service", log)
29+
if err != nil {
30+
return
31+
}
32+
profStop := func() {}
33+
if prof != nil {
34+
profStop = func() { _ = prof.Stop() }
35+
}
36+
2737
tracer, err := cmd.NewTracer(c, log,
2838
semconv.ServiceNameKey.String("my-service"),
2939
semconv.ServiceVersionKey.String("1.0.0"),
@@ -34,7 +44,25 @@ func ExampleNew() {
3444
}
3545
tracerCancel := func() { _ = tracer.Shutdown(context.Background()) }
3646

37-
obsrv := observe.New(log, stats, tracer, tracerCancel)
47+
obsrv := observe.New(log, stats, tracer, tracerCancel, profStop)
48+
49+
_ = obsrv
50+
}
51+
52+
func ExampleNewFromCLI() {
53+
var c *cli.Context // Get this from your action.
54+
55+
obsrv, err := observe.NewFromCLI(c, "my-service", &observe.Options{
56+
LogTimestamps: true,
57+
StatsRuntime: true,
58+
TracingAttrs: []attribute.KeyValue{
59+
semconv.ServiceVersionKey.String("1.0.0"),
60+
},
61+
})
62+
if err != nil {
63+
// Handle error.
64+
return
65+
}
3866

3967
_ = obsrv
4068
}

observe/observer.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
package observe
22

33
import (
4+
"context"
45
"io"
56
"time"
67

8+
otelpyroscope "github.com/grafana/otel-profiling-go"
9+
"github.com/hamba/cmd/v2"
710
"github.com/hamba/logger/v2"
11+
lctx "github.com/hamba/logger/v2/ctx"
812
"github.com/hamba/statter/v2"
13+
"github.com/hamba/statter/v2/runtime"
14+
"github.com/hamba/statter/v2/tags"
15+
"github.com/urfave/cli/v2"
916
"go.opentelemetry.io/otel"
17+
"go.opentelemetry.io/otel/attribute"
18+
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
1019
"go.opentelemetry.io/otel/trace"
1120
)
1221

22+
// Options optionally configures an observer.
23+
type Options struct {
24+
LogTimeFormat string
25+
LogTimestamps bool
26+
LogCtx []logger.Field
27+
28+
StatsRuntime bool
29+
StatsTags []statter.Tag
30+
31+
TracingAttrs []attribute.KeyValue
32+
}
33+
1334
// Observer contains observability primitives.
1435
type Observer struct {
1536
Log *logger.Logger
@@ -29,6 +50,81 @@ func New(log *logger.Logger, stats *statter.Statter, traceProv trace.TracerProvi
2950
}
3051
}
3152

53+
// NewFromCLI returns an observer with the given observability primitives.
54+
//
55+
//nolint:cyclop // Splitting this will not make it simpler.
56+
func NewFromCLI(cliCtx *cli.Context, svc string, opts *Options) (*Observer, error) {
57+
var closeFns []func()
58+
59+
if opts == nil {
60+
opts = &Options{}
61+
}
62+
63+
// Logger.
64+
log, err := cmd.NewLogger(cliCtx)
65+
if err != nil {
66+
return nil, err
67+
}
68+
if opts.LogTimeFormat != "" {
69+
logger.TimeFormat = opts.LogTimeFormat
70+
}
71+
if opts.LogTimestamps {
72+
closeFns = append(closeFns, log.WithTimestamp())
73+
}
74+
opts.LogCtx = append([]logger.Field{lctx.Str("svc", svc)}, opts.LogCtx...)
75+
log = log.With(opts.LogCtx...)
76+
77+
// Statter.
78+
stats, err := cmd.NewStatter(cliCtx, log)
79+
if err != nil {
80+
for _, fn := range closeFns {
81+
fn()
82+
}
83+
return nil, err
84+
}
85+
closeFns = append(closeFns, func() { _ = stats.Close() })
86+
if opts.StatsRuntime {
87+
go runtime.Collect(stats)
88+
}
89+
opts.StatsTags = append([]statter.Tag{tags.Str("svc", svc)}, opts.StatsTags...)
90+
stats = stats.With("", opts.StatsTags...)
91+
92+
// Profiler.
93+
prof, err := cmd.NewProfiler(cliCtx, svc, log)
94+
if err != nil {
95+
for _, fn := range closeFns {
96+
fn()
97+
}
98+
return nil, err
99+
}
100+
if prof != nil {
101+
closeFns = append(closeFns, func() { _ = prof.Stop() })
102+
}
103+
104+
// Tracer.
105+
opts.TracingAttrs = append(opts.TracingAttrs, semconv.ServiceNameKey.String(svc))
106+
tracer, err := cmd.NewTracer(cliCtx, log, opts.TracingAttrs...)
107+
if err != nil {
108+
for _, fn := range closeFns {
109+
fn()
110+
}
111+
return nil, err
112+
}
113+
closeFns = append(closeFns, func() { _ = tracer.Shutdown(context.Background()) })
114+
115+
var tp trace.TracerProvider = tracer
116+
if prof != nil && tracer != nil {
117+
tp = otelpyroscope.NewTracerProvider(tp)
118+
}
119+
120+
return &Observer{
121+
Log: log,
122+
Stats: stats,
123+
TraceProv: tp,
124+
closeFns: closeFns,
125+
}, nil
126+
}
127+
32128
// Tracer returns a tracer with the given name and options.
33129
// If no trace provider has been set, this function will panic.
34130
func (o *Observer) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {

0 commit comments

Comments
 (0)