crowdsecurity/crowdsec · error

invalid Loki URL

Error message

invalid Loki URL %q: %w

What it means

updateURI re-parses the current query_range URI to append new start/next parameters when paginating through results. If the stored URI cannot be parsed by url.Parse, this error wraps the cause and aborts the query loop.

Solutions

  1. Fix the datasource URL in the DSN so it is a valid absolute URL (scheme://host:port/path?params)
  2. Percent-encode special characters ('%' as '%25', spaces as '%20')
  3. Verify with a quick url.Parse in Go or a browser that the URL is well formed

Example fix

// before
loki://localhost:3100/loki/api/v1/query_range?query={job="app"}
// after
loki://localhost:3100/loki/api/v1/query_range?query=%7Bjob%3D%22app%22%7D
Defensive patterns

Strategy: validation

Validate before calling

if _, err := url.Parse(dsnURL); err != nil {
    return fmt.Errorf("datasource URL invalid: %w", err)
}

Prevention

When it happens

Trigger: The URI constructed from the DSN (getURLFor + query params) is malformed — e.g. invalid percent-escapes or a corrupted URL string built from a bad datasource config.

Common situations: Malformed DSN host with stray characters; control characters or unencoded '%' in query parameters.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/25f25d0464440954. Report an issue: GitHub.

Appendix: source

Thrown at pkg/acquisition/modules/loki/internal/lokiclient/loki_client.go:54

	Query      string
	Headers    map[string]string

	Username string
	Password string

	Since time.Duration
	Until time.Duration

	FailMaxDuration time.Duration

	DelayFor int
	Limit    int
}

func updateURI(uri string, lq LokiQueryRangeResponse, infinite bool) (string, error) {
	u, err := url.Parse(uri)
	if err != nil {
		return "", fmt.Errorf("invalid Loki URL %q: %w", uri, err)
	}
	queryParams := u.Query()

	var maxTS time.Time
	for _, stream := range lq.Data.Result {
		for _, entry := range stream.Entries {
			if entry.Timestamp.After(maxTS) {
				maxTS = entry.Timestamp
			}
		}
	}

	if !maxTS.IsZero() {
		// +1 the last timestamp to avoid getting the same result again.
		queryParams.Set("start", strconv.FormatInt(maxTS.UnixNano()+1, 10))
	}
	// When maxTS.IsZero() (no results), keep the existing start to avoid
	// re-fetching already-processed logs. Only end is moved forward below.

View on GitHub (pinned to 909b515798)