Skip to content

Commit c652332

Browse files
committed
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>
1 parent 97aa049 commit c652332

File tree

2 files changed

+34
-9
lines changed

2 files changed

+34
-9
lines changed

prometheus/promhttp/http.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
168168
if opts.ErrorLog != nil {
169169
opts.ErrorLog.Println("error gathering metrics:", err)
170170
}
171+
if opts.StructuredErrorLog != nil {
172+
opts.StructuredErrorLog.Error("error gathering metrics", "error", err)
173+
}
171174
errCnt.WithLabelValues("gathering").Inc()
172175
switch opts.ErrorHandling {
173176
case PanicOnError:
@@ -197,6 +200,9 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
197200
if opts.ErrorLog != nil {
198201
opts.ErrorLog.Println("error getting writer", err)
199202
}
203+
if opts.StructuredErrorLog != nil {
204+
opts.StructuredErrorLog.Error("error getting writer", "error", err)
205+
}
200206
w = io.Writer(rsp)
201207
encodingHeader = string(Identity)
202208
}
@@ -218,6 +224,9 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
218224
if opts.ErrorLog != nil {
219225
opts.ErrorLog.Println("error encoding and sending metric family:", err)
220226
}
227+
if opts.StructuredErrorLog != nil {
228+
opts.StructuredErrorLog.Error("error encoding and sending metric family", "error", err)
229+
}
221230
errCnt.WithLabelValues("encoding").Inc()
222231
switch opts.ErrorHandling {
223232
case PanicOnError:
@@ -344,6 +353,12 @@ type Logger interface {
344353
Println(v ...interface{})
345354
}
346355

356+
// StructuredLogger is a minimal interface HandlerOpts needs for structured
357+
// logging. This is implementd by the standard library log/slog.Logger type.
358+
type StructuredLogger interface {
359+
Error(msg string, args ...any)
360+
}
361+
347362
// HandlerOpts specifies options how to serve metrics via an http.Handler. The
348363
// zero value of HandlerOpts is a reasonable default.
349364
type HandlerOpts struct {
@@ -354,6 +369,9 @@ type HandlerOpts struct {
354369
// latter, create a Logger implementation that detects a
355370
// prometheus.MultiError and formats the contained errors into one line.
356371
ErrorLog Logger
372+
// StructuredErrorLog StructuredLogger specifies an optional structured log
373+
// handler.
374+
StructuredErrorLog StructuredLogger
357375
// ErrorHandling defines how errors are handled. Note that errors are
358376
// logged regardless of the configured ErrorHandling provided ErrorLog
359377
// is not nil.

prometheus/promhttp/http_test.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"fmt"
2121
"io"
2222
"log"
23+
"log/slog"
2324
"net/http"
2425
"net/http/httptest"
26+
"os"
2527
"strings"
2628
"testing"
2729
"time"
@@ -130,25 +132,30 @@ func TestHandlerErrorHandling(t *testing.T) {
130132
logBuf := &bytes.Buffer{}
131133
logger := log.New(logBuf, "", 0)
132134

135+
slogger := slog.New(slog.NewTextHandler(os.Stderr, nil))
136+
133137
writer := httptest.NewRecorder()
134138
request, _ := http.NewRequest("GET", "/", nil)
135139
request.Header.Add("Accept", "test/plain")
136140

137141
mReg := &mockTransactionGatherer{g: reg}
138142
errorHandler := HandlerForTransactional(mReg, HandlerOpts{
139-
ErrorLog: logger,
140-
ErrorHandling: HTTPErrorOnError,
141-
Registry: reg,
143+
ErrorLog: logger,
144+
StructuredErrorLog: slogger,
145+
ErrorHandling: HTTPErrorOnError,
146+
Registry: reg,
142147
})
143148
continueHandler := HandlerForTransactional(mReg, HandlerOpts{
144-
ErrorLog: logger,
145-
ErrorHandling: ContinueOnError,
146-
Registry: reg,
149+
ErrorLog: logger,
150+
StructuredErrorLog: slogger,
151+
ErrorHandling: ContinueOnError,
152+
Registry: reg,
147153
})
148154
panicHandler := HandlerForTransactional(mReg, HandlerOpts{
149-
ErrorLog: logger,
150-
ErrorHandling: PanicOnError,
151-
Registry: reg,
155+
ErrorLog: logger,
156+
StructuredErrorLog: slogger,
157+
ErrorHandling: PanicOnError,
158+
Registry: reg,
152159
})
153160
// Expect gatherer not touched.
154161
if got := mReg.gatherInvoked; got != 0 {

0 commit comments

Comments
 (0)