diff --git a/internal/observabilityproxy/logsparams.go b/internal/observabilityproxy/logsparams.go index 220a06762..ec6043c6f 100644 --- a/internal/observabilityproxy/logsparams.go +++ b/internal/observabilityproxy/logsparams.go @@ -160,10 +160,11 @@ func window(in url.Values, now time.Time, lim LogsLimits) (time.Time, time.Time, if end.Sub(start) > lim.MaxRange { start = end.Add(-lim.MaxRange) } - for k, t := range map[string]time.Time{"start": start, "end": end} { - if herr := sendableTime(k, t); herr != nil { - return time.Time{}, time.Time{}, herr - } + if herr := sendableTime("end", end); herr != nil { + return time.Time{}, time.Time{}, herr + } + if herr := sendableTime("start", start); herr != nil { + return time.Time{}, time.Time{}, herr } return start.UTC(), end.UTC(), nil } @@ -337,7 +338,7 @@ func scaleDuration(f float64, unit time.Duration) (time.Duration, bool) { // actually sent are checked; an older start is clamped first. func sendableTime(key string, t time.Time) *HTTPError { if t.Before(time.Unix(0, math.MinInt64)) || t.After(time.Unix(0, math.MaxInt64)) { - return errorf(http.StatusBadRequest, "invalid %s: %s is outside 1677..2262", key, t.Format(time.RFC3339)) + return errorf(http.StatusBadRequest, "invalid %s: %s is outside 1677..2262", key, t.UTC().Format(time.RFC3339Nano)) } return nil } diff --git a/internal/observabilityproxy/logsparams_test.go b/internal/observabilityproxy/logsparams_test.go index f7d21753a..82ed29190 100644 --- a/internal/observabilityproxy/logsparams_test.go +++ b/internal/observabilityproxy/logsparams_test.go @@ -17,6 +17,7 @@ limitations under the License. package observabilityproxy import ( + "math" "net/http" "net/url" "sort" @@ -408,3 +409,26 @@ func TestBuildLogsParamsOldTimesClamped(t *testing.T) { t.Errorf("start_offset = %s, want the %s cap", got, lim.MaxTailResume) } } + +// TestBuildLogsParamsSendableBoundary pins the inclusive int64-nanosecond +// bound and that the refusal names the instant exactly. +func TestBuildLogsParamsSendableBoundary(t *testing.T) { + last := time.Unix(0, math.MaxInt64).UTC() + in := url.Values{"start": {last.Add(-time.Hour).Format(time.RFC3339Nano)}, "end": {last.Format(time.RFC3339Nano)}} + if _, herr := BuildLogsParams("query", in, "tenant-a", testNow, testLimits()); herr != nil { + t.Fatalf("the last int64 nanosecond refused: %v", herr) + } + past := last.Add(time.Nanosecond).Format(time.RFC3339Nano) + in = url.Values{"start": {last.Add(-time.Hour).Format(time.RFC3339Nano)}, "end": {past}} + _, herr := BuildLogsParams("query", in, "tenant-a", testNow, testLimits()) + if herr == nil || !strings.Contains(herr.Message, "invalid end") || !strings.Contains(herr.Message, past) { + t.Fatalf("one nanosecond past answered %v, want end refused naming %s", herr, past) + } + // Both out of range: the answer names end, deterministically. + for range 20 { + _, herr := BuildLogsParams("query", url.Values{"start": {"0001-01-01T00:00:00Z"}, "end": {"0001-01-02T00:00:00Z"}}, "tenant-a", testNow, testLimits()) + if herr == nil || !strings.Contains(herr.Message, "invalid end") { + t.Fatalf("answer = %v, want end named", herr) + } + } +}