Add structured logging support to promhttp
In order to better support the standard library `log/slog` add a
new interface to the `promhttp` `HandlerOpts`.
Signed-off-by: SuperQ <superq@gmail.com>
diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go
index e598e66..e914d69 100644
--- a/prometheus/promhttp/http.go
+++ b/prometheus/promhttp/http.go
@@ -168,6 +168,9 @@
if opts.ErrorLog != nil {
opts.ErrorLog.Println("error gathering metrics:", err)
}
+ if opts.StructuredErrorLog != nil {
+ opts.StructuredErrorLog.Error("error gathering metrics", "error", err)
+ }
errCnt.WithLabelValues("gathering").Inc()
switch opts.ErrorHandling {
case PanicOnError:
@@ -197,6 +200,9 @@
if opts.ErrorLog != nil {
opts.ErrorLog.Println("error getting writer", err)
}
+ if opts.StructuredErrorLog != nil {
+ opts.StructuredErrorLog.Error("error getting writer", "error", err)
+ }
w = io.Writer(rsp)
encodingHeader = string(Identity)
}
@@ -218,6 +224,9 @@
if opts.ErrorLog != nil {
opts.ErrorLog.Println("error encoding and sending metric family:", err)
}
+ if opts.StructuredErrorLog != nil {
+ opts.StructuredErrorLog.Error("error encoding and sending metric family", "error", err)
+ }
errCnt.WithLabelValues("encoding").Inc()
switch opts.ErrorHandling {
case PanicOnError:
@@ -344,6 +353,12 @@
Println(v ...interface{})
}
+// StructuredLogger is a minimal interface HandlerOpts needs for structured
+// logging. This is implementd by the standard library log/slog.Logger type.
+type StructuredLogger interface {
+ Error(msg string, args ...any)
+}
+
// HandlerOpts specifies options how to serve metrics via an http.Handler. The
// zero value of HandlerOpts is a reasonable default.
type HandlerOpts struct {
@@ -354,6 +369,9 @@
// latter, create a Logger implementation that detects a
// prometheus.MultiError and formats the contained errors into one line.
ErrorLog Logger
+ // StructuredErrorLog StructuredLogger specifies an optional structured log
+ // handler.
+ StructuredErrorLog StructuredLogger
// ErrorHandling defines how errors are handled. Note that errors are
// logged regardless of the configured ErrorHandling provided ErrorLog
// is not nil.
diff --git a/prometheus/promhttp/http_test.go b/prometheus/promhttp/http_test.go
index 3ad2d1d..35a6bf2 100644
--- a/prometheus/promhttp/http_test.go
+++ b/prometheus/promhttp/http_test.go
@@ -20,8 +20,10 @@
"fmt"
"io"
"log"
+ "log/slog"
"net/http"
"net/http/httptest"
+ "os"
"strings"
"testing"
"time"
@@ -130,25 +132,30 @@
logBuf := &bytes.Buffer{}
logger := log.New(logBuf, "", 0)
+ slogger := slog.New(slog.NewTextHandler(os.Stderr, nil))
+
writer := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/", nil)
request.Header.Add("Accept", "test/plain")
mReg := &mockTransactionGatherer{g: reg}
errorHandler := HandlerForTransactional(mReg, HandlerOpts{
- ErrorLog: logger,
- ErrorHandling: HTTPErrorOnError,
- Registry: reg,
+ ErrorLog: logger,
+ StructuredErrorLog: slogger,
+ ErrorHandling: HTTPErrorOnError,
+ Registry: reg,
})
continueHandler := HandlerForTransactional(mReg, HandlerOpts{
- ErrorLog: logger,
- ErrorHandling: ContinueOnError,
- Registry: reg,
+ ErrorLog: logger,
+ StructuredErrorLog: slogger,
+ ErrorHandling: ContinueOnError,
+ Registry: reg,
})
panicHandler := HandlerForTransactional(mReg, HandlerOpts{
- ErrorLog: logger,
- ErrorHandling: PanicOnError,
- Registry: reg,
+ ErrorLog: logger,
+ StructuredErrorLog: slogger,
+ ErrorHandling: PanicOnError,
+ Registry: reg,
})
// Expect gatherer not touched.
if got := mReg.gatherInvoked; got != 0 {