mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
* debrand: signoz/SigNoz/SIGNOZ -> o11y/O11y/O11Y across the tree Rename all SigNoz *branding* to O11y in file contents, file/dir names, package names, env vars, config keys, comments, docs, SDK code, and the ClickHouse schema identifiers the querier reads. Collector dependency: - Repoint github.com/hanzoai/signoz-otel-collector -> github.com/hanzoai/otel-collector and bump v0.144.6 -> v0.144.7 (go.mod + go.sum reconciled via go mod tidy). Internal package paths also debranded upstream: signozschemamigrator -> o11yschemamigrator, signozlogspipelineprocessor -> o11ylogspipelineprocessor. - Fix: v0.144.7 dropped otelconst.DistributedFieldKeysTable from the collector's public constants; pin FieldKeysTable = "distributed_field_keys" locally in pkg/telemetrymetadata/tables.go. Package/dir renames (package decl + all importers): pkg/apiserver/signozapiserver -> o11yapiserver, plus signozalertmanager, signozauthzapi, signozglobal, signozquerier, signozruler, and pkg/signoz -> pkg/o11y. Schema (read plane): signoz_traces/metrics/logs/metadata/analytics/meter and signoz_index_v3/index_v2/error_index_v2/spans (+ distributed_*) -> o11y_*. Data-preserving cutover migration added: deploy/clickhouse/migrations/0001_rename_signoz_to_o11y.sql (metadata-only RENAME DATABASE/TABLE, no drop/recreate). Preserved (attribution / external / wire contracts): - LICENSE + NOTICE verbatim (incl. "software from SigNoz", "Copyright ... SigNoz Inc."). - Upstream github.com/SigNoz/* repo URLs + @SigNoz/* CODEOWNERS teams. - Billing resource-attribute regex "signoz.workspace.*" (external wire contract). go build ./pkg/... ./cmd/... = 0; gofmt clean; renamed+schema pkg tests pass. * o11y: bump collector to v0.144.8 (o11y_* physical schema writer) + lockstep deploy plan Collector v0.144.8's schema-migrator + exporters now CREATE/WRITE the same o11y_* physical databases/tables the querier reads and the RENAME migration (0001_rename_signoz_to_o11y.sql) targets. Cross-checked byte-identical: writer DB set == migration target DB set (6); writer prefixed-table set == migration target table set (8); o11y querier reads and cloud reads are subsets of that set. Zero table-name mismatches between writer, readers, and migration. Collector go.mod unchanged between v0.144.7 and v0.144.8 (identical go.mod hash) — pure source rename, no module-graph change. Adds deploy/clickhouse/DEPLOY_signoz_to_o11y_cutover.md: the lockstep order (collector v0.144.8 -> RENAME migration -> o11y+cloud readers) with a fresh/scratch-ClickHouse pre-flight (create via migrator, emit trace+metric, query back) and rollback. Ships together or telemetry blackholes. go build ./pkg/... ./cmd/community = 0.
144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
// Copyright (c) 2026 O11y, Inc.
|
|
// Copyright 2019 Prometheus Team
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package webhook
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/o11y/pkg/errors"
|
|
"github.com/hanzoai/o11y/pkg/types/alertmanagertypes"
|
|
commoncfg "github.com/prometheus/common/config"
|
|
|
|
"github.com/prometheus/alertmanager/config"
|
|
"github.com/prometheus/alertmanager/notify"
|
|
"github.com/prometheus/alertmanager/template"
|
|
"github.com/prometheus/alertmanager/types"
|
|
)
|
|
|
|
const (
|
|
Integration = "webhook"
|
|
)
|
|
|
|
// Notifier implements a Notifier for generic webhooks.
|
|
type Notifier struct {
|
|
conf *config.WebhookConfig
|
|
tmpl *template.Template
|
|
logger *slog.Logger
|
|
client *http.Client
|
|
retrier *notify.Retrier
|
|
templater alertmanagertypes.Templater
|
|
}
|
|
|
|
// New returns a new Webhook.
|
|
func New(conf *config.WebhookConfig, t *template.Template, l *slog.Logger, templater alertmanagertypes.Templater, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
|
client, err := notify.NewClientWithTracing(*conf.HTTPConfig, Integration, httpOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Notifier{
|
|
conf: conf,
|
|
tmpl: t,
|
|
logger: l,
|
|
client: client,
|
|
// Webhooks are assumed to respond with 2xx response codes on a successful
|
|
// request and 5xx response codes are assumed to be recoverable.
|
|
retrier: ¬ify.Retrier{},
|
|
templater: templater,
|
|
}, nil
|
|
}
|
|
|
|
// Message defines the JSON object send to webhook endpoints.
|
|
type Message struct {
|
|
*template.Data
|
|
|
|
// The protocol version.
|
|
Version string `json:"version"`
|
|
GroupKey string `json:"groupKey"`
|
|
TruncatedAlerts uint64 `json:"truncatedAlerts"`
|
|
}
|
|
|
|
func truncateAlerts(maxAlerts uint64, alerts []*types.Alert) ([]*types.Alert, uint64) {
|
|
if maxAlerts != 0 && uint64(len(alerts)) > maxAlerts {
|
|
return alerts[:maxAlerts], uint64(len(alerts)) - maxAlerts
|
|
}
|
|
|
|
return alerts, 0
|
|
}
|
|
|
|
// Notify implements the Notifier interface.
|
|
func (n *Notifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, error) {
|
|
alerts, numTruncated := truncateAlerts(n.conf.MaxAlerts, alerts)
|
|
data := notify.GetTemplateData(ctx, n.tmpl, alerts, n.logger)
|
|
|
|
groupKey, err := notify.ExtractGroupKey(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
logger := n.logger.With(slog.Any("group_key", groupKey))
|
|
logger.DebugContext(ctx, "extracted group key")
|
|
|
|
msg := &Message{
|
|
Version: "4",
|
|
Data: data,
|
|
GroupKey: groupKey.String(),
|
|
TruncatedAlerts: numTruncated,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
var url string
|
|
var tmplErr error
|
|
tmpl := notify.TmplText(n.tmpl, data, &tmplErr)
|
|
|
|
if n.conf.URL != "" {
|
|
url = tmpl(string(n.conf.URL))
|
|
} else {
|
|
content, err := os.ReadFile(n.conf.URLFile)
|
|
if err != nil {
|
|
return false, errors.WrapInternalf(err, errors.CodeInternal, "read url_file")
|
|
}
|
|
url = tmpl(strings.TrimSpace(string(content)))
|
|
}
|
|
|
|
if tmplErr != nil {
|
|
return false, errors.NewInternalf(errors.CodeInternal, "failed to template webhook URL: %v", tmplErr)
|
|
}
|
|
|
|
if url == "" {
|
|
return false, errors.NewInternalf(errors.CodeInternal, "webhook URL is empty after templating")
|
|
}
|
|
|
|
if n.conf.Timeout > 0 {
|
|
postCtx, cancel := context.WithTimeoutCause(ctx, n.conf.Timeout, errors.NewInternalf(errors.CodeTimeout, "configured webhook timeout reached (%s)", n.conf.Timeout))
|
|
defer cancel()
|
|
ctx = postCtx
|
|
}
|
|
|
|
resp, err := notify.PostJSON(ctx, n.client, url, &buf) //nolint:bodyclose
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
err = errors.NewInternalf(errors.CodeInternal, "failed to post JSON to webhook: %v", context.Cause(ctx))
|
|
}
|
|
return true, notify.RedactURL(err)
|
|
}
|
|
defer notify.Drain(resp)
|
|
|
|
shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body)
|
|
if err != nil {
|
|
return shouldRetry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err)
|
|
}
|
|
return shouldRetry, err
|
|
}
|