Add agent heartbeat monitoring and fix a query-language lexer bug

Agents now send an independent "still alive" record on a configurable
schedule (seconds/minutes/hours, [heartbeat] in agent.toml), separate
from real log traffic and tagged with a sentry.heartbeat attribute.
No new wire protocol -- it's an ordinary record through the same
PushBatch RPC/mTLS identity every log line already uses. Unavailability
alerting reuses the existing absence-condition alert rule type
unchanged; no new alerting code was needed. See
/docs/agent-heartbeat-monitoring.md for the design and how to build the
alert rule.

While verifying the alert rule live, found that the query language's
lexer never treated '-' as part of an identifier, so any unquoted
hyphenated filter value -- including the reference doc's own canonical
example, `host!=host-03` -- failed to parse at all. Fixed in
api/internal/querylang/lexer/lexer.go with regression tests; a leading
'-' still lexes as its own token so earliest=-1h/sort -count are
unaffected.
This commit is contained in:
2026-08-16 18:08:05 -07:00
parent 7d316f92db
commit 4df6931869
8 changed files with 367 additions and 3 deletions
@@ -26,6 +26,39 @@ func TestParseSimpleFilter(t *testing.T) {
}
}
// TestParseFilterWithHyphenatedValue is a regression test for a real
// bug in the lexer (isIdentPart excluded '-'): this exact query is
// /docs/query-language-reference.md's own canonical unquoted example
// and used to fail with "unexpected MINUS after query".
func TestParseFilterWithHyphenatedValue(t *testing.T) {
q, err := Parse(`host!=host-03`)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
cmp, ok := q.Base.Terms[0].(ast.Comparison)
if !ok {
t.Fatalf("expected Comparison, got %T", q.Base.Terms[0])
}
if cmp.Field != "host" || cmp.Op != "!=" || cmp.Value != "host-03" {
t.Fatalf("unexpected comparison: %+v", cmp)
}
}
// TestParseNegativeTimeExprStillWorks guards against the hyphenated-
// identifier fix above accidentally swallowing the leading sign
// earliest=/latest= depend on -- a leading '-' must stay its own token.
func TestParseNegativeTimeExprStillWorks(t *testing.T) {
q, err := Parse(`earliest=-1h`)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
earliest := q.Base.Terms[0].(ast.TimeBound)
if earliest.Kind != "earliest" || !earliest.Expr.IsRelative || earliest.Expr.RelativeSign != -1 ||
earliest.Expr.RelativeN != 1 || earliest.Expr.RelativeUnit != "h" {
t.Fatalf("unexpected earliest: %+v", earliest)
}
}
func TestParseFullPipeline(t *testing.T) {
q, err := Parse(`service=api | where status>=500 | stats count(*) as errors by host | sort -errors`)
if err != nil {