Repository navigation
Conversation
Picks up grafana/prometheus-alertmanager#165, which mirrors upstream's config package layout (shared types moved to config/common, several notifier configs moved to notify/<channel>). This module will not compile until the fallout is fixed in a follow-up commit. Ref: #694
Update qualifiers for types that moved with grafana/prometheus-alertmanager#165: NotifierConfig, URL, SecretURL, MatchRegexps, Matchers and InhibitRule now live in config/common; DiscordConfig, WebhookConfig, MSTeamsConfig, MSTeamsV2Config, JiraConfig, PagerdutyConfig (and PagerdutyImage/Link), OpsGenieConfig (and OpsGenieConfigResponder) and TelegramConfig now live in their respective notify/<channel> packages. PagerDuty ServiceKey/RoutingKey, OpsGenie APIKey and Telegram BotToken are now commoncfg.Secret instead of the fork's own config.Secret. No behavior change: same fields, same JSON/YAML output, only import paths and type qualifiers differ. Ref: #694
Bridges a go-kit/log.Logger to *slog.Logger for the boundary where grafana/alerting will call into the (slog-based) prometheus-alertmanager fork, without touching the go-kit log lines grafana/grafana and grafana-alertmanager emit today. Golden-line tests cover level mapping, Debug filtering, WithAttrs/WithGroup flattening, and the two known github.com/tjhop/slog-gokit defects (an extra 'time' key and an extra, wrongly-depthed 'caller' key) across three consumer decoration styles. Caller parity is not fully solvable from within grafana/alerting: consumers that bake a fixed-stack-depth caller Valuer into their go-kit logger before handing it to us (e.g. grafana-alertmanager's InitLogger, spanlogger.Caller(6|7)) get a wrong caller through this adapter, because the extra slog call frames shift the depth the Valuer walks, and the same shared logger instance is also used for direct (non-slog) call sites where the original depth is correct. TestCallerParity documents the measured gap.
Fork branch refactor/adopt-slog (grafana/prometheus-alertmanager#167), which adopts log/slog and drops go-kit/log throughout. Breaks every exported call into the fork that used to take a go-kit log.Logger; fixed in the following commit.
- appendAttr forwarded the slog.Value wrapper instead of its resolved underlying value: typed attrs (int, bool, nil, time.Time, structs) came out wrong (e.g. quoted strings) through the adapter but not through a direct go-kit call. Use a.Value.Any() after resolving. - Enabled(Debug) only detected DebugEnabled() on the exact logger instance passed to NewSlogLogger; wrapping it first with log.With(...) (this package's own scoping pattern) hides the method behind an opaque *log.context, silently losing detection. Document which of grafana-alertmanager and grafana/grafana's logger constructions can be detected at all, and add WithDebugEnabled so a caller that wraps before handing off can supply the answer explicitly. - Rebuilt the golden tests: real ordered logfmt decoding (go-logfmt/logfmt) instead of an unordered map, type-aware JSON assertions, every level (not just Info), typed values, slog.LogValuer, nested/inline groups, independent With branches, and fixtures that mirror each consumer's actual construction (including grafana-alertmanager's own level filter). TestCallerParity now pins the actual observed caller value per path instead of only asserting the two differ, which could pass for the wrong reason.
The fork (bumped in the previous commit) now takes *slog.Logger at its public boundaries. grafana/alerting's own public API stays on go-kit's log.Logger (GrafanaAlertmanagerOpts.Logger, v1 receiver constructors, TestTemplate, NewGrafanaAlertmanagerMetrics); every call site that reaches into the fork now wraps with logging.NewSlogLogger before crossing that boundary. Found by the compiler, not by grepping: - notify/grafana_alertmanager.go: compat.InitFromFlags, nflog.New, flushlog.New, mem.NewAlerts, inhibit.NewInhibitor, silence.NewSilencer, dispatch.NewDispatcher, notify.GetTemplateData (in TestTemplate), via two new helpers, forkLogger/forkLoggerRaw, matching the two existing raw/scoped go-kit logger shapes this file already used. - notify/grafana_alertmanager_metrics.go: metrics.NewAlerts. - notify/stages/wait_stage.go: WaitStage.Exec, which implements notify.Stage -- its logger parameter is now *slog.Logger like every other Stage implementation upstream converted. - templates/template_data.go and every v0mimir1/v0mimir2 receiver's Notify(): notify.GetTemplateData. The receivers themselves (New(), n.logger) stay on go-kit -- only this one call crosses into the fork. - cluster/cluster.go: Create was a bare re-export, so its signature would have silently changed along with the fork's; grafana/grafana calls this with a go-kit logger (pkg/services/ngalert/notifier/multiorg_alertmanager.go), so it's now a real wrapper function that keeps the go-kit signature and adapts internally. Audited every other var/type alias in cluster/ and the rest of the repo for the same problem -- nothing else re-exports a logger-carrying fork signature. config.NewCoordinator is unused in this repo, so it needed no change. Checked with the fork's compiler errors, not by inspection: after this commit, go build/vet is clean (root module and apps/historian, in and out of workspace mode) and go test -race passes, except the pre-existing apps/historian/cmd/historian-operator TestMain_OperatorComesUp race that also fails on main.
Commander decision, 2026-09-25: for the caller-parity gap documented in slog-caller-report.md, add GrafanaAlertmanagerOpts.LoggerWithoutCaller, an optional go-kit logger equivalent to Logger but without its own "caller" field, used only for the loggers handed into the fork. The grafana-alertmanager side (building and passing that logger) is a later PR, not in this repo. - logging.WithCaller(): when set, the adapter adds exactly one "caller" field per record, in go-kit's own basename:line format, derived from slog.Record.PC -- the call site slog itself captured, correct regardless of how many extra frames the adapter or slog add on top (unlike a go-kit Valuer's fixed-skip stack walk). Must only be paired with a logger that doesn't already carry a "caller" field; the adapter cannot detect or strip one already baked into an opaque go-kit log.Logger, and combining the two produces a visible duplicate (logfmt) or a silent overwrite (JSON, which deduplicates keys, last write wins) -- documented on the option and exercised by TestOptInCaller_NeverDoubledWithExistingCaller. - GrafanaAlertmanager.forkLogger/forkLoggerRaw (added wrapping the fork boundary in the previous commit) now check LoggerWithoutCaller: set, they wrap it with WithCaller(); unset, behavior is unchanged from the previous commit -- am.logger/opts.Logger wrapped with no caller added, so grafana/grafana's output (which never opts in) stays byte-identical. - Golden tests: a grafana-alertmanager-style logger (ts baked in) minus its own caller field, with the option set, gets the real call line exactly once; a grafana/grafana-style logger, option unset, gets no caller key at all; and the same pair through GrafanaAlertmanager.forkLogger/ forkLoggerRaw directly.
Per log line, ns/op and allocs/op for: direct go-kit, the adapter with no options, the adapter with WithCaller() (option b+d, replaces what would otherwise have been a prototype -- the real option landed in the previous commit), and the adapter with debug disabled while logging at debug (using a grafana-alertmanager-shaped logger, i.e. one DebugEnabled() can actually be detected on, so this measures the Enabled() fast path rather than building-then-dropping). Realistic call: a message plus 6 key/values, logfmt writer to io.Discard. linux/amd64, Intel Core Ultra 9 185H, go1.26.4: BenchmarkLogLine_DirectGoKit-6 988.8 ns/op 372 B/op 4 allocs/op BenchmarkLogLine_Adapter-6 1923 ns/op 865 B/op 18 allocs/op BenchmarkLogLine_AdapterWithCaller-6 2202 ns/op 1185 B/op 22 allocs/op BenchmarkLogLine_AdapterDebugDisabled-6 6.196 ns/op 0 B/op 0 allocs/op The adapter costs roughly 2x direct go-kit for the enabled-level path (slog's own Record/Attr machinery plus our flattening), and WithCaller() adds a further ~15% for the runtime.CallersFrames lookup. When the level is actually disabled and DebugEnabled() can be detected, Enabled() skips building the record entirely -- effectively free, several orders of magnitude cheaper than either enabled path.
- Handle() emitted msg before preformatted (WithAttrs-bound) attrs, the
reverse of go-kit's own log.With ordering ("With returns a new
contextual logger with keyvals prepended to those passed to calls to
Log"). A real fork call shaped as
log.With(am.logger, "aggrGroup", g, "group_fingerprint", f).Log("msg", m, "alerts", a)
emits aggrGroup/group_fingerprint before msg; the adapter emitted msg
first. Swap the order.
- forkLogger/forkLoggerRaw built a scoped go-kit logger via log.With(...)
and adapted that, losing DebugEnabled() detection on the way (the same
opaque-wrapping problem NewSlogLogger's own doc comment describes) even
though the pre-scoping logger (opts.Logger / opts.LoggerWithoutCaller)
supports it. Probe DebugEnabled() there, before scoping, and forward it
via WithDebugEnabled.
- Golden-test fixture gaLevelFilter only embedded log.Logger without
overriding Log(), so it was one stack frame shallower than
grafana-alertmanager's real levelFilter (which overrides Log to
intercept a private probe value) -- not faithful for caller-depth
measurements. Added the override.
- TestCallerParity_GrafanaAlertmanagerStyle: rebuilt using the corrected
fixture plus the "component"/tenant log.With scoping grafana/alerting's
own am.logger applies in production, and replaced every hardcoded
expected-line-number constant (here and in
TestOptInCaller_GrafanaAlertmanagerStyleMinusCaller and
TestGrafanaAlertmanager_forkLogger) with a nextLine(t) helper
(runtime.Caller-based) so edits can't silently go stale. With the
faithful fixture shape, the direct path now resolves to the real call
site (it didn't before); the adapter path is asserted to land inside our
own adapter file, not pinned to a specific internal file:line (compiler-
and Go-version-sensitive, and not the point being tested).
- Golden line comparisons stripped ts/t/caller without checking they were
even there: added assertTimeAndCallerShape, checked for every
fixture/level/path, asserting presence exactly once and RFC3339Nano
format for the timestamp key each consumer actually binds (ts or t), and
that plain-logfmt/grafana-grafana (which never bind a caller in these
tests) don't have one.
Same checks as before: go build/vet clean (root module and apps/historian),
make lint 0 issues, make mod-check clean, go test -tags netgo -race
-count 1 ./... passes in both modules except the pre-existing
apps/historian/cmd/historian-operator TestMain_OperatorComesUp race.
Requested by review: the committed suite as of 54f738f could not actually catch the field-order bug it fixed -- every committed test still passed after temporarily reverting Handle() to put "msg" before h.preformatted again (verified locally, then restored; not a permanent change). - TestGoldenLines_BoundAttributeOrder_Logfmt: two binding layers -- a go-kit-level one (component/orgID, on the underlying logger itself, applied identically on both paths and not exercising the bug) and a slog-level one via our own WithAttrs (aggrGroup/group_fingerprint, mirroring dispatch.go's aggrGroup flush line), then a message plus event attrs (alerts). Compares direct vs adapter output as an ordered slice, which catches a transposed msg/aggrGroup pair, not just a missing or extra field. Verified this fails on the reverted code and passes on the fix. - TestGoldenLines_BoundMsgOverride_JSON: a bound "msg" (log.With(l, "msg", ...)) combined with the event's own msg at call time. go-kit's JSON logger dedupes by last-write-wins, so the event message must win in both paths; asserts that directly instead of relying on a duplicate-key count, which JSON's dedup would hide. Same checks: go build/vet clean, make lint 0 issues, make mod-check clean, go test -tags netgo -race -count 1 ./logging/... passes (only logging/ touched).
Review found it bound "msg" on the underlying go-kit logger
(log.With(newJSONLogger(...), "msg", "bound-msg")), leaving
h.preformatted empty -- the test still passed with Handle() reverted to
put "msg" before h.preformatted, same as the logfmt test's original gap.
Bind it via our own WithAttrs instead
(NewSlogLogger(...).With("msg", "bound-msg")), matching
TestGoldenLines_BoundAttributeOrder_Logfmt's two-binding-layer approach.
Verified (then restored) that this version does fail under the reverted
ordering: bound-msg wins instead of event-msg.
Port receivers/sns/v1 and receivers/sns/v0mimir1 from aws-sdk-go v1 to aws-sdk-go-v2, keeping the existing credential, assume-role, endpoint and HTTP client behaviour. Publish now honours the notification context. aws-sdk-go v1 remains an indirect dependency through prometheus/common/sigv4, which the alertmanager fork's SNSConfig uses.
Bump grafana/prometheus-alertmanager to 8b0db09d6c8a (branch moustafab/aws-sdk-go-v2), which moves the SNS notifier to aws-sdk-go-v2 and github.com/prometheus/sigv4. Switch the SNS compat conversion to github.com/prometheus/sigv4 accordingly. github.com/aws/aws-sdk-go and github.com/prometheus/common/sigv4 are no longer in the module graph.
yuri-tceretian
force-pushed
the
yuri-tseretyan/slog-adapter
branch
2 times, most recently
from
October 7, 2026 18:43
d30e09b to
de78442
Compare
Bridge go-kit logging at fork call sites. Add adapter tests and benchmarks, and pin both modules to the fork main revision that provides the slog API. Keep caller reporting available to users of logging.WithCaller().
yuri-tceretian
force-pushed
the
yuri-tseretyan/slog-adapter
branch
from
October 7, 2026 19:17
de78442 to
28c6a84
Compare
…stacked # Conflicts: # apps/historian/go.mod # apps/historian/go.sum # go.mod # go.sum
…o moustafab/aws-sdk-v2-stacked # Conflicts: # apps/historian/go.mod # apps/historian/go.sum # go.mod # go.sum # go.work.sum # logging/slog_test.go # notify/grafana_alertmanager.go # notify/grafana_alertmanager_test.go
titolins
marked this pull request as ready for review
October 14, 2026 08:30
konstantinmv
previously approved these changes
Oct 14, 2026
titolins
marked this pull request as draft
October 14, 2026 13:59
Collaborator
|
I was planning to try and deliver this but as talked about directly we need to discuss a few things. Moved back to draft for now |
yuri-tceretian
dismissed
konstantinmv’s stale review
October 14, 2026 14:45
The base branch was changed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Stacked on #696 (which is stacked on #695). The base branch is
yuri-tseretyan/slog-adapter, so this diff shows only the two commits below. It needs grafana/prometheus-alertmanager#168.This removes
github.com/aws/aws-sdk-go(v1, end of support) from grafana/alerting entirely. v1 carries CVE-2020-8911 and CVE-2020-8912 (GO-2022-0646 / GO-2022-0635, ins3crypto, no fix available). Tracking issue: grafana/alerting-squad#2185.Commits
receivers/sns/v1(Grafana) andreceivers/sns/v0mimir1(Mimir) fromsession/credentials/stscreds/snsv1 toconfig.LoadDefaultConfig,credentials,stscreds.NewAssumeRoleProviderandsns.NewFromConfig. The goal is to keep existing behaviour for credentials, profile, region, assume-role,api_urland the HTTP client:api_urlsemantics are unchanged. v2 requires a full URL, sowithDefaultSchemeaddshttps://when the scheme is missing, the same way v1'sendpoints.AddSchemedid.v0mimir1, the user'shttp_configclient is set only on the SNS client, as before. It's set per service rather than throughconfig.WithHTTPClient, which also avoids the v2AWS_CA_BUNDLEfailure that upstream fixed in alertmanager#5178.RoleSessionNamekeeps the v1 format.v0mimir1, HTTP errors still map to the retrier andErrorWithReasonthroughsmithyhttp.ResponseError.Publishnow honours the notification context.aws/arnmoves toaws-sdk-go-v2/aws/arn.api_url, a 400 error mapping to not-retryable plusClientErrorReason, a missing region, and the scheme default.8b0db09d6c8a(Upgrade SNS notifier to aws-sdk-go-v2 and prometheus/sigv4 prometheus-alertmanager#168) and switchesdefinition/compattogithub.com/prometheus/sigv4. The new fork brings some indirect upgrades, e.g. sprig 3.3.0, go-openapi/strfmt 0.26.3 and snappy 1.0.0.This PR deliberately keeps how the existing v1 code chooses credentials for the STS
AssumeRolecall whensigv4.role_arnis set. That choice depends on whetherapi_urlis set and whether static keys are configured; see the comment increateSNSClient. Upstream alertmanager changed this logic when it migrated to v2 (prometheus/alertmanager#4482), and the fork now follows upstream (#168).Decision needed before this leaves draft: keep the current behaviour for Grafana-managed SNS contact points, or align with upstream? Either way, it should be its own reviewed change, not something that slips in with the SDK migration. Related: there's no
external_idsupport today (upstream added it in prometheus/alertmanager#5157).Other behaviour differences in v2 (worth a release note)
sts.<region>.amazonaws.comendpoint. v1 defaulted to the globalsts.amazonaws.com.~/.aws/configas well as the credentials file. That means profileregion, SSO andcredential_processsettings now apply. A named profile that doesn't exist now fails when the client is built, not at publish.AWS_DEFAULT_REGION,AWS_ENDPOINT_URLandAWS_ENDPOINT_URL_SNSare now honoured when the corresponding setting is empty.operation error SNS: Publish, https response error StatusCode: …). They show up in contact-point test results and notification history.Not done here
use_aws_http_client, which the fork'sSNSConfiggained in #168, is not mirrored inv0mimir1. Our notifier sets the HTTP client per service, so the CA-bundle problem it works around doesn't come up. We could still accept the field for Mimir config parity; that's open for discussion.Verification
go build ./...andgo vet ./...pass.go test -tags netgo -timeout 30m -race -count 1 ./...passes (58 packages).make lintreports 0 issues, andmake mod-checkpasses.go mod why -m github.com/aws/aws-sdk-goandgo mod why -m github.com/prometheus/common/sigv4both report "main module does not need module".