Skip to content

Conversation

@tjhop
Copy link
Contributor

@tjhop tjhop commented Jul 1, 2025

Addresses: prometheus/prometheus#16766

As brought up in the linked issue, it seems that upstream slog handles
time.Duration values in a sub-optimal way in the JSON handler. To
improve handling of duration values in a consistent way across the
ecosystem, we should ensure that duration values are handled the same
way across all supported log formats (json and logfmt).

This change ensures that any key containing a value that is a
time.Duration is reformatted as a boring string value, explicitly
formatted as a Go duration string (ie, "1d2h3m") by calling the
String() method on the duration.

Example test output pre/post patch:

Before:

~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
time=2025-06-30T22:11:01.682-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s

{"time":"2025-06-30T22:11:01.683153372-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":90000000000,"duration_string":"1m30s"}

    slog_test.go:128:
                Error Trace:    /home/tjhop/go/src/github.com/prometheus/common/promslog/slog_test.go:128
                Error:          "{\"time\":\"2025-06-30T22:11:01.683153372-04:00\",\"level\":\"INFO\",\"source\":\"slog_test.go:123\",\"msg\":\"duration testing\",\"duration_raw\":90000000000,\"duration_string\":\"1m30s\"}\n" should not contain "\"duration_raw\":90000000000"
                Test:           TestDurationValues
                Messages:       Expected duration to be output as Go duration string "1m30s", got "90000000000"
--- FAIL: TestDurationValues (0.00s)
FAIL
FAIL    github.com/prometheus/common/promslog   0.018s
FAIL

After:

~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
time=2025-06-30T22:13:03.714-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s

{"time":"2025-06-30T22:13:03.714880745-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":"1m30s","duration_string":"1m30s"}

--- PASS: TestDurationValues (0.00s)
PASS
ok      github.com/prometheus/common/promslog   1.014s

Signed-off-by: TJ Hoplock t.hoplock@gmail.com

Addresses: prometheus/prometheus#16766

As brought up in the linked issue, it seems that upstream slog handles
time.Duration values in a sub-optimal way in the JSON handler. To
improve handling of duration values in a consistent way across the
ecosystem, we should ensure that duration values are handled the same
way across all supported log formats (json and logfmt).

This change ensures that any key containing a value that is a
time.Duration is reformatted as a boring string value, explicitly
formatted as a Go duration string (ie, "1d2h3m") by calling the
`String()` method on the duration.

Example test output pre/post patch:

Before:
```
~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
time=2025-06-30T22:11:01.682-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s

{"time":"2025-06-30T22:11:01.683153372-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":90000000000,"duration_string":"1m30s"}

    slog_test.go:128:
                Error Trace:    /home/tjhop/go/src/github.com/prometheus/common/promslog/slog_test.go:128
                Error:          "{\"time\":\"2025-06-30T22:11:01.683153372-04:00\",\"level\":\"INFO\",\"source\":\"slog_test.go:123\",\"msg\":\"duration testing\",\"duration_raw\":90000000000,\"duration_string\":\"1m30s\"}\n" should not contain "\"duration_raw\":90000000000"
                Test:           TestDurationValues
                Messages:       Expected duration to be output as Go duration string "1m30s", got "90000000000"
--- FAIL: TestDurationValues (0.00s)
FAIL
FAIL    github.com/prometheus/common/promslog   0.018s
FAIL
```

After:

```
~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
time=2025-06-30T22:13:03.714-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s

{"time":"2025-06-30T22:13:03.714880745-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":"1m30s","duration_string":"1m30s"}

--- PASS: TestDurationValues (0.00s)
PASS
ok      github.com/prometheus/common/promslog   1.014s
```

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
Copy link
Member

@machine424 machine424 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, some suggestions.

Addresses PR feedback

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
@tjhop
Copy link
Contributor Author

tjhop commented Jul 1, 2025

Tests improved:

# before patch
~/go/src/github.com/prometheus/common (fix/promslog-consistent-duration-formatting [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
=== RUN   TestDurationValues/logfmt_duration_testing
=== RUN   TestDurationValues/json_duration_testing
    slog_test.go:122:
                Error Trace:    /home/tjhop/go/src/github.com/prometheus/common/promslog/slog_test.go:122
                Error:          "{\"time\":\"2025-07-01T15:31:37.545281226-04:00\",\"level\":\"INFO\",\"source\":\"slog_test.go:121\",\"msg\":\"duration testing\",\"duration_raw\":90000000000,\"duration_string\":\"1m30s\"}\n" does not contain "\"duration_raw\":\"1m30s\""
                Test:           TestDurationValues/json_duration_testing
--- FAIL: TestDurationValues (0.00s)
    --- PASS: TestDurationValues/logfmt_duration_testing (0.00s)
    --- FAIL: TestDurationValues/json_duration_testing (0.00s)
FAIL
FAIL    github.com/prometheus/common/promslog   0.015s
FAIL
# after patch
~/go/src/github.com/prometheus/common (fix/promslog-consistent-duration-formatting [ U ]) -> go test -v -race ./promslog -run TestDurationValues
=== RUN   TestDurationValues
=== RUN   TestDurationValues/logfmt_duration_testing
=== RUN   TestDurationValues/json_duration_testing
--- PASS: TestDurationValues (0.00s)
    --- PASS: TestDurationValues/logfmt_duration_testing (0.00s)
    --- PASS: TestDurationValues/json_duration_testing (0.00s)
PASS
ok      github.com/prometheus/common/promslog   1.015s

Copy link
Member

@machine424 machine424 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thanks.

Some nits to isolate the subtests and test both fields:

diff --git a/promslog/slog_test.go b/promslog/slog_test.go
index 2efb82d..5ffe3cf 100644
--- a/promslog/slog_test.go
+++ b/promslog/slog_test.go
@@ -96,27 +96,25 @@ func getLogEntryLevelCounts(s string, re *regexp.Regexp) map[string]int {
 }
 
 func TestDurationValues(t *testing.T) {
-       var (
-               buf    bytes.Buffer
-               dur, _ = time.ParseDuration("1m30s")
-               config = &Config{
-                       Writer: &buf,
-                       Format: NewFormat(),
-               }
-       )
+       dur, err := time.ParseDuration("1m30s")
+       require.NoError(t, err)
 
        tests := map[string]struct {
-               want      string
                logFormat string
+               want      string
        }{
-               "logfmt_duration_testing": {want: "duration_raw=1m30s", logFormat: "logfmt"},
-               "json_duration_testing":   {want: "\"duration_raw\":\"1m30s\"", logFormat: "json"},
+               "logfmt_duration_testing": {logFormat: "logfmt", want: "duration_raw=1m30s duration_string=1m30s"},
+               "json_duration_testing":   {logFormat: "json", want: "\"duration_raw\":\"1m30s\",\"duration_string\":\"1m30s\""},
        }
 
        for name, tc := range tests {
                t.Run(name, func(t *testing.T) {
-                       buf.Reset()
-                       _ = config.Format.Set(tc.logFormat)
+                       var buf bytes.Buffer
+                       config := &Config{
+                               Writer: &buf,
+                               Format: NewFormat(),
+                       }
+                       require.NoError(t, config.Format.Set(tc.logFormat))
                        logger := New(config)
                        logger.Info("duration testing", "duration_raw", dur, "duration_string", dur.String())
                        require.Contains(t, buf.String(), tc.want)

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
@machine424 machine424 merged commit d5e1c62 into prometheus:main Jul 8, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants