Skip to content

[Feature] Lifecycle hooks at ingestion, query, and alert evaluation #216

@Polliog

Description

@Polliog

Feature Description

Define a small set of well-known lifecycle hooks at the critical paths of the platform: before ingestion accepts a payload, before a search query executes, before an alert rule is evaluated, and before an outbound webhook is dispatched. The default implementation is a no-op. Operators or downstream distributions can register handlers that observe, mutate, or reject the operation.

Problem/Use Case

Several adjacent concerns want to intercept these paths:

  • Quota enforcement (reject ingestion when the org is over its monthly limit)
  • Custom rate limiting policies beyond the global ones
  • Compliance hooks (e.g. additional logging of every alert evaluation for regulated environments)
  • Synchronous PII checks beyond the existing masking pipeline
    Each of these can be solved ad-hoc inside a Fastify hook, but doing so means business logic gets scattered across routes and middleware, and BullMQ-driven paths (alerts, webhooks) don't benefit at all. A small, named set of hooks gives all these concerns a single home.

Proposed Solution

Define hooks as async functions registered against well-known phase names. Each hook receives a typed context and may throw a typed error to abort the operation.

hooks.register('beforeIngest', async (ctx) => {
  // ctx: { organizationId, projectId, payloadSize, eventCount }
  // throw new HookRejectionError('quota.exceeded', 'Monthly limit reached')
})
 
hooks.register('beforeQuery', async (ctx) => { /* ... */ })
hooks.register('beforeAlertEvaluation', async (ctx) => { /* ... */ })
hooks.register('beforeWebhookDispatch', async (ctx) => { /* ... */ })

Initial set:

Hook When Context fields
beforeIngest After auth, before reservoir write org, project, eventCount, byteSize
beforeQuery After parsing, before reservoir read org, project, queryShape, timeRange
beforeAlertEvaluation In BullMQ worker, before rule execution org, ruleId, ruleType
beforeWebhookDispatch Before outbound HTTP call org, webhookId, targetHost

In OSS, no hooks are registered by default. The performance overhead of an empty hook list is negligible.

Alternatives Considered

  • Per-feature middleware. Works for HTTP paths but doesn't reach BullMQ workers. Inconsistent shape. Rejected.
  • A general event bus. Too broad. We want a small, intentional set of named extension points, not "subscribe to anything that happens." Easier to reason about and to keep stable.
  • Wait until a concrete need lands. The two concrete needs (quota, custom rate limits) are already on the horizon. Adding hooks reactively means changing the same code paths twice.

Implementation Details (Optional)

  • Hooks are typed per phase. beforeIngest and beforeQuery get different context shapes; the registry enforces this via discriminated unions.
  • Hook execution is sequential within a phase, in registration order. Any thrown error short-circuits and propagates with a typed rejection reason.
  • All hooks receive the current RequestContext (see #request-context-propagation issue) — they don't need to thread it manually.
  • A HookRejectionError carries a machine-readable code and a human-readable message. The caller decides whether to surface the message to the user (typically yes for quota errors, no for internal policy errors).
  • Document the hook contract clearly: what fields are stable, what can be mutated, and what guarantees exist about order of execution.
  • No hook should perform unbounded I/O. Document this convention; we may add a soft timeout later.

Priority

  • Critical - Blocking my usage of Logtide
  • High - Would significantly improve my workflow
  • Medium - Nice to have
  • Low - Minor enhancement

Target Users

  • Operators wanting to enforce custom organizational policies without forking the codebase
  • Self-hosters needing fine-grained control over what can be ingested, queried, or evaluated
  • Downstream distributions that need a stable, narrow extension surface

Contribution

  • I would like to work on implementing this feature

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions