diff --git a/caddyhttp/httpserver/replacer.go b/caddyhttp/httpserver/replacer.go index 41c68114c..407c43dfb 100644 --- a/caddyhttp/httpserver/replacer.go +++ b/caddyhttp/httpserver/replacer.go @@ -144,7 +144,10 @@ func (r *replacer) Replace(s string) string { if r.responseRecorder != nil { r.replacements["{status}"] = func() string { return strconv.Itoa(r.responseRecorder.status) } r.replacements["{size}"] = func() string { return strconv.Itoa(r.responseRecorder.size) } - r.replacements["{latency}"] = func() string { return time.Since(r.responseRecorder.start).String() } + r.replacements["{latency}"] = func() string { + dur := time.Since(r.responseRecorder.start) + return roundDuration(dur).String() + } } // Include custom placeholders, overwriting existing ones if necessary @@ -187,6 +190,36 @@ func (r *replacer) Replace(s string) string { return s } +func roundDuration(d time.Duration) time.Duration { + if d >= time.Millisecond { + return round(d, time.Millisecond) + } else if d >= time.Microsecond { + return round(d, time.Microsecond) + } + + return d +} + +// round rounds d to the nearest r +func round(d, r time.Duration) time.Duration { + if r <= 0 { + return d + } + neg := d < 0 + if neg { + d = -d + } + if m := d % r; m+m < r { + d = d - m + } else { + d = d + r - m + } + if neg { + return -d + } + return d +} + // Set sets key to value in the r.customReplacements map. func (r *replacer) Set(key, value string) { r.customReplacements["{"+key+"}"] = func() string { return value } diff --git a/caddyhttp/httpserver/replacer_test.go b/caddyhttp/httpserver/replacer_test.go index cb72ba5e7..158e819e3 100644 --- a/caddyhttp/httpserver/replacer_test.go +++ b/caddyhttp/httpserver/replacer_test.go @@ -6,6 +6,7 @@ import ( "os" "strings" "testing" + "time" ) func TestNewReplacer(t *testing.T) { @@ -140,3 +141,23 @@ func TestSet(t *testing.T) { t.Error("Expected variable replacement failed") } } + +func TestRound(t *testing.T) { + var tests = map[time.Duration]time.Duration{ + // 599.935µs -> 560µs + 559935 * time.Nanosecond: 560 * time.Microsecond, + // 1.55ms -> 2ms + 1550 * time.Microsecond: 2 * time.Millisecond, + // 1.5555s -> 1.556s + 1555500 * time.Microsecond: 1556 * time.Millisecond, + // 1m2.0035s -> 1m2.004s + 62003500 * time.Microsecond: 62004 * time.Millisecond, + } + + for dur, expected := range tests { + rounded := roundDuration(dur) + if rounded != expected { + t.Errorf("Expected %v, Got %v", expected, rounded) + } + } +}