Documentation
¶
Overview ¶
Package alerts evaluates operator-defined rules against osquery log traffic and node state, and dispatches notifications through configured channels (email, webhooks, …).
Design constraints (hot-path first):
- The ingest path never reads the database or Redis to decide whether a log line matches. Rules are served from an immutable RuleSet snapshot loaded at boot / refresh; a config change swaps the snapshot atomically (copy-on-write), so matching is lock-free.
- Regexes are compiled once per snapshot. Matching over a decoded batch is O(rules) with no allocations beyond the hit slice.
- Cooldown/dedupe state lives in Redis with a TTL equal to the rule's cooldown window, and is consulted by the dispatch worker — never by the ingest goroutine.
Row conventions follow pkg/logsinks: EnvironmentID=0 means the rule applies globally to every environment; per-env rows override. Rules are stored per source type so the ingest hook only walks rules registered for the log type it just decoded.
Index ¶
- Constants
- Variables
- func CompileRule(rule AlertRule) (compiledRule, error)
- func DecodeChannelIDs(raw string) ([]uint, error)
- func DecodeChannelIDsOrEmpty(raw string) []uint
- func EncodeChannelIDs(ids []uint) string
- func MergeChannelSecrets(typ, prevCfgJSON, newCfgJSON string) (string, error)
- func RedactedChannelConfig(typ, cfgJSON string) string
- func RegisterWorkerMetrics(reg prometheus.Registerer, w *Worker)
- func SupportedChannelTypes() []string
- func TestSend(typ, cfgJSON string) error
- func ValidateChannelConfig(typ, cfgJSON string) error
- func ValidateChannelType(typ string) bool
- func ValidateRule(rule AlertRule) error
- type AlertChannel
- type AlertHistory
- type AlertRule
- type ChannelSender
- type ChannelSpec
- type DispatchSink
- type Dispatcher
- type EmailConfig
- type FieldSpec
- type Hit
- type InactiveWatcher
- type IngestMatcher
- func (m *IngestMatcher) MatchQueryResult(envID uint, environment, queryName string, result json.RawMessage, status int, ...)
- func (m *IngestMatcher) MatchResultLogs(envID uint, environment string, logs []types.LogResultData)
- func (m *IngestMatcher) MatchStatusLogs(envID uint, environment string, logs []types.LogStatusData)
- type Manager
- func (m *Manager) CreateChannel(ch AlertChannel) (AlertChannel, error)
- func (m *Manager) CreateRule(rule AlertRule) (AlertRule, error)
- func (m *Manager) DeleteChannel(id uint) error
- func (m *Manager) DeleteRule(id uint) error
- func (m *Manager) GetChannel(id uint) (AlertChannel, error)
- func (m *Manager) GetRule(id uint) (AlertRule, error)
- func (m *Manager) ListChannels(envID *uint) ([]AlertChannel, error)
- func (m *Manager) ListRules(envID *uint) ([]AlertRule, error)
- func (m *Manager) LoadSnapshot(store *Store) error
- func (m *Manager) PruneHistory(olderThan interface{}) (int64, error)
- func (m *Manager) PruneHistoryWithRetention(retentionDays int64, now time.Time) (int64, error)
- func (m *Manager) RecentHistory(limit int) ([]AlertHistory, error)
- func (m *Manager) RecordHistory(h AlertHistory) error
- func (m *Manager) UpdateChannel(id uint, ch AlertChannel) (AlertChannel, error)
- func (m *Manager) UpdateRule(id uint, rule AlertRule) (AlertRule, error)
- type NodeSnapshot
- type NodeSource
- type RuleSet
- func (rs *RuleSet) MatchQueryResult(envID uint, environment, queryName string, result json.RawMessage, status int, ...) []Hit
- func (rs *RuleSet) MatchResultLogs(envID uint, environment string, logs []types.LogResultData) []Hit
- func (rs *RuleSet) MatchStatusLogs(envID uint, environment string, logs []types.LogStatusData) []Hit
- type State
- type Store
- type WebhookConfig
- type Worker
- type WorkerMetrics
Constants ¶
const ( ChannelWebhook = "webhook" ChannelEmail = "email" )
Channel types.
const ( FieldString = "string" FieldInteger = "integer" FieldBoolean = "boolean" FieldSecret = "secret" )
Field types for channel forms.
const ( // SourceResultLog watches osquery scheduled-query result logs. SourceResultLog = "result_log" // SourceStatusLog watches osquery daemon status logs. SourceStatusLog = "status_log" // SourceQueryLog watches on-demand distributed query results. SourceQueryLog = "query_log" // SourceNodeInactive fires when a node crosses the inactive // threshold. Evaluated by a periodic sweep, not the ingest path. SourceNodeInactive = "node_inactive" // SourceNodeRecovered fires when a previously inactive node is // seen again. Evaluated by the same sweep. SourceNodeRecovered = "node_recovered" )
Sources a rule can watch. The ingest hook only evaluates rules whose Source matches the data it just decoded, so unrelated rules cost nothing on any given path.
const ( MatchTypeSubstring = "substring" MatchTypeRegex = "regex" )
Match types. Substring is the operator default — regex is opt-in because an author-supplied pattern is a DoS vector otherwise.
const DefaultCooldown = 15 * time.Minute
DefaultCooldown is used when a rule specifies CooldownMinutes == 0. A small non-zero default keeps a flood of identical matches from spamming channels when the operator has not thought about cooldowns.
const MaxNodeUUIDLen = 64
MaxNodeUUIDLen bounds NodeUUID to its column width.
const MaxPatternLen = 512
MaxPatternLen caps an operator-supplied regex/substring length. Long patterns are a trivial DoS lever on a hot path.
const MaxRulesPerEnv = 50
MaxRulesPerEnv caps rule count per environment so a fleet of very matchy rules cannot make ingest O(huge). Enforced at create/update in the manager.
const NoEnvironmentID uint = 0
NoEnvironmentID is the sentinel for global (non-env-scoped) rows. Mirrors settings.NoEnvironmentID / logsinks.NoEnvironmentID.
const SignatureHeader = "X-Osctrl-Signature"
SignatureHeader carries the HMAC of the request body.
const TestSendTimeout = 15 * time.Second
TestSendTimeout bounds a channel test so an unresponsive relay cannot hold the API request open. Senders carry their own per-request timeouts; this is the backstop for the ones that do not (SMTP).
Variables ¶
var ( // ErrRuleNotFound is returned by manager Get/Update/Delete. ErrRuleNotFound = errors.New("alert rule not found") // ErrChannelNotFound is returned by channel manager methods. ErrChannelNotFound = errors.New("alert channel not found") // ErrRuleExists is returned on duplicate (name, environment). ErrRuleExists = errors.New("alert rule already exists") // ErrChannelExists is returned on duplicate (name, environment). ErrChannelExists = errors.New("alert channel already exists") // ErrInvalidSource is returned when Source is not a known source. ErrInvalidSource = errors.New("invalid alert source") // ErrInvalidRule wraps every ValidateRule failure. ErrInvalidRule = errors.New("invalid alert rule") // ErrTooManyRules guards MaxRulesPerEnv. ErrTooManyRules = fmt.Errorf("too many alert rules for one environment (max %d)", MaxRulesPerEnv) )
var ChannelRegistry = map[string]ChannelSpec{ ChannelWebhook: { Type: ChannelWebhook, Description: "HTTP POST to a URL with the alert as JSON", HasSecret: true, SecretFields: []string{"secret"}, Fields: []FieldSpec{ {Name: "url", Label: "URL", Type: FieldString, Required: true, Placeholder: "https://example.com/hook", Help: "HTTPS URL receiving the alert as a JSON POST."}, {Name: "secret", Label: "HMAC secret", Type: FieldSecret, Help: "When set, the payload is signed with X-Osctrl-Signature (HMAC-SHA256, hex)."}, {Name: "timeoutSeconds", Label: "Timeout (seconds)", Type: FieldInteger, Default: 10, Help: "Per-request timeout, including retries."}, {Name: "insecureSkipVerify", Label: "Skip TLS verify", Type: FieldBoolean, Default: false, Help: "Do not verify the server certificate. Avoid in production."}, {Name: "allowPrivateTargets", Label: "Allow private/loopback target", Type: FieldBoolean, Default: false, Help: "Required to reach localhost or an RFC1918 address. Off by default so a webhook cannot be pointed at internal services; enable only for a relay you run yourself."}, }, Decode: decodeTyped[WebhookConfig](), Build: func(cfg any) (ChannelSender, error) { return buildWebhook(*cfg.(*WebhookConfig)) }, }, ChannelEmail: { Type: ChannelEmail, Description: "Email to a list of recipients via SMTP", HasSecret: true, SecretFields: []string{"password"}, Fields: []FieldSpec{ {Name: "host", Label: "SMTP host", Type: FieldString, Required: true, Placeholder: "smtp.example.com", Help: "SMTP relay host."}, {Name: "port", Label: "SMTP port", Type: FieldInteger, Default: 587, Help: "587 = STARTTLS, 465 = implicit TLS, 25 = plaintext."}, {Name: "username", Label: "Username", Type: FieldString}, {Name: "password", Label: "Password", Type: FieldSecret}, {Name: "from", Label: "From address", Type: FieldString, Required: true, Placeholder: "osctrl@example.com", Help: "Envelope sender for the notification."}, {Name: "to", Label: "Recipients", Type: FieldString, Required: true, Placeholder: "soc@example.com, oncall@example.com", Help: "Comma-separated recipient addresses."}, {Name: "starttls", Label: "Use STARTTLS", Type: FieldBoolean, Default: true, Help: "Upgrade to TLS after connect (port 587). Ignored on port 465 (implicit TLS)."}, }, Decode: decodeTyped[EmailConfig](), Build: func(cfg any) (ChannelSender, error) { return buildEmail(*cfg.(*EmailConfig)) }, }, }
ChannelRegistry maps each channel type to its spec. Adding a channel type means: implement the sender, add the config struct here, add a ChannelSpec entry.
var ErrChannelDisabled = errors.New("alert channel disabled")
ErrChannelDisabled marks a channel the operator turned off. The dispatcher skips these without treating them as failures.
var ErrInvalidChannelConfig = fmt.Errorf("invalid channel configuration")
ErrInvalidChannelConfig is returned when the config JSON fails to decode against the registered type.
var ErrInvalidChannelType = fmt.Errorf("invalid channel type")
ErrInvalidChannelType is returned for unregistered channel types.
Functions ¶
func CompileRule ¶
CompileRule validates and compiles one rule into its matcher form. Returns an error when the regex is invalid or the pattern is too long — callers (manager Create/Update) must reject such rules before they reach the store.
func DecodeChannelIDs ¶
DecodeChannelIDs parses the JSON channel-ID array. Empty string / null decodes to nil.
func DecodeChannelIDsOrEmpty ¶
DecodeChannelIDsOrEmpty parses the channel-ID array and returns an empty slice on any error — a convenience for API clients that only need display values.
func EncodeChannelIDs ¶
EncodeChannelIDs serializes channel IDs for storage.
func MergeChannelSecrets ¶
MergeChannelSecrets replaces "***" placeholder values in newCfg with the corresponding values from prevCfg, so an API update that did not touch a secret preserves the stored one instead of writing the placeholder.
func RedactedChannelConfig ¶
RedactedChannelConfig returns the Config JSON with secret fields replaced by "***" for the channel Type. Non-secret types return the raw config unchanged. Best-effort: undecodable configs pass through.
func RegisterWorkerMetrics ¶
func RegisterWorkerMetrics(reg prometheus.Registerer, w *Worker)
RegisterWorkerMetrics registers a collector for the worker's counters. Call once per process with the live worker; the collector reads the same atomics the hot path bumps, so there is no locking between scrape and dispatch.
func SupportedChannelTypes ¶
func SupportedChannelTypes() []string
SupportedChannelTypes lists the registry keys, sorted.
func TestSend ¶
TestSend delivers one synthetic notification through a channel config without storing it, so an operator can verify a channel from the form before saving. Config errors and delivery errors are both returned as they are — the operator needs to see which relay refused what.
func ValidateChannelConfig ¶
ValidateChannelConfig decodes the JSON config against the registry.
func ValidateChannelType ¶
ValidateChannelType reports whether the type is registered.
func ValidateRule ¶
ValidateRule checks the operator-facing fields of a rule before it is stored. Mirrors logsinks.ValidateSink. Every failure wraps ErrInvalidRule: these are all operator-input errors, so the API answers 400 with the reason instead of an opaque 500.
Types ¶
type AlertChannel ¶
type AlertChannel struct {
gorm.Model
Name string `gorm:"uniqueIndex:idx_alert_channels_unique"`
EnvironmentID uint `gorm:"uniqueIndex:idx_alert_channels_unique"`
// Type is the Registry key: "email", "webhook", …
Type string `gorm:"index;size:32"`
// Config is a JSON blob whose shape depends on Type.
Config string `gorm:"type:text"`
Enabled bool
Info string
}
AlertChannel is one notification destination. Type + Config follow the logsinks pattern: Config is a JSON blob validated against the channel Registry (Stage 3); the typed form schema ships with the Registry so the SPA renders it generically.
func (AlertChannel) TableName ¶
func (AlertChannel) TableName() string
TableName overrides the default table name.
type AlertHistory ¶
type AlertHistory struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"`
// RuleID / ChannelID are denormalized as uint rather than FKs so
// deleting a rule or channel does not cascade-delete history.
RuleID uint `gorm:"index" json:"rule_id"`
RuleName string `gorm:"size:128" json:"rule_name"`
ChannelID uint `gorm:"index" json:"channel_id"`
ChannelName string `gorm:"size:128" json:"channel_name"`
Environment string `gorm:"size:64;index" json:"environment"`
NodeUUID string `gorm:"size:64;index" json:"node_uuid"`
// Entity is what matched: a hostname, a query name, or "node" for
// inactive/recovered rules. Part of the Redis dedupe key.
Entity string `gorm:"size:256" json:"entity"`
// Detail is the rendered match context (bounded at match time).
Detail string `gorm:"type:text" json:"detail"`
}
AlertHistory records every notification the system sent, for the "recent alerts" view and for auditing. Written only by the dispatch worker (off the hot path); reads happen through the management API.
func (AlertHistory) TableName ¶
func (AlertHistory) TableName() string
TableName overrides the default table name.
type AlertRule ¶
type AlertRule struct {
gorm.Model
Name string `gorm:"uniqueIndex:idx_alert_rules_unique"`
EnvironmentID uint `gorm:"uniqueIndex:idx_alert_rules_unique"`
// Source is one of the Source* constants.
Source string `gorm:"index;size:32"`
// NodeUUID optionally scopes the rule to a single node. When set:
// log-matching rules only match entries whose hostIdentifier equals
// it, and node-state rules only fire for that node. Empty = all
// nodes in scope (the original behavior).
NodeUUID string `gorm:"size:64;index"`
// MatchType is MatchTypeSubstring or MatchTypeRegex.
MatchType string `gorm:"size:16"`
// MatchField scopes the match to one column ("message" for status
// logs, a column name for result/query logs). Empty = any field.
MatchField string `gorm:"size:128"`
// MatchValue is the substring or regex pattern.
MatchValue string `gorm:"type:text"`
// StatusSeverity filters status-log rules: "error", "warning" or
// "any". Only consulted when Source == SourceStatusLog.
StatusSeverity string `gorm:"size:16;default:any"`
// CooldownMinutes suppresses repeat notifications for the same
// rule+entity within this window. 0 = alert on every match (subject
// to the global default cooldown).
CooldownMinutes int `gorm:"default:0"`
// ChannelIDs is a JSON-encoded array of alert_channel IDs. Empty
// array = no channels yet; the rule matches but nothing is sent.
ChannelIDs string `gorm:"type:text"`
Enabled bool
Info string
}
AlertRule is one operator-defined alert. Name + EnvironmentID are unique together (gorm composite index below), mirroring saved queries and log sinks.
type ChannelSender ¶
type ChannelSender interface {
Send(h Hit) error
// Name identifies the channel in logs and history.
Name() string
}
ChannelSender delivers one notification for one hit. Implementations must be safe for concurrent use from the worker pool.
type ChannelSpec ¶
type ChannelSpec struct {
Type string
Description string
HasSecret bool
SecretFields []string
Fields []FieldSpec
// Decode unmarshals a raw JSON Config into the typed struct the
// sender expects.
Decode func(json.RawMessage) (any, error)
// Build instantiates a ChannelSender from a decoded config.
Build func(any) (ChannelSender, error)
}
ChannelSpec describes one supported channel type.
type DispatchSink ¶
DispatchSink processes one claimed hit. Implemented by the channel fan-out in Stage 3; tests substitute their own.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher fans hits out to configured channels.
func NewDispatcher ¶
func NewDispatcher(mgr *Manager) *Dispatcher
NewDispatcher builds a channel dispatcher over the alert manager.
func (*Dispatcher) Dispatch ¶
func (d *Dispatcher) Dispatch(ctx context.Context, h Hit) error
Dispatch delivers the hit to every referenced channel. Channels that are disabled, mis-typed, or missing are skipped without counting as failures — an operator disabling a channel is a choice, not an outage. Real delivery errors are per-channel: one dead webhook does not block the email. Returns an error only when at least one delivery was attempted and every one of them failed (so the worker can retry the claim). Zero usable channels is a no-op success.
func (*Dispatcher) RefreshChannel ¶
func (d *Dispatcher) RefreshChannel(id uint)
RefreshChannel drops the cached sender for one channel so the next dispatch rebuilds it from the current row.
func (*Dispatcher) Reset ¶
func (d *Dispatcher) Reset()
Reset drops all cached senders (full reload).
type EmailConfig ¶
type EmailConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
From string `json:"from"`
To string `json:"to"`
StartTLS bool `json:"starttls"`
}
EmailConfig is the typed JSON config for email channels.
type FieldSpec ¶
type FieldSpec struct {
Name string
Label string
Type string // string | integer | boolean | secret
Required bool
Placeholder string
Help string
Default any
}
FieldSpec reuses the logsinks field schema for SPA form rendering.
type Hit ¶
type Hit struct {
RuleID uint
RuleName string
EnvironmentID uint
// Environment is the env name captured at match time, used for the
// history row and rendered payloads.
Environment string
NodeUUID string
Entity string
Detail string
CooldownMinutes int
Channels []uint
}
Hit is one rule × one entity match, ready for the dispatch worker.
type InactiveWatcher ¶
type InactiveWatcher struct {
// contains filtered or unexported fields
}
InactiveWatcher runs the sweep and emits hits to the dispatch worker.
func NewInactiveWatcher ¶
func NewInactiveWatcher(source NodeSource, store *Store, worker *Worker, client *redis.Client) *InactiveWatcher
NewInactiveWatcher builds the watcher. worker may be nil (feature off mid-restart) — sweeps then no-op. A nil client builds a watcher with no persistence: every sweep re-evaluates transitions from scratch, so inactive nodes re-alert each pass (acceptable only for tests; production always passes the Redis client).
func (*InactiveWatcher) Run ¶
func (w *InactiveWatcher) Run(stop <-chan struct{}, interval time.Duration)
Run blocks running the sweep on the interval until stop closes.
func (*InactiveWatcher) Sweep ¶
func (w *InactiveWatcher) Sweep(ctx context.Context)
Sweep runs one detection pass: mark newly-inactive nodes, unmark recovered ones, and enqueue hits for the transitions.
type IngestMatcher ¶
type IngestMatcher struct {
// contains filtered or unexported fields
}
IngestMatcher adapts a rule snapshot + worker to the ingest hook. A nil *IngestMatcher is a valid disabled matcher (all methods no-op), so cmd/tls can store the nil pointer without nil-interface traps.
func NewIngestMatcher ¶
func NewIngestMatcher(store *Store, worker *Worker) *IngestMatcher
NewIngestMatcher wires a matcher over the given snapshot store and dispatch worker. Both must be non-nil.
func (*IngestMatcher) MatchQueryResult ¶
func (m *IngestMatcher) MatchQueryResult(envID uint, environment, queryName string, result json.RawMessage, status int, message string)
MatchQueryResult implements logging.AlertMatcher.
func (*IngestMatcher) MatchResultLogs ¶
func (m *IngestMatcher) MatchResultLogs(envID uint, environment string, logs []types.LogResultData)
MatchResultLogs implements logging.AlertMatcher.
func (*IngestMatcher) MatchStatusLogs ¶
func (m *IngestMatcher) MatchStatusLogs(envID uint, environment string, logs []types.LogStatusData)
MatchStatusLogs implements logging.AlertMatcher.
type Manager ¶
Manager manages the alert_rules / alert_channels / alert_history tables.
func NewManager ¶
NewManager initializes the manager and auto-migrates the three tables. AutoMigrate is production-impacting (it creates the tables on first boot) but additive: existing deployments gain three empty tables.
func (*Manager) CreateChannel ¶
func (m *Manager) CreateChannel(ch AlertChannel) (AlertChannel, error)
CreateChannel inserts a new notification channel. Type and config are validated against the channel registry.
func (*Manager) CreateRule ¶
CreateRule validates and inserts a new rule. The rule-count cap per environment is enforced here.
func (*Manager) DeleteChannel ¶
DeleteChannel removes a channel by ID. Rules referencing it keep the dangling ID in ChannelIDs; dispatch treats unknown channels as disabled and skips them.
func (*Manager) DeleteRule ¶
DeleteRule removes a rule by ID. History rows are not cascaded — they keep the RuleName snapshot for the audit trail.
func (*Manager) GetChannel ¶
func (m *Manager) GetChannel(id uint) (AlertChannel, error)
GetChannel retrieves one channel by ID.
func (*Manager) ListChannels ¶
func (m *Manager) ListChannels(envID *uint) ([]AlertChannel, error)
ListChannels returns all channels, optionally env-scoped.
func (*Manager) ListRules ¶
ListRules returns all rules, optionally scoped to one environment. envID nil = all environments; NoEnvironmentID = global rows only.
func (*Manager) LoadSnapshot ¶
LoadSnapshot reads all enabled rules, compiles them, and publishes a fresh RuleSet to the store. A rule that fails to compile (bad regex introduced by direct DB edit) is skipped with the error surfaced; it never blocks the rest of the ruleset.
func (*Manager) PruneHistory ¶
PruneHistory removes rows older than the retention window. Called by a periodic sweep, never on the hot path. Returns the number of rows deleted so the sweep can log the effect.
func (*Manager) PruneHistoryWithRetention ¶
PruneHistoryWithRetention computes the cutoff from a retention window in days and prunes. Kept separate from the raw PruneHistory so the sweep can log "retention=Nd deleted=N" in one place.
func (*Manager) RecentHistory ¶
func (m *Manager) RecentHistory(limit int) ([]AlertHistory, error)
RecentHistory returns the newest history rows, capped.
func (*Manager) RecordHistory ¶
func (m *Manager) RecordHistory(h AlertHistory) error
RecordHistory appends one dispatched-alert row (dispatch worker only). Detail is bounded here as defense in depth — the matcher already truncates at hit time.
func (*Manager) UpdateChannel ¶
func (m *Manager) UpdateChannel(id uint, ch AlertChannel) (AlertChannel, error)
UpdateChannel replaces the mutable fields of a channel. Type and config are re-validated.
type NodeSnapshot ¶
type NodeSnapshot struct {
UUID string
// EnvironmentID / Environment identify the node's environment for
// rule scoping (ruleApplies) and hit attribution respectively.
EnvironmentID uint
Environment string
Hostname string
Active bool
LastSeen time.Time
}
NodeSnapshot is one node's liveness state as seen by the sweep.
type NodeSource ¶
type NodeSource interface {
// InactiveNodes returns every node whose last_seen is older than
// the given threshold, and the threshold itself is resolved per
// environment by the implementation.
InactiveNodes(ctx context.Context) ([]NodeSnapshot, error)
// ActiveNodes returns every currently-active node (used to detect
// recoveries of nodes previously marked inactive).
ActiveNodes(ctx context.Context) ([]NodeSnapshot, error)
}
NodeSource produces node snapshots for the sweep. Implemented over the nodes manager in osctrl-tls; tests substitute their own.
type RuleSet ¶
type RuleSet struct {
// contains filtered or unexported fields
}
RuleSet is an immutable snapshot of all enabled rules, grouped by source so each ingest path only walks its own rules. The zero value (no rules) is valid and match-free.
func (*RuleSet) MatchQueryResult ¶
func (rs *RuleSet) MatchQueryResult(envID uint, environment, queryName string, result json.RawMessage, status int, message string) []Hit
MatchQueryResult evaluates one on-demand query result (the ProcessLogQueryResult tap) against rules scoped to envID. queryName scopes the hit to the query; rows is the decoded result payload.
func (*RuleSet) MatchResultLogs ¶
func (rs *RuleSet) MatchResultLogs(envID uint, environment string, logs []types.LogResultData) []Hit
MatchResultLogs evaluates result-log entries against rules scoped to envID. The columns of each entry (or its snapshot rows) are matched field-scoped or across all fields depending on the rule.
func (*RuleSet) MatchStatusLogs ¶
func (rs *RuleSet) MatchStatusLogs(envID uint, environment string, logs []types.LogStatusData) []Hit
MatchStatusLogs evaluates status-log entries against rules scoped to envID. Entries below the rule's severity floor are skipped before any pattern work happens.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is the Redis-backed dedupe gate.
func NewState ¶
NewState builds a cooldown state on top of an existing Redis client (the same client the backend cache uses).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store publishes rule snapshots. Safe for concurrent use; readers pull via Snapshot() which returns the current immutable set.
type WebhookConfig ¶
type WebhookConfig struct {
URL string `json:"url"`
Secret string `json:"secret"`
TimeoutSeconds int `json:"timeoutSeconds"`
InsecureSkipVerify bool `json:"insecureSkipVerify"`
// AllowPrivateTargets permits http(s):// targets on loopback or
// RFC1918 / link-local ranges. Intended for the dev stack's
// sink-catchall container; production deployments should leave it
// off so a compromised operator account cannot probe internals.
AllowPrivateTargets bool `json:"allowPrivateTargets"`
}
WebhookConfig is the typed JSON config for webhook channels.
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker is the dispatch pipeline behind the matcher.
func NewSyncWorker ¶
func NewSyncWorker(store *Store, state claimGate, manager *Manager, sink DispatchSink) *Worker
NewSyncWorker builds a worker that dispatches inline (tests only).
func NewWorker ¶
func NewWorker(store *Store, state claimGate, manager *Manager, sink DispatchSink, queueSize, workers int) *Worker
NewWorker builds and starts the dispatch worker pool. store is the rule snapshot (for future per-rule lookups); state may be nil (claims pass through); sink may be nil (hits are only claimed + recorded). Set selfRecording when the sink writes its own history rows.
func (*Worker) Enqueue ¶
Enqueue offers hits to the dispatch pipeline. Never blocks: a full queue drops the hit and bumps the counter. Nil worker is a no-op (alerts disabled), which keeps the ingest hook allocation-free when the feature is off.
func (*Worker) QueueDepth ¶
QueueDepth returns the current queue length.
type WorkerMetrics ¶
type WorkerMetrics struct {
// Matched counts hits accepted into the queue.
Matched atomic.Uint64
// Dropped counts hits rejected because the queue was full.
Dropped atomic.Uint64
// Dispatched counts hits processed by the sink.
Dispatched atomic.Uint64
// Collapsed counts hits suppressed by the cooldown gate.
Collapsed atomic.Uint64
// Failed counts sink errors.
Failed atomic.Uint64
}
WorkerMetrics collects the counters exposed to Prometheus. Kept as plain atomics so the hot path never touches the registry; cmd/tls polls them from its metrics endpoint.