mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-23 04:10:12 +00:00
feat: Introduces sample window size for metric alerting (#4498)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,8 @@
|
||||
<template v-if="alert.metricExpression">
|
||||
<span>{{ $t("notifications.alert.metric-filter") }}</span>
|
||||
<code class="bg-base-200 text-base-content rounded px-2 py-0.5 font-mono">{{ alert.metricExpression }}</code>
|
||||
<span>{{ $t("notifications.alert.sample-window") }}</span>
|
||||
<span>{{ formatDuration(alert.sampleWindow || 15, locale || undefined) }}</span>
|
||||
<span>{{ $t("notifications.alert.cooldown") }}</span>
|
||||
<span>{{ formatDuration(alert.cooldown || 300, locale || undefined) }}</span>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.sample-window-label") }}</legend>
|
||||
<input v-model.number="sampleWindow" type="range" min="15" max="300" step="15" class="range range-primary" />
|
||||
<p class="text-base-content/50 mt-1 text-xs">
|
||||
{{
|
||||
$t("notifications.alert-form.sample-window-hint", {
|
||||
duration: formatDuration(sampleWindow, locale || undefined),
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.cooldown-label") }}</legend>
|
||||
<input v-model.number="cooldown" type="range" min="10" max="3600" step="10" class="range range-primary" />
|
||||
@@ -46,6 +58,7 @@ const props = defineProps<{
|
||||
|
||||
const metricExpression = ref(props.alert?.metricExpression ?? props.prefill?.metricExpression ?? "");
|
||||
const metricError = ref<string | null>(null);
|
||||
const sampleWindow = ref(props.alert?.sampleWindow ?? 15);
|
||||
const cooldown = ref(props.alert?.cooldown ?? 300);
|
||||
|
||||
const canSave = computed(() => !!metricExpression.value.trim() && !metricError.value);
|
||||
@@ -53,6 +66,7 @@ const typeFields = computed(() => ({
|
||||
metricExpression: metricExpression.value,
|
||||
logExpression: "",
|
||||
cooldown: cooldown.value,
|
||||
sampleWindow: sampleWindow.value,
|
||||
}));
|
||||
|
||||
defineExpose({ canSave, typeFields });
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface NotificationRule {
|
||||
logExpression: string;
|
||||
metricExpression?: string;
|
||||
cooldown?: number;
|
||||
sampleWindow?: number;
|
||||
triggerCount: number;
|
||||
triggeredContainers: number;
|
||||
lastTriggeredAt: string | null;
|
||||
@@ -30,6 +31,7 @@ export interface NotificationRuleInput {
|
||||
containerExpression: string;
|
||||
metricExpression?: string;
|
||||
cooldown?: number;
|
||||
sampleWindow?: number;
|
||||
}
|
||||
|
||||
export interface PreviewResult {
|
||||
|
||||
@@ -515,6 +515,9 @@ func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []t
|
||||
DispatcherId: int32(sub.DispatcherID),
|
||||
LogExpression: sub.LogExpression,
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: int32(sub.Cooldown),
|
||||
SampleWindow: int32(sub.SampleWindow),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -828,6 +828,9 @@ type NotificationSubscription struct {
|
||||
DispatcherId int32 `protobuf:"varint,4,opt,name=dispatcherId,proto3" json:"dispatcherId,omitempty"`
|
||||
LogExpression string `protobuf:"bytes,5,opt,name=logExpression,proto3" json:"logExpression,omitempty"`
|
||||
ContainerExpression string `protobuf:"bytes,6,opt,name=containerExpression,proto3" json:"containerExpression,omitempty"`
|
||||
MetricExpression string `protobuf:"bytes,7,opt,name=metricExpression,proto3" json:"metricExpression,omitempty"`
|
||||
Cooldown int32 `protobuf:"varint,8,opt,name=cooldown,proto3" json:"cooldown,omitempty"`
|
||||
SampleWindow int32 `protobuf:"varint,9,opt,name=sampleWindow,proto3" json:"sampleWindow,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -904,6 +907,27 @@ func (x *NotificationSubscription) GetContainerExpression() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NotificationSubscription) GetMetricExpression() string {
|
||||
if x != nil {
|
||||
return x.MetricExpression
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NotificationSubscription) GetCooldown() int32 {
|
||||
if x != nil {
|
||||
return x.Cooldown
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NotificationSubscription) GetSampleWindow() int32 {
|
||||
if x != nil {
|
||||
return x.SampleWindow
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type NotificationDispatcher struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
@@ -1058,14 +1082,17 @@ const file_types_proto_rawDesc = "" +
|
||||
"\rdockerVersion\x18\f \x01(\tR\rdockerVersion\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc0\x02\n" +
|
||||
"\x18NotificationSubscription\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" +
|
||||
"\aenabled\x18\x03 \x01(\bR\aenabled\x12\"\n" +
|
||||
"\fdispatcherId\x18\x04 \x01(\x05R\fdispatcherId\x12$\n" +
|
||||
"\rlogExpression\x18\x05 \x01(\tR\rlogExpression\x120\n" +
|
||||
"\x13containerExpression\x18\x06 \x01(\tR\x13containerExpression\"~\n" +
|
||||
"\x13containerExpression\x18\x06 \x01(\tR\x13containerExpression\x12*\n" +
|
||||
"\x10metricExpression\x18\a \x01(\tR\x10metricExpression\x12\x1a\n" +
|
||||
"\bcooldown\x18\b \x01(\x05R\bcooldown\x12\"\n" +
|
||||
"\fsampleWindow\x18\t \x01(\x05R\fsampleWindow\"~\n" +
|
||||
"\x16NotificationDispatcher\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
|
||||
|
||||
@@ -414,6 +414,9 @@ func (s *server) UpdateNotificationConfig(ctx context.Context, req *pb.UpdateNot
|
||||
DispatcherID: int(sub.DispatcherId),
|
||||
LogExpression: sub.LogExpression,
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: int(sub.Cooldown),
|
||||
SampleWindow: int(sub.SampleWindow),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/amir20/dozzle/types"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -46,6 +47,7 @@ func (m *Manager) LoadConfig(r io.Reader) error {
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +101,7 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
}
|
||||
if err := m.loadSubscription(s); err != nil {
|
||||
return fmt.Errorf("failed to load subscription %s: %w", sub.Name, err)
|
||||
@@ -151,6 +154,9 @@ func (m *Manager) loadSubscription(sub *Subscription) error {
|
||||
if sub.MetricCooldowns == nil {
|
||||
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
|
||||
}
|
||||
if sub.MetricSampleBuffers == nil {
|
||||
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Debug().Str("name", sub.Name).Int("id", sub.ID).Msg("Loaded subscription")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/amir20/dozzle/types"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
@@ -79,6 +80,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error {
|
||||
sub.ID = int(m.subscriptionCounter.Add(1))
|
||||
sub.Enabled = true
|
||||
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
|
||||
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
|
||||
|
||||
if err := sub.CompileExpressions(); err != nil {
|
||||
return err
|
||||
@@ -104,6 +106,7 @@ func (m *Manager) RemoveSubscription(id int) {
|
||||
// ReplaceSubscription replaces a subscription with new data
|
||||
func (m *Manager) ReplaceSubscription(sub *Subscription) error {
|
||||
sub.MetricCooldowns = xsync.NewMap[string, time.Time]()
|
||||
sub.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
|
||||
|
||||
if err := sub.CompileExpressions(); err != nil {
|
||||
return err
|
||||
@@ -146,7 +149,9 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
|
||||
MetricExpression: sub.MetricExpression,
|
||||
MetricProgram: sub.MetricProgram,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
MetricCooldowns: sub.MetricCooldowns,
|
||||
MetricSampleBuffers: sub.MetricSampleBuffers,
|
||||
}
|
||||
|
||||
// Apply updates to the clone
|
||||
@@ -208,6 +213,11 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
|
||||
if cd, ok := value.(int); ok {
|
||||
updated.Cooldown = cd
|
||||
}
|
||||
case "sampleWindow":
|
||||
if sw, ok := value.(int); ok {
|
||||
updated.SampleWindow = sw
|
||||
updated.MetricSampleBuffers = xsync.NewMap[string, *utils.RingBuffer[bool]]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,8 +133,9 @@ func (m *Manager) processStatEvent(event *ContainerStatEvent) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check metric expression
|
||||
if !sub.MatchesMetric(notificationStat) {
|
||||
// Evaluate metric expression and record in sample window
|
||||
matched := sub.MatchesMetric(notificationStat)
|
||||
if !sub.RecordMetricSample(event.Stat.ID, matched) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -171,6 +172,7 @@ func (m *Manager) processStatEvent(event *ContainerStatEvent) {
|
||||
MetricExpression: sub.MetricExpression,
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
},
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/amir20/dozzle/types"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
@@ -91,7 +92,8 @@ type Subscription struct {
|
||||
LogExpression string `json:"logExpression" yaml:"logExpression"`
|
||||
ContainerExpression string `json:"containerExpression" yaml:"containerExpression"`
|
||||
MetricExpression string `json:"metricExpression,omitempty" yaml:"metricExpression,omitempty"`
|
||||
Cooldown int `json:"cooldown,omitempty" yaml:"cooldown,omitempty"` // seconds between metric notifications, default 300
|
||||
Cooldown int `json:"cooldown,omitempty" yaml:"cooldown,omitempty"` // seconds between metric notifications, default 300
|
||||
SampleWindow int `json:"sampleWindow,omitempty" yaml:"sampleWindow,omitempty"` // seconds of samples to evaluate, default 15
|
||||
|
||||
// Compiled filter expressions
|
||||
LogProgram *vm.Program `json:"-" yaml:"-"` // Compiled log filter expression
|
||||
@@ -105,6 +107,9 @@ type Subscription struct {
|
||||
|
||||
// Per-container cooldown tracking for metric alerts (containerID -> last triggered time)
|
||||
MetricCooldowns *xsync.Map[string, time.Time] `json:"-" yaml:"-"`
|
||||
|
||||
// Per-container sample buffers for windowed metric evaluation (containerID -> ring buffer of match results)
|
||||
MetricSampleBuffers *xsync.Map[string, *utils.RingBuffer[bool]] `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// TriggeredContainersCount returns the number of unique containers that triggered this subscription
|
||||
@@ -259,3 +264,47 @@ func (s *Subscription) IsMetricCooldownActive(containerID string) bool {
|
||||
func (s *Subscription) SetMetricCooldown(containerID string) {
|
||||
s.MetricCooldowns.Store(containerID, time.Now())
|
||||
}
|
||||
|
||||
// GetSampleWindowSeconds returns the sample window in seconds, clamped to [1, 300], defaulting to 15
|
||||
func (s *Subscription) GetSampleWindowSeconds() int {
|
||||
if s.SampleWindow <= 0 {
|
||||
return 15
|
||||
}
|
||||
if s.SampleWindow < 1 {
|
||||
return 1
|
||||
}
|
||||
if s.SampleWindow > 300 {
|
||||
return 300
|
||||
}
|
||||
return s.SampleWindow
|
||||
}
|
||||
|
||||
// RecordMetricSample records a metric evaluation result and returns true if the window threshold is met.
|
||||
// The alert fires when the buffer is full and >=80% of samples matched.
|
||||
func (s *Subscription) RecordMetricSample(containerID string, matched bool) bool {
|
||||
windowSize := s.GetSampleWindowSeconds()
|
||||
|
||||
// For window size of 1, just return the match result directly
|
||||
if windowSize <= 1 {
|
||||
return matched
|
||||
}
|
||||
|
||||
buf, _ := s.MetricSampleBuffers.LoadOrCompute(containerID, func() (*utils.RingBuffer[bool], bool) {
|
||||
return utils.NewRingBuffer[bool](windowSize), false
|
||||
})
|
||||
|
||||
buf.Push(matched)
|
||||
|
||||
if buf.Len() < windowSize {
|
||||
return false
|
||||
}
|
||||
|
||||
trueCount := 0
|
||||
for _, v := range buf.Data() {
|
||||
if v {
|
||||
trueCount++
|
||||
}
|
||||
}
|
||||
|
||||
return float64(trueCount)/float64(buf.Len()) >= 0.8
|
||||
}
|
||||
|
||||
@@ -253,6 +253,7 @@ func (m *MultiHostService) broadcastNotificationConfig() {
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ type NotificationRuleResponse struct {
|
||||
LogExpression string `json:"logExpression"`
|
||||
MetricExpression string `json:"metricExpression,omitempty"`
|
||||
Cooldown int `json:"cooldown,omitempty"`
|
||||
SampleWindow int `json:"sampleWindow,omitempty"`
|
||||
TriggerCount int64 `json:"triggerCount"`
|
||||
TriggeredContainers int `json:"triggeredContainers"`
|
||||
LastTriggeredAt *time.Time `json:"lastTriggeredAt"`
|
||||
@@ -53,6 +54,7 @@ type NotificationRuleInput struct {
|
||||
ContainerExpression string `json:"containerExpression"`
|
||||
MetricExpression string `json:"metricExpression,omitempty"`
|
||||
Cooldown int `json:"cooldown,omitempty"`
|
||||
SampleWindow int `json:"sampleWindow,omitempty"`
|
||||
}
|
||||
|
||||
type NotificationRuleUpdateInput struct {
|
||||
@@ -63,6 +65,7 @@ type NotificationRuleUpdateInput struct {
|
||||
ContainerExpression *string `json:"containerExpression,omitempty"`
|
||||
MetricExpression *string `json:"metricExpression,omitempty"`
|
||||
Cooldown *int `json:"cooldown,omitempty"`
|
||||
SampleWindow *int `json:"sampleWindow,omitempty"`
|
||||
}
|
||||
|
||||
type DispatcherInput struct {
|
||||
@@ -123,6 +126,7 @@ func subscriptionToResponse(sub *notification.Subscription, dispatchers []notifi
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
MetricExpression: sub.MetricExpression,
|
||||
Cooldown: sub.Cooldown,
|
||||
SampleWindow: sub.SampleWindow,
|
||||
TriggerCount: sub.TriggerCount.Load(),
|
||||
LastTriggeredAt: lastTriggeredAt,
|
||||
TriggeredContainers: sub.TriggeredContainersCount(),
|
||||
@@ -210,6 +214,7 @@ func (h *handler) createNotificationRule(w http.ResponseWriter, r *http.Request)
|
||||
ContainerExpression: input.ContainerExpression,
|
||||
MetricExpression: input.MetricExpression,
|
||||
Cooldown: input.Cooldown,
|
||||
SampleWindow: input.SampleWindow,
|
||||
}
|
||||
|
||||
if err := h.hostService.AddSubscription(sub); err != nil {
|
||||
@@ -242,6 +247,7 @@ func (h *handler) replaceNotificationRule(w http.ResponseWriter, r *http.Request
|
||||
ContainerExpression: input.ContainerExpression,
|
||||
MetricExpression: input.MetricExpression,
|
||||
Cooldown: input.Cooldown,
|
||||
SampleWindow: input.SampleWindow,
|
||||
}
|
||||
|
||||
if err := h.hostService.ReplaceSubscription(sub); err != nil {
|
||||
@@ -287,6 +293,9 @@ func (h *handler) updateNotificationRule(w http.ResponseWriter, r *http.Request)
|
||||
if input.Cooldown != nil {
|
||||
updates["cooldown"] = *input.Cooldown
|
||||
}
|
||||
if input.SampleWindow != nil {
|
||||
updates["sampleWindow"] = *input.SampleWindow
|
||||
}
|
||||
|
||||
if err := h.hostService.UpdateSubscription(id, updates); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Log filter
|
||||
metric-filter: Metrik
|
||||
cooldown: Afkøling
|
||||
sample-window: Prøvevindue
|
||||
destination: Destination
|
||||
dispatcher-deleted: Dispatcher slettet
|
||||
containers-count: "{count} containere"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Metrikudtryk
|
||||
expression-valid: Udtryk er gyldigt
|
||||
metric-fields-hint: "Tilgængelige felter: {fields}"
|
||||
sample-window-label: Prøvevindue
|
||||
sample-window-hint: "Alarm udløses, når betingelsen er opfyldt i {duration}"
|
||||
cooldown-label: Afkøling
|
||||
cooldown-hint: "{duration} mellem alarmer pr. container"
|
||||
alert-type: Alarmtype
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Log-Filter
|
||||
metric-filter: Metrik
|
||||
cooldown: Abklingzeit
|
||||
sample-window: Zeitfenster
|
||||
destination: Ziel
|
||||
dispatcher-deleted: Dispatcher gelöscht
|
||||
containers-count: "{count} Container"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Metrik-Ausdruck
|
||||
expression-valid: Ausdruck ist gültig
|
||||
metric-fields-hint: "Verfügbare Felder: {fields}"
|
||||
sample-window-label: Zeitfenster
|
||||
sample-window-hint: "Alarm wird ausgelöst, wenn Bedingung für {duration} erfüllt ist"
|
||||
cooldown-label: Abklingzeit
|
||||
cooldown-hint: "{duration} zwischen Alarmen pro Container"
|
||||
alert-type: Alarmtyp
|
||||
|
||||
@@ -188,6 +188,7 @@ notifications:
|
||||
log-filter: Log filter
|
||||
metric-filter: Metric
|
||||
cooldown: Cooldown
|
||||
sample-window: Sample window
|
||||
destination: Destination
|
||||
dispatcher-deleted: Dispatcher deleted
|
||||
containers-count: "{count} containers"
|
||||
@@ -220,6 +221,8 @@ notifications:
|
||||
metric-filter: Metric Expression
|
||||
expression-valid: Expression is valid
|
||||
metric-fields-hint: "Available fields: {fields}"
|
||||
sample-window-label: Sample Window
|
||||
sample-window-hint: "Alert triggers when condition is met for {duration}"
|
||||
cooldown-label: Cooldown
|
||||
cooldown-hint: "{duration} between alerts per container"
|
||||
alert-type: Alert Type
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Filtro de registro
|
||||
metric-filter: Métrica
|
||||
cooldown: Tiempo de espera
|
||||
sample-window: Ventana de muestreo
|
||||
destination: Destino
|
||||
dispatcher-deleted: Dispatcher eliminado
|
||||
containers-count: "{count} contenedores"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Expresión de Métrica
|
||||
expression-valid: La expresión es válida
|
||||
metric-fields-hint: "Campos disponibles: {fields}"
|
||||
sample-window-label: Ventana de muestreo
|
||||
sample-window-hint: "La alerta se activa cuando la condición se cumple durante {duration}"
|
||||
cooldown-label: Tiempo de espera
|
||||
cooldown-hint: "{duration} entre alertas por contenedor"
|
||||
alert-type: Tipo de alerta
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Filtre de journal
|
||||
metric-filter: Métrique
|
||||
cooldown: Temps d'attente
|
||||
sample-window: Fenêtre d'échantillonnage
|
||||
destination: Destination
|
||||
dispatcher-deleted: Dispatcher supprimé
|
||||
containers-count: "{count} conteneurs"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Expression de Métrique
|
||||
expression-valid: L'expression est valide
|
||||
metric-fields-hint: "Champs disponibles : {fields}"
|
||||
sample-window-label: Fenêtre d'échantillonnage
|
||||
sample-window-hint: "L'alerte se déclenche lorsque la condition est remplie pendant {duration}"
|
||||
cooldown-label: Temps d'attente
|
||||
cooldown-hint: "{duration} entre les alertes par conteneur"
|
||||
alert-type: Type d'alerte
|
||||
|
||||
@@ -191,6 +191,7 @@ notifications:
|
||||
log-filter: Filter log
|
||||
metric-filter: Metrik
|
||||
cooldown: Jeda
|
||||
sample-window: Jendela sampel
|
||||
destination: Tujuan
|
||||
dispatcher-deleted: Dispatcher dihapus
|
||||
containers-count: "{count} kontainer"
|
||||
@@ -223,6 +224,8 @@ notifications:
|
||||
metric-filter: Ekspresi Metrik
|
||||
expression-valid: Ekspresi valid
|
||||
metric-fields-hint: "Field tersedia: {fields}"
|
||||
sample-window-label: Jendela Sampel
|
||||
sample-window-hint: "Peringatan dipicu ketika kondisi terpenuhi selama {duration}"
|
||||
cooldown-label: Jeda
|
||||
cooldown-hint: "{duration} antara peringatan per kontainer"
|
||||
alert-type: Jenis Peringatan
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Filtro log
|
||||
metric-filter: Metrica
|
||||
cooldown: Tempo di attesa
|
||||
sample-window: Finestra di campionamento
|
||||
destination: Destinazione
|
||||
dispatcher-deleted: Dispatcher eliminato
|
||||
containers-count: "{count} container"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Espressione Metrica
|
||||
expression-valid: L'espressione è valida
|
||||
metric-fields-hint: "Campi disponibili: {fields}"
|
||||
sample-window-label: Finestra di campionamento
|
||||
sample-window-hint: "L'avviso si attiva quando la condizione è soddisfatta per {duration}"
|
||||
cooldown-label: Tempo di attesa
|
||||
cooldown-hint: "{duration} tra gli avvisi per container"
|
||||
alert-type: Tipo di avviso
|
||||
|
||||
@@ -182,6 +182,7 @@ notifications:
|
||||
log-filter: 로그 필터
|
||||
metric-filter: 메트릭
|
||||
cooldown: 대기 시간
|
||||
sample-window: 샘플 창
|
||||
destination: 목적지
|
||||
dispatcher-deleted: 디스패처 삭제됨
|
||||
containers-count: "컨테이너 {count}개"
|
||||
@@ -214,6 +215,8 @@ notifications:
|
||||
metric-filter: 메트릭 표현식
|
||||
expression-valid: 표현식이 유효합니다
|
||||
metric-fields-hint: "사용 가능한 필드: {fields}"
|
||||
sample-window-label: 샘플 창
|
||||
sample-window-hint: "조건이 {duration} 동안 충족되면 알림이 트리거됩니다"
|
||||
cooldown-label: 대기 시간
|
||||
cooldown-hint: "컨테이너당 알림 간격 {duration}"
|
||||
alert-type: 알림 유형
|
||||
|
||||
@@ -180,6 +180,7 @@ notifications:
|
||||
log-filter: Logfilter
|
||||
metric-filter: Metriek
|
||||
cooldown: Wachttijd
|
||||
sample-window: Steekproefvenster
|
||||
destination: Bestemming
|
||||
dispatcher-deleted: Dispatcher verwijderd
|
||||
containers-count: "{count} containers"
|
||||
@@ -212,6 +213,8 @@ notifications:
|
||||
metric-filter: Metriekexpressie
|
||||
expression-valid: Expressie is geldig
|
||||
metric-fields-hint: "Beschikbare velden: {fields}"
|
||||
sample-window-label: Steekproefvenster
|
||||
sample-window-hint: "Waarschuwing wordt geactiveerd wanneer de voorwaarde gedurende {duration} is voldaan"
|
||||
cooldown-label: Wachttijd
|
||||
cooldown-hint: "{duration} tussen waarschuwingen per container"
|
||||
alert-type: Waarschuwingstype
|
||||
|
||||
@@ -186,6 +186,7 @@ notifications:
|
||||
log-filter: Filtr logów
|
||||
metric-filter: Metryka
|
||||
cooldown: Czas oczekiwania
|
||||
sample-window: Okno próbkowania
|
||||
destination: Miejsce docelowe
|
||||
dispatcher-deleted: Dispatcher usunięty
|
||||
containers-count: "{count} kontenerów"
|
||||
@@ -218,6 +219,8 @@ notifications:
|
||||
metric-filter: Wyrażenie metryki
|
||||
expression-valid: Wyrażenie jest poprawne
|
||||
metric-fields-hint: "Dostępne pola: {fields}"
|
||||
sample-window-label: Okno próbkowania
|
||||
sample-window-hint: "Alert jest wyzwalany, gdy warunek jest spełniony przez {duration}"
|
||||
cooldown-label: Czas oczekiwania
|
||||
cooldown-hint: "{duration} między alertami na kontener"
|
||||
alert-type: Typ alertu
|
||||
|
||||
@@ -188,6 +188,7 @@ notifications:
|
||||
log-filter: Filtro de registo
|
||||
metric-filter: Métrica
|
||||
cooldown: Tempo de espera
|
||||
sample-window: Janela de amostragem
|
||||
destination: Destino
|
||||
dispatcher-deleted: Dispatcher eliminado
|
||||
containers-count: "{count} contentores"
|
||||
@@ -220,6 +221,8 @@ notifications:
|
||||
metric-filter: Expressão de Métrica
|
||||
expression-valid: A expressão é válida
|
||||
metric-fields-hint: "Campos disponíveis: {fields}"
|
||||
sample-window-label: Janela de amostragem
|
||||
sample-window-hint: "O alerta é ativado quando a condição é cumprida durante {duration}"
|
||||
cooldown-label: Tempo de espera
|
||||
cooldown-hint: "{duration} entre alertas por contentor"
|
||||
alert-type: Tipo de alerta
|
||||
|
||||
@@ -178,6 +178,7 @@ notifications:
|
||||
log-filter: Filtro de log
|
||||
metric-filter: Métrica
|
||||
cooldown: Tempo de espera
|
||||
sample-window: Janela de amostragem
|
||||
destination: Destino
|
||||
dispatcher-deleted: Dispatcher excluído
|
||||
containers-count: "{count} containers"
|
||||
@@ -210,6 +211,8 @@ notifications:
|
||||
metric-filter: Expressão de Métrica
|
||||
expression-valid: A expressão é válida
|
||||
metric-fields-hint: "Campos disponíveis: {fields}"
|
||||
sample-window-label: Janela de amostragem
|
||||
sample-window-hint: "O alerta é acionado quando a condição é atendida por {duration}"
|
||||
cooldown-label: Tempo de espera
|
||||
cooldown-hint: "{duration} entre alertas por container"
|
||||
alert-type: Tipo de alerta
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Фильтр логов
|
||||
metric-filter: Метрика
|
||||
cooldown: Период ожидания
|
||||
sample-window: Окно выборки
|
||||
destination: Назначение
|
||||
dispatcher-deleted: Диспетчер удалён
|
||||
containers-count: "{count} контейнеров"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Выражение метрики
|
||||
expression-valid: Выражение корректно
|
||||
metric-fields-hint: "Доступные поля: {fields}"
|
||||
sample-window-label: Окно выборки
|
||||
sample-window-hint: "Оповещение срабатывает, когда условие выполняется в течение {duration}"
|
||||
cooldown-label: Период ожидания
|
||||
cooldown-hint: "{duration} между оповещениями на контейнер"
|
||||
alert-type: Тип оповещения
|
||||
|
||||
@@ -184,6 +184,7 @@ notifications:
|
||||
log-filter: Filter dnevnika
|
||||
metric-filter: Metrika
|
||||
cooldown: Čas mirovanja
|
||||
sample-window: Vzorčno okno
|
||||
destination: Cilj
|
||||
dispatcher-deleted: Pošiljatelj izbrisan
|
||||
containers-count: "{count} zabojnikov"
|
||||
@@ -216,6 +217,8 @@ notifications:
|
||||
metric-filter: Izraz metrike
|
||||
expression-valid: Izraz je veljaven
|
||||
metric-fields-hint: "Razpoložljiva polja: {fields}"
|
||||
sample-window-label: Vzorčno okno
|
||||
sample-window-hint: "Opozorilo se sproži, ko je pogoj izpolnjen za {duration}"
|
||||
cooldown-label: Čas mirovanja
|
||||
cooldown-hint: "{duration} med opozorili na zabojnik"
|
||||
alert-type: Vrsta opozorila
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: Günlük filtresi
|
||||
metric-filter: Metrik
|
||||
cooldown: Bekleme süresi
|
||||
sample-window: Örnek penceresi
|
||||
destination: Hedef
|
||||
dispatcher-deleted: Dağıtıcı silindi
|
||||
containers-count: "{count} konteyner"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: Metrik İfadesi
|
||||
expression-valid: İfade geçerli
|
||||
metric-fields-hint: "Kullanılabilir alanlar: {fields}"
|
||||
sample-window-label: Örnek Penceresi
|
||||
sample-window-hint: "Koşul {duration} boyunca karşılandığında uyarı tetiklenir"
|
||||
cooldown-label: Bekleme süresi
|
||||
cooldown-hint: "Konteyner başına uyarılar arası {duration}"
|
||||
alert-type: Uyarı Türü
|
||||
|
||||
@@ -182,6 +182,7 @@ notifications:
|
||||
log-filter: 日誌篩選器
|
||||
metric-filter: 指標
|
||||
cooldown: 冷卻時間
|
||||
sample-window: 取樣視窗
|
||||
destination: 目標
|
||||
dispatcher-deleted: 發送器已刪除
|
||||
containers-count: "{count} 個容器"
|
||||
@@ -214,6 +215,8 @@ notifications:
|
||||
metric-filter: 指標表達式
|
||||
expression-valid: 表達式有效
|
||||
metric-fields-hint: "可用欄位:{fields}"
|
||||
sample-window-label: 取樣視窗
|
||||
sample-window-hint: "當條件在 {duration} 內滿足時觸發警報"
|
||||
cooldown-label: 冷卻時間
|
||||
cooldown-hint: "每個容器警報間隔 {duration}"
|
||||
alert-type: 警報類型
|
||||
|
||||
@@ -179,6 +179,7 @@ notifications:
|
||||
log-filter: 日志筛选器
|
||||
metric-filter: 指标
|
||||
cooldown: 冷却时间
|
||||
sample-window: 采样窗口
|
||||
destination: 目标
|
||||
dispatcher-deleted: 发送器已删除
|
||||
containers-count: "{count} 个容器"
|
||||
@@ -211,6 +212,8 @@ notifications:
|
||||
metric-filter: 指标表达式
|
||||
expression-valid: 表达式有效
|
||||
metric-fields-hint: "可用字段:{fields}"
|
||||
sample-window-label: 采样窗口
|
||||
sample-window-hint: "当条件在 {duration} 内满足时触发警报"
|
||||
cooldown-label: 冷却时间
|
||||
cooldown-hint: "每个容器警报间隔 {duration}"
|
||||
alert-type: 警报类型
|
||||
|
||||
@@ -100,6 +100,9 @@ message NotificationSubscription {
|
||||
int32 dispatcherId = 4;
|
||||
string logExpression = 5;
|
||||
string containerExpression = 6;
|
||||
string metricExpression = 7;
|
||||
int32 cooldown = 8;
|
||||
int32 sampleWindow = 9;
|
||||
}
|
||||
|
||||
message NotificationDispatcher {
|
||||
|
||||
@@ -61,6 +61,7 @@ type SubscriptionConfig struct {
|
||||
ContainerExpression string `json:"containerExpression"`
|
||||
MetricExpression string `json:"metricExpression,omitempty"`
|
||||
Cooldown int `json:"cooldown,omitempty"`
|
||||
SampleWindow int `json:"sampleWindow,omitempty"`
|
||||
}
|
||||
|
||||
// DispatcherConfig represents a notification dispatcher configuration
|
||||
|
||||
Reference in New Issue
Block a user