LogQL: label filter regex `=~` is not anchored for one-sided `foo.*` / `.*foo` patterns
Describe the bug
A pipeline label filter regex (| name=~"…") should match the whole label value, exactly like a stream selector matcher. For a one-sided pattern — foo.* or .*foo — it matches a substring instead.
- Affected:
foo.*,.*foo, and their(?i)forms, for both=~and!~, on parsed fields, stream labels and structured metadata. - Not affected:
foo,foo|bar,.*foo.*(a substring match is equivalent there),pre.*ha,al[a-z]*.
To Reproduce
- Loki
main(5bc7440e34). - Two streams:
{app="a", name="alpha"}and{app="a", name="prealpha"}. - Run the same regex as a selector matcher and as a label filter:
{app="a", name=~"al.*"} # → name="alpha" (correct)
{app="a"} | name=~"al.*" # → name="alpha", name="prealpha" (wrong)Expected behavior
Both queries return only name="alpha". docs/sources/query/log_queries/_index.md states that the string label filter type "work[s] exactly like Prometheus label matchers use in log stream selector", and those are fully anchored.
Cause
parseRegexpFilter(re, match, isLabel=true) in pkg/logql/log/filter.go anchors with ^(?:…)$ only on the fallback path, when regex simplification fails. Every simplified branch therefore has to anchor itself, which is what the isLabel flag is for. Simplify passes isLabel to its OpLiteral and OpAlternate branches, but the OpConcat branch calls s.simplifyConcat(reg, nil) without it, and simplifyConcat always returns newContainsFilter.
The flag arrived in #8659, which threaded it through every other branch and the anchoring fallback.
Environment:
- Infrastructure: reproduced by a
pkg/logqltest onmain, no deployment needed.
Source: grafana/loki