11package observe
22
33import (
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.
1435type 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.
34130func (o * Observer ) Tracer (name string , opts ... trace.TracerOption ) trace.Tracer {
0 commit comments