Skip to content

Commit be7aa75

Browse files
authored
Add Logging interface to Autometrics (#80)
1 parent 560c75c commit be7aa75

File tree

11 files changed

+159
-12
lines changed

11 files changed

+159
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ specification.
3535
- [Generator] The generator now tries to keep going with instrumentation even if some instrumentation
3636
fails. On error, still _No file will be modified_, and the generator will exit with an error code
3737
and output all the collected errors instead of only the first one.
38+
- [All] `autometrics.Init` function takes an additional argument, a logging interface to decide whether
39+
and how users want autometrics to log events. Passing `nil` for the argument value will use a "No Op"
40+
logger that does not do anything.
3841

3942
### Deprecated
4043

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ And then in your main function initialize the metrics
9898
autometrics.DefBuckets,
9999
autometrics.BuildInfo{Version: "0.4.0", Commit: "anySHA", Branch: "", Service: "myApp"},
100100
nil,
101+
nil,
101102
)
102103
if err != nil {
103104
log.Fatalf("could not initialize autometrics: %s", err)
@@ -273,6 +274,7 @@ func main() {
273274
autometrics.DefBuckets,
274275
autometrics.BuildInfo{Version: "0.4.0", Commit: "anySHA", Branch: "", Service: "myApp"},
275276
nil,
277+
nil,
276278
)
277279
http.Handle("/metrics", promhttp.Handler())
278280
}
@@ -384,6 +386,7 @@ metric. You can use the name of the application or its version for example
384386
autometrics.DefBuckets,
385387
autometrics.BuildInfo{ Version: "2.1.37", Commit: "anySHA", Branch: "", Service: "myApp" },
386388
nil,
389+
nil,
387390
)
388391
```
389392

@@ -447,6 +450,7 @@ passing a non `nil` argument to `autometrics.Init` for the `pushConfiguration`:
447450
+ Period: 1 * time.Second, // Period is only relevant when using OpenTelemetry implementation
448451
+ Timeout: 500 * time.Millisecond, // Timeout is only relevant when using OpenTelementry implementation
449452
+ },
453+
nil,
450454
)
451455
```
452456

@@ -456,6 +460,35 @@ can contact us so we can setup a managed instance of Prometheus for you. We will
456460
give you collector URLs, that will work with both OpenTelemetry and Prometheus; and can be
457461
visualized easily with our explorer as well!
458462

463+
#### Logging
464+
465+
Monitoring/Observability must not crash the application.
466+
467+
So when Autometrics encounters
468+
errors, instead of bubbling it up until the program stops, it will log the error and absorb it.
469+
To leave choice in the logging implementation (depending on your application dependencies),
470+
Autometrics exposes a `Logger` interface you can implement and then inject in the `Init` call
471+
to have the logging you want.
472+
473+
The `Logger` interface is a subset of `slog.Logger` methods, so that most loggers can be used.
474+
Autometrics also provides 2 simple loggers out of the box:
475+
- `NoOpLogger`, which is the default logger and does nothing,
476+
- `PrintLogger` which uses `fmt.Print` to write logging messages on stdout.
477+
478+
To use the `PrintLogger` instead of the `NoOpLogger` for examble, you just have to change
479+
the `Init` call:
480+
481+
``` patch
482+
shutdown, err := autometrics.Init(
483+
nil,
484+
autometrics.DefBuckets,
485+
autometrics.BuildInfo{ Version: "2.1.37", Commit: "anySHA", Branch: "", Service: "myApp" },
486+
nil,
487+
- nil,
488+
+ autometrics.PrintLogger{},
489+
)
490+
```
491+
459492
#### Git hook
460493

461494
As autometrics is a Go generator that modifies the source code when run, it

examples/otel/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func main() {
5151
Branch: Branch,
5252
},
5353
pushConfiguration,
54+
autometrics.PrintLogger{},
5455
)
5556
if err != nil {
5657
log.Fatalf("Failed initialization of autometrics: %s", err)

examples/web/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func main() {
5050
Branch: Branch,
5151
},
5252
pushConfiguration,
53+
autometrics.PrintLogger{},
5354
)
5455
if err != nil {
5556
log.Fatalf("Failed initialization of autometrics: %s", err)

examples/web/cmd/main.go.orig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func main() {
5050
Service: "autometrics-go-example-prometheus"
5151
},
5252
pushConfiguration,
53+
autometrics.PrintLogger{},
5354
)
5455
if err != nil {
5556
log.Fatalf("Failed initialization of autometrics: %s", err)

internal/generate/context.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,7 @@ func detectContextSelectorImpl(ctx *internal.GeneratorContext, argName string, s
320320
}
321321
}
322322
} else {
323-
// TODO: log that autometrics cannot detect multi-nested contexts instead of errorring
324-
// continue
325-
return true, fmt.Errorf("expecting parent to be an identifier, got %s instead", reflect.TypeOf(selector.X).String())
323+
am.GetLogger().Error("expecting parent to be an identifier, got %s instead", reflect.TypeOf(selector.X).String())
326324
}
327325
return false, nil
328326
}

otel/autometrics/otel.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
87
"os"
98
"sync"
109
"time"
1110

1211
"github.com/autometrics-dev/autometrics-go/pkg/autometrics"
12+
"github.com/autometrics-dev/autometrics-go/pkg/autometrics/log"
1313

1414
"go.opentelemetry.io/otel/attribute"
1515
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
@@ -120,6 +120,17 @@ func completeMeterName(meterName string) string {
120120
// the current (otel) package imported at the call site.
121121
type BuildInfo = autometrics.BuildInfo
122122

123+
// Logger is an interface for logging autometrics-related events.
124+
//
125+
// This is a reexport to allow using only the current package at call site.
126+
type Logger = log.Logger
127+
128+
// This is a reexport to allow using only the current package at call site.
129+
type PrintLogger = log.PrintLogger
130+
131+
// This is a reexport to allow using only the current package at call site.
132+
type NoOpLogger = log.NoOpLogger
133+
123134
// PushConfiguration holds meta information about the push-to-collector configuration of the instrumented code.
124135
type PushConfiguration struct {
125136
// URL of the collector to push to. It must be non-empty if this struct is built.
@@ -180,14 +191,19 @@ type PushConfiguration struct {
180191
// Make sure that all the latency targets you want to use for SLOs are
181192
// present in the histogramBuckets array, otherwise the alerts will fail
182193
// to work (they will never trigger).
183-
func Init(meterName string, histogramBuckets []float64, buildInformation BuildInfo, pushConfiguration *PushConfiguration) (context.CancelCauseFunc, error) {
194+
func Init(meterName string, histogramBuckets []float64, buildInformation BuildInfo, pushConfiguration *PushConfiguration, logger log.Logger) (context.CancelCauseFunc, error) {
184195
var err error
185196
newCtx, cancelFunc := context.WithCancelCause(context.Background())
186197
amCtx = newCtx
187198

188199
autometrics.SetCommit(buildInformation.Commit)
189200
autometrics.SetVersion(buildInformation.Version)
190201
autometrics.SetBranch(buildInformation.Branch)
202+
if logger == nil {
203+
autometrics.SetLogger(log.NoOpLogger{})
204+
} else {
205+
autometrics.SetLogger(logger)
206+
}
191207

192208
var pushExporter metric.Exporter
193209
if pushConfiguration != nil {
@@ -334,7 +350,7 @@ func initProvider(pushExporter metric.Exporter, pushConfiguration *PushConfigura
334350
metric.WithResource(autometricsSrc),
335351
), nil
336352
} else {
337-
log.Printf("autometrics: opentelemetry: setting up OTLP push configuration, pushing %s to %s\n",
353+
autometrics.GetLogger().Debug("opentelemetry: setting up OTLP push configuration, pushing %s to %s\n",
338354
autometrics.GetPushJobName(),
339355
autometrics.GetPushJobURL(),
340356
)
@@ -368,7 +384,7 @@ func initProvider(pushExporter metric.Exporter, pushConfiguration *PushConfigura
368384
}
369385

370386
func initPushExporter(pushConfiguration *PushConfiguration) (metric.Exporter, error) {
371-
log.Println("autometrics: opentelemetry: Init: detected push configuration")
387+
autometrics.GetLogger().Debug("opentelemetry: Init: detected push configuration")
372388
if pushConfiguration.CollectorURL == "" {
373389
return nil, errors.New("invalid PushConfiguration: the CollectorURL must be set.")
374390
}

pkg/autometrics/ctx.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,10 @@ func FillTracingAndCallerInfo(ctx context.Context) context.Context {
269269
// NOTE: This also means that goroutines that outlive their as the caller will not have access to parent
270270
// caller information, but hopefully by that point we got all the necessary accesses done.
271271
// If not, it is a convenience we accept to give up to prevent memory usage from exploding.
272-
// TODO: once settled on a login library, log the error instead of ignoring it
273-
_ = PushFunctionName(ctx, callInfo.Current)
272+
err := PushFunctionName(ctx, callInfo.Current)
273+
if err != nil {
274+
GetLogger().Error("adding a function name to the known spans: %s", err)
275+
}
274276

275277
return ctx
276278
}

pkg/autometrics/global_state.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package autometrics // import "github.com/autometrics-dev/autometrics-go/pkg/aut
22

33
import (
44
"fmt"
5+
6+
"github.com/autometrics-dev/autometrics-go/pkg/autometrics/log"
57
)
68

79
// These variables are describing the state of the application being autometricized,
@@ -36,13 +38,24 @@ var (
3638
pushJobName string
3739
pushJobURL string
3840
instrumentedSpans map[spanKey]FunctionID = make(map[spanKey]FunctionID)
41+
logger log.Logger
3942
)
4043

4144
type spanKey struct {
4245
tid TraceID
4346
sid SpanID
4447
}
4548

49+
// GetLogger returns the current logging interface for Autometrics
50+
func GetLogger() log.Logger {
51+
return logger
52+
}
53+
54+
// SetLogger sets the logging interface for Autometrics.
55+
func SetLogger(newLogger log.Logger) {
56+
logger = newLogger
57+
}
58+
4659
// GetVersion returns the version of the codebase being instrumented.
4760
func GetVersion() string {
4861
return version

pkg/autometrics/log/log.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package log // import "github.com/autometrics-dev/autometrics-go/pkg/autometrics/log"
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// Logger is an interface to implement to be able to inject a logger to Autometrics.
9+
// The interface follows the interface of slog.Logger
10+
type Logger interface {
11+
Debug(msg string, args ...any)
12+
DebugContext(ctx context.Context, msg string, args ...any)
13+
Info(msg string, args ...any)
14+
InfoContext(ctx context.Context, msg string, args ...any)
15+
Warn(msg string, args ...any)
16+
WarnContext(ctx context.Context, msg string, args ...any)
17+
Error(msg string, args ...any)
18+
ErrorContext(ctx context.Context, msg string, args ...any)
19+
}
20+
21+
// NoOpLogger is the default logger for Autometrics. It does nothing.
22+
type NoOpLogger struct{}
23+
24+
var _ Logger = NoOpLogger{}
25+
26+
func (_ NoOpLogger) Debug(msg string, args ...any) {}
27+
func (_ NoOpLogger) DebugContext(ctx context.Context, msg string, args ...any) {}
28+
func (_ NoOpLogger) Info(msg string, args ...any) {}
29+
func (_ NoOpLogger) InfoContext(ctx context.Context, msg string, args ...any) {}
30+
func (_ NoOpLogger) Warn(msg string, args ...any) {}
31+
func (_ NoOpLogger) WarnContext(ctx context.Context, msg string, args ...any) {}
32+
func (_ NoOpLogger) Error(msg string, args ...any) {}
33+
func (_ NoOpLogger) ErrorContext(ctx context.Context, msg string, args ...any) {}
34+
35+
// PrintLogger is a simple logger implementation that simply prints the events to stdout
36+
type PrintLogger struct{}
37+
38+
var _ Logger = PrintLogger{}
39+
40+
func (_ PrintLogger) Debug(msg string, args ...any) {
41+
fmt.Printf("Autometrics - Debug: %v", fmt.Sprintf(msg, args...))
42+
}
43+
func (_ PrintLogger) DebugContext(ctx context.Context, msg string, args ...any) {
44+
fmt.Printf("Autometrics - Debug: %v", fmt.Sprintf(msg, args...))
45+
}
46+
func (_ PrintLogger) Info(msg string, args ...any) {
47+
fmt.Printf("Autometrics - Info: %v", fmt.Sprintf(msg, args...))
48+
}
49+
func (_ PrintLogger) InfoContext(ctx context.Context, msg string, args ...any) {
50+
fmt.Printf("Autometrics - Info: %v", fmt.Sprintf(msg, args...))
51+
}
52+
func (_ PrintLogger) Warn(msg string, args ...any) {
53+
fmt.Printf("Autometrics - Warn: %v", fmt.Sprintf(msg, args...))
54+
}
55+
func (_ PrintLogger) WarnContext(ctx context.Context, msg string, args ...any) {
56+
fmt.Printf("Autometrics - Warn: %v", fmt.Sprintf(msg, args...))
57+
}
58+
func (_ PrintLogger) Error(msg string, args ...any) {
59+
fmt.Printf("Autometrics - Error: %v", fmt.Sprintf(msg, args...))
60+
}
61+
func (_ PrintLogger) ErrorContext(ctx context.Context, msg string, args ...any) {
62+
fmt.Printf("Autometrics - Error: %v", fmt.Sprintf(msg, args...))
63+
}

prometheus/autometrics/prometheus.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
87
"os"
98
"sync"
109

1110
"github.com/autometrics-dev/autometrics-go/pkg/autometrics"
11+
"github.com/autometrics-dev/autometrics-go/pkg/autometrics/log"
1212
"github.com/prometheus/client_golang/prometheus"
1313
"github.com/prometheus/client_golang/prometheus/push"
1414
"github.com/prometheus/common/expfmt"
@@ -105,6 +105,17 @@ const (
105105
// the current (prometheus) package imported at the call site.
106106
type BuildInfo = autometrics.BuildInfo
107107

108+
// Logger is an interface for logging autometrics-related events.
109+
//
110+
// This is a reexport to allow using only the current package at call site.
111+
type Logger = log.Logger
112+
113+
// This is a reexport to allow using only the current package at call site.
114+
type PrintLogger = log.PrintLogger
115+
116+
// This is a reexport to allow using only the current package at call site.
117+
type NoOpLogger = log.NoOpLogger
118+
108119
// PushConfiguration holds meta information about the push-to-collector configuration of the instrumented code.
109120
//
110121
// This is a reexport of the autometrics type to allow [Init] to work with only
@@ -131,17 +142,22 @@ type PushConfiguration = autometrics.PushConfiguration
131142
// Make sure that all the latency targets you want to use for SLOs are
132143
// present in the histogramBuckets array, otherwise the alerts will fail
133144
// to work (they will never trigger.)
134-
func Init(reg *prometheus.Registry, histogramBuckets []float64, buildInformation BuildInfo, pushConfiguration *PushConfiguration) (context.CancelCauseFunc, error) {
145+
func Init(reg *prometheus.Registry, histogramBuckets []float64, buildInformation BuildInfo, pushConfiguration *PushConfiguration, logger log.Logger) (context.CancelCauseFunc, error) {
135146
newCtx, cancelFunc := context.WithCancelCause(context.Background())
136147
amCtx = newCtx
137148

138149
autometrics.SetCommit(buildInformation.Commit)
139150
autometrics.SetVersion(buildInformation.Version)
140151
autometrics.SetBranch(buildInformation.Branch)
152+
if logger == nil {
153+
autometrics.SetLogger(log.NoOpLogger{})
154+
} else {
155+
autometrics.SetLogger(logger)
156+
}
141157

142158
pusher = nil
143159
if pushConfiguration != nil {
144-
log.Printf("autometrics: Init: detected push configuration to %s", pushConfiguration.CollectorURL)
160+
autometrics.GetLogger().Debug("Init: detected push configuration to %s", pushConfiguration.CollectorURL)
145161

146162
if pushConfiguration.CollectorURL == "" {
147163
return nil, errors.New("invalid PushConfiguration: the CollectorURL must be set.")

0 commit comments

Comments
 (0)