bump reva 2.28
This commit is contained in:
-5
@@ -11,8 +11,3 @@ Bootstrap
|
||||
http://getbootstrap.com
|
||||
Copyright 2011-2014 Twitter, Inc.
|
||||
Licensed under the MIT License
|
||||
|
||||
bootstrap-datetimepicker.js
|
||||
http://www.eyecon.ro/bootstrap-datepicker
|
||||
Copyright 2012 Stefan Petre
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
+5
-7
File diff suppressed because one or more lines are too long
+44
-8
@@ -16,35 +16,41 @@ package featurecontrol
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
)
|
||||
|
||||
const (
|
||||
FeatureReceiverNameInMetrics = "receiver-name-in-metrics"
|
||||
FeatureClassicMode = "classic-mode"
|
||||
FeatureUTF8StrictMode = "utf8-strict-mode"
|
||||
FeatureAutoGOMEMLIMIT = "auto-gomemlimit"
|
||||
FeatureAutoGOMAXPROCS = "auto-gomaxprocs"
|
||||
)
|
||||
|
||||
var AllowedFlags = []string{
|
||||
FeatureReceiverNameInMetrics,
|
||||
FeatureClassicMode,
|
||||
FeatureUTF8StrictMode,
|
||||
FeatureAutoGOMEMLIMIT,
|
||||
FeatureAutoGOMAXPROCS,
|
||||
}
|
||||
|
||||
type Flagger interface {
|
||||
EnableReceiverNamesInMetrics() bool
|
||||
ClassicMode() bool
|
||||
UTF8StrictMode() bool
|
||||
EnableAutoGOMEMLIMIT() bool
|
||||
EnableAutoGOMAXPROCS() bool
|
||||
}
|
||||
|
||||
type Flags struct {
|
||||
logger log.Logger
|
||||
logger *slog.Logger
|
||||
enableReceiverNamesInMetrics bool
|
||||
classicMode bool
|
||||
utf8StrictMode bool
|
||||
enableAutoGOMEMLIMIT bool
|
||||
enableAutoGOMAXPROCS bool
|
||||
}
|
||||
|
||||
func (f *Flags) EnableReceiverNamesInMetrics() bool {
|
||||
@@ -59,6 +65,14 @@ func (f *Flags) UTF8StrictMode() bool {
|
||||
return f.utf8StrictMode
|
||||
}
|
||||
|
||||
func (f *Flags) EnableAutoGOMEMLIMIT() bool {
|
||||
return f.enableAutoGOMEMLIMIT
|
||||
}
|
||||
|
||||
func (f *Flags) EnableAutoGOMAXPROCS() bool {
|
||||
return f.enableAutoGOMAXPROCS
|
||||
}
|
||||
|
||||
type flagOption func(flags *Flags)
|
||||
|
||||
func enableReceiverNameInMetrics() flagOption {
|
||||
@@ -79,7 +93,19 @@ func enableUTF8StrictMode() flagOption {
|
||||
}
|
||||
}
|
||||
|
||||
func NewFlags(logger log.Logger, features string) (Flagger, error) {
|
||||
func enableAutoGOMEMLIMIT() flagOption {
|
||||
return func(configs *Flags) {
|
||||
configs.enableAutoGOMEMLIMIT = true
|
||||
}
|
||||
}
|
||||
|
||||
func enableAutoGOMAXPROCS() flagOption {
|
||||
return func(configs *Flags) {
|
||||
configs.enableAutoGOMAXPROCS = true
|
||||
}
|
||||
}
|
||||
|
||||
func NewFlags(logger *slog.Logger, features string) (Flagger, error) {
|
||||
fc := &Flags{logger: logger}
|
||||
opts := []flagOption{}
|
||||
|
||||
@@ -91,13 +117,19 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) {
|
||||
switch feature {
|
||||
case FeatureReceiverNameInMetrics:
|
||||
opts = append(opts, enableReceiverNameInMetrics())
|
||||
level.Warn(logger).Log("msg", "Experimental receiver name in metrics enabled")
|
||||
logger.Warn("Experimental receiver name in metrics enabled")
|
||||
case FeatureClassicMode:
|
||||
opts = append(opts, enableClassicMode())
|
||||
level.Warn(logger).Log("msg", "Classic mode enabled")
|
||||
logger.Warn("Classic mode enabled")
|
||||
case FeatureUTF8StrictMode:
|
||||
opts = append(opts, enableUTF8StrictMode())
|
||||
level.Warn(logger).Log("msg", "UTF-8 strict mode enabled")
|
||||
logger.Warn("UTF-8 strict mode enabled")
|
||||
case FeatureAutoGOMEMLIMIT:
|
||||
opts = append(opts, enableAutoGOMEMLIMIT())
|
||||
logger.Warn("Automatically set GOMEMLIMIT to match the Linux container or system memory limit.")
|
||||
case FeatureAutoGOMAXPROCS:
|
||||
opts = append(opts, enableAutoGOMAXPROCS())
|
||||
logger.Warn("Automatically set GOMAXPROCS to match Linux container CPU quota")
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature)
|
||||
}
|
||||
@@ -121,3 +153,7 @@ func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false }
|
||||
func (n NoopFlags) ClassicMode() bool { return false }
|
||||
|
||||
func (n NoopFlags) UTF8StrictMode() bool { return false }
|
||||
|
||||
func (n NoopFlags) EnableAutoGOMEMLIMIT() bool { return false }
|
||||
|
||||
func (n NoopFlags) EnableAutoGOMAXPROCS() bool { return false }
|
||||
|
||||
Generated
Vendored
+32
-32
@@ -15,23 +15,23 @@ package compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
|
||||
"github.com/prometheus/alertmanager/featurecontrol"
|
||||
"github.com/prometheus/alertmanager/matchers/parse"
|
||||
"github.com/prometheus/alertmanager/matcher/parse"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
)
|
||||
|
||||
var (
|
||||
isValidLabelName = isValidClassicLabelName(log.NewNopLogger())
|
||||
parseMatcher = ClassicMatcherParser(log.NewNopLogger())
|
||||
parseMatchers = ClassicMatchersParser(log.NewNopLogger())
|
||||
isValidLabelName = isValidClassicLabelName(promslog.NewNopLogger())
|
||||
parseMatcher = ClassicMatcherParser(promslog.NewNopLogger())
|
||||
parseMatchers = ClassicMatchersParser(promslog.NewNopLogger())
|
||||
)
|
||||
|
||||
// IsValidLabelName returns true if the string is a valid label name.
|
||||
@@ -56,7 +56,7 @@ func Matchers(input, origin string) (labels.Matchers, error) {
|
||||
}
|
||||
|
||||
// InitFromFlags initializes the compat package from the flagger.
|
||||
func InitFromFlags(l log.Logger, f featurecontrol.Flagger) {
|
||||
func InitFromFlags(l *slog.Logger, f featurecontrol.Flagger) {
|
||||
if f.ClassicMode() {
|
||||
isValidLabelName = isValidClassicLabelName(l)
|
||||
parseMatcher = ClassicMatcherParser(l)
|
||||
@@ -74,27 +74,27 @@ func InitFromFlags(l log.Logger, f featurecontrol.Flagger) {
|
||||
|
||||
// ClassicMatcherParser uses the pkg/labels parser to parse the matcher in
|
||||
// the input string.
|
||||
func ClassicMatcherParser(l log.Logger) ParseMatcher {
|
||||
func ClassicMatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
return labels.ParseMatcher(input)
|
||||
}
|
||||
}
|
||||
|
||||
// ClassicMatchersParser uses the pkg/labels parser to parse zero or more
|
||||
// matchers in the input string. It returns an error if the input is invalid.
|
||||
func ClassicMatchersParser(l log.Logger) ParseMatchers {
|
||||
func ClassicMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
return labels.ParseMatchers(input)
|
||||
}
|
||||
}
|
||||
|
||||
// UTF8MatcherParser uses the new matchers/parse parser to parse the matcher
|
||||
// UTF8MatcherParser uses the new matcher/parse parser to parse the matcher
|
||||
// in the input string. If this fails it does not revert to the pkg/labels parser.
|
||||
func UTF8MatcherParser(l log.Logger) ParseMatcher {
|
||||
func UTF8MatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
|
||||
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
|
||||
}
|
||||
@@ -102,22 +102,22 @@ func UTF8MatcherParser(l log.Logger) ParseMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// UTF8MatchersParser uses the new matchers/parse parser to parse zero or more
|
||||
// UTF8MatchersParser uses the new matcher/parse parser to parse zero or more
|
||||
// matchers in the input string. If this fails it does not revert to the
|
||||
// pkg/labels parser.
|
||||
func UTF8MatchersParser(l log.Logger) ParseMatchers {
|
||||
func UTF8MatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
return parse.Matchers(input)
|
||||
}
|
||||
}
|
||||
|
||||
// FallbackMatcherParser uses the new matchers/parse parser to parse zero or more
|
||||
// FallbackMatcherParser uses the new matcher/parse parser to parse zero or more
|
||||
// matchers in the string. If this fails it reverts to the pkg/labels parser and
|
||||
// emits a warning log line.
|
||||
func FallbackMatcherParser(l log.Logger) ParseMatcher {
|
||||
func FallbackMatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
|
||||
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
|
||||
}
|
||||
@@ -130,28 +130,28 @@ func FallbackMatcherParser(l log.Logger) ParseMatcher {
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
// The input is valid in the pkg/labels parser, but not the matchers/parse
|
||||
// The input is valid in the pkg/labels parser, but not the matcher/parse
|
||||
// parser. This means the input is not forwards compatible.
|
||||
suggestion := cMatcher.String()
|
||||
level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
return cMatcher, nil
|
||||
}
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement.
|
||||
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) {
|
||||
level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin)
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatcher, nil
|
||||
}
|
||||
return nMatcher, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FallbackMatchersParser uses the new matchers/parse parser to parse the
|
||||
// FallbackMatchersParser uses the new matcher/parse parser to parse the
|
||||
// matcher in the input string. If this fails it falls back to the pkg/labels
|
||||
// parser and emits a warning log line.
|
||||
func FallbackMatchersParser(l log.Logger) ParseMatchers {
|
||||
func FallbackMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
// Parse the input in both parsers to look for disagreement and incompatible
|
||||
// inputs.
|
||||
nMatchers, nErr := parse.Matchers(input)
|
||||
@@ -161,7 +161,7 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
// The input is valid in the pkg/labels parser, but not the matchers/parse
|
||||
// The input is valid in the pkg/labels parser, but not the matcher/parse
|
||||
// parser. This means the input is not forwards compatible.
|
||||
var sb strings.Builder
|
||||
for i, n := range cMatchers {
|
||||
@@ -172,15 +172,15 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
|
||||
}
|
||||
suggestion := sb.String()
|
||||
// The input is valid in the pkg/labels parser, but not the
|
||||
// new matchers/parse parser.
|
||||
level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
// new matcher/parse parser.
|
||||
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
return cMatchers, nil
|
||||
}
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement. We need to compare to labels.Matchers(cMatchers)
|
||||
// as cMatchers is a []*labels.Matcher not labels.Matchers.
|
||||
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) {
|
||||
level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin)
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatchers, nil
|
||||
}
|
||||
return nMatchers, nil
|
||||
@@ -188,14 +188,14 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
|
||||
}
|
||||
|
||||
// isValidClassicLabelName returns true if the string is a valid classic label name.
|
||||
func isValidClassicLabelName(_ log.Logger) func(model.LabelName) bool {
|
||||
func isValidClassicLabelName(_ *slog.Logger) func(model.LabelName) bool {
|
||||
return func(name model.LabelName) bool {
|
||||
return name.IsValid()
|
||||
}
|
||||
}
|
||||
|
||||
// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name.
|
||||
func isValidUTF8LabelName(_ log.Logger) func(model.LabelName) bool {
|
||||
func isValidUTF8LabelName(_ *slog.Logger) func(model.LabelName) bool {
|
||||
return func(name model.LabelName) bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
Generated
Vendored
+1
-1
@@ -251,7 +251,7 @@ func (l *lexer) accept(valid string) bool {
|
||||
}
|
||||
|
||||
// expect consumes the next rune if its one of the valid runes.
|
||||
// it returns nil if the next rune is valid, otherwise an expectedError
|
||||
// It returns nil if the next rune is valid, otherwise an expectedError
|
||||
// error.
|
||||
func (l *lexer) expect(valid string) error {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
Generated
Vendored
+6
-2
@@ -196,7 +196,7 @@ func (p *parser) parseEndOfMatcher(l *lexer) (parseFunc, error) {
|
||||
if err != nil {
|
||||
if errors.Is(err, errEOF) {
|
||||
// If this is the end of input we still need to check if the optional
|
||||
// open brace has a matching close brace
|
||||
// open brace has a matching close brace.
|
||||
return p.parseCloseBrace, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %w", err, errExpectedCommaOrCloseBrace)
|
||||
@@ -220,7 +220,7 @@ func (p *parser) parseComma(l *lexer) (parseFunc, error) {
|
||||
if err != nil {
|
||||
if errors.Is(err, errEOF) {
|
||||
// If this is the end of input we still need to check if the optional
|
||||
// open brace has a matching close brace
|
||||
// open brace has a matching close brace.
|
||||
return p.parseCloseBrace, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %w", err, errExpectedMatcherOrCloseBrace)
|
||||
@@ -242,6 +242,7 @@ func (p *parser) parseEOF(l *lexer) (parseFunc, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// nolint:godot
|
||||
// accept returns true if the next token is one of the specified kinds,
|
||||
// otherwise false. If the token is accepted it is consumed. tokenEOF is
|
||||
// not an accepted kind and instead accept returns ErrEOF if there is no
|
||||
@@ -256,6 +257,7 @@ func (p *parser) accept(l *lexer, kinds ...tokenKind) (ok bool, err error) {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
// nolint:godot
|
||||
// acceptPeek returns true if the next token is one of the specified kinds,
|
||||
// otherwise false. However, unlike accept, acceptPeek does not consume accepted
|
||||
// tokens. tokenEOF is not an accepted kind and instead accept returns ErrEOF
|
||||
@@ -271,6 +273,7 @@ func (p *parser) acceptPeek(l *lexer, kinds ...tokenKind) (bool, error) {
|
||||
return t.isOneOf(kinds...), nil
|
||||
}
|
||||
|
||||
// nolint:godot
|
||||
// expect returns the next token if it is one of the specified kinds, otherwise
|
||||
// it returns an error. If the token is expected it is consumed. tokenEOF is not
|
||||
// an accepted kind and instead expect returns ErrEOF if there is no more input.
|
||||
@@ -285,6 +288,7 @@ func (p *parser) expect(l *lexer, kind ...tokenKind) (token, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// nolint:godot
|
||||
// expect returns the next token if it is one of the specified kinds, otherwise
|
||||
// it returns an error. However, unlike expect, expectPeek does not consume tokens.
|
||||
// tokenEOF is not an accepted kind and instead expect returns ErrEOF if there is no
|
||||
Generated
Vendored
+1
-1
@@ -205,7 +205,7 @@ func (ms Matchers) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// This is copied from matchers/parse/lexer.go. It will be removed when
|
||||
// This is copied from matcher/parse/lexer.go. It will be removed when
|
||||
// the transition window from classic matchers to UTF-8 matchers is complete,
|
||||
// as then we can use double quotes when printing the label name for all
|
||||
// matchers. Until then, the classic parser does not understand double quotes
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ func ParseMatcher(s string) (_ *Matcher, err error) {
|
||||
return nil, fmt.Errorf("matcher value not valid UTF-8: %s", ms[3])
|
||||
}
|
||||
|
||||
// Unescape the rawValue:
|
||||
// Unescape the rawValue.
|
||||
for i, r := range rawValue {
|
||||
if escaped {
|
||||
escaped = false
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
FROM node
|
||||
FROM node:20-alpine
|
||||
|
||||
ENV NODE_PATH="/usr/local/lib/node_modules"
|
||||
|
||||
RUN npm install juice -g
|
||||
RUN npm install juice@10.0.1 -g
|
||||
|
||||
ENTRYPOINT [""]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
DOCKER_IMG := altermanager-template
|
||||
DOCKER_IMG := alertmanager-template
|
||||
DOCKER_RUN_CURRENT_USER := docker run --user=$(shell id -u $(USER)):$(shell id -g $(USER))
|
||||
DOCKER_CMD := $(DOCKER_RUN_CURRENT_USER) --rm -t -v $(PWD):/app -w /app $(DOCKER_IMG)
|
||||
|
||||
|
||||
+59
@@ -123,6 +123,7 @@ Alerts Resolved:
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "discord.default.content" }}{{ end }}
|
||||
{{ define "discord.default.title" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "discord.default.message" }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}
|
||||
@@ -158,3 +159,61 @@ Alerts Resolved:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "msteamsv2.default.title" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "msteamsv2.default.text" }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}
|
||||
# Alerts Firing:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Alerts.Resolved) 0 }}
|
||||
# Alerts Resolved:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "jira.default.summary" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "jira.default.description" }}
|
||||
{{ if gt (len .Alerts.Firing) 0 }}
|
||||
# Alerts Firing:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
|
||||
{{ end }}
|
||||
{{ if gt (len .Alerts.Resolved) 0 }}
|
||||
# Alerts Resolved:
|
||||
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{- define "jira.default.priority" -}}
|
||||
{{- $priority := "" }}
|
||||
{{- range .Alerts.Firing -}}
|
||||
{{- $severity := index .Labels "severity" -}}
|
||||
{{- if (eq $severity "critical") -}}
|
||||
{{- $priority = "High" -}}
|
||||
{{- else if (and (eq $severity "warning") (ne $priority "High")) -}}
|
||||
{{- $priority = "Medium" -}}
|
||||
{{- else if (and (eq $severity "info") (eq $priority "")) -}}
|
||||
{{- $priority = "Low" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if eq $priority "" -}}
|
||||
{{- range .Alerts.Resolved -}}
|
||||
{{- $severity := index .Labels "severity" -}}
|
||||
{{- if (eq $severity "critical") -}}
|
||||
{{- $priority = "High" -}}
|
||||
{{- else if (and (eq $severity "warning") (ne $priority "High")) -}}
|
||||
{{- $priority = "Medium" -}}
|
||||
{{- else if (and (eq $severity "info") (eq $priority "")) -}}
|
||||
{{- $priority = "Low" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $priority -}}
|
||||
{{- end -}}
|
||||
|
||||
{{ define "rocketchat.default.title" }}{{ template "__subject" . }}{{ end }}
|
||||
{{ define "rocketchat.default.alias" }}{{ template "__alertmanager" . }}{{ end }}
|
||||
{{ define "rocketchat.default.titlelink" }}{{ template "__alertmanagerURL" . }}{{ end }}
|
||||
{{ define "rocketchat.default.emoji" }}{{ end }}
|
||||
{{ define "rocketchat.default.iconurl" }}{{ end }}
|
||||
{{ define "rocketchat.default.text" }}{{ end }}
|
||||
|
||||
+15
@@ -26,6 +26,7 @@ import (
|
||||
tmpltext "text/template"
|
||||
"time"
|
||||
|
||||
commonTemplates "github.com/prometheus/common/helpers/templates"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -192,6 +193,20 @@ var DefaultFuncs = FuncMap{
|
||||
"stringSlice": func(s ...string) []string {
|
||||
return s
|
||||
},
|
||||
// date returns the text representation of the time in the specified format.
|
||||
"date": func(fmt string, t time.Time) string {
|
||||
return t.Format(fmt)
|
||||
},
|
||||
// tz returns the time in the timezone.
|
||||
"tz": func(name string, t time.Time) (time.Time, error) {
|
||||
loc, err := time.LoadLocation(name)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return t.In(loc), nil
|
||||
},
|
||||
"since": time.Since,
|
||||
"humanizeDuration": commonTemplates.HumanizeDuration,
|
||||
}
|
||||
|
||||
// Pair is a key/value string pair.
|
||||
|
||||
+101
-41
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/alertmanager/matchers/compat"
|
||||
"github.com/prometheus/alertmanager/matcher/compat"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
)
|
||||
|
||||
@@ -52,9 +52,17 @@ type AlertStatus struct {
|
||||
silencesVersion int
|
||||
}
|
||||
|
||||
// Marker helps to mark alerts as silenced and/or inhibited.
|
||||
// groupStatus stores the state of the group, and, as applicable, the names
|
||||
// of all active and mute time intervals that are muting it.
|
||||
type groupStatus struct {
|
||||
// mutedBy contains the names of all active and mute time intervals that
|
||||
// are muting it.
|
||||
mutedBy []string
|
||||
}
|
||||
|
||||
// AlertMarker helps to mark alerts as silenced and/or inhibited.
|
||||
// All methods are goroutine-safe.
|
||||
type Marker interface {
|
||||
type AlertMarker interface {
|
||||
// SetActiveOrSilenced replaces the previous SilencedBy by the provided IDs of
|
||||
// active and pending silences, including the version number of the
|
||||
// silences state. The set of provided IDs is supposed to represent the
|
||||
@@ -92,24 +100,74 @@ type Marker interface {
|
||||
Inhibited(model.Fingerprint) ([]string, bool)
|
||||
}
|
||||
|
||||
// NewMarker returns an instance of a Marker implementation.
|
||||
func NewMarker(r prometheus.Registerer) Marker {
|
||||
m := &memMarker{
|
||||
m: map[model.Fingerprint]*AlertStatus{},
|
||||
// GroupMarker helps to mark groups as active or muted.
|
||||
// All methods are goroutine-safe.
|
||||
//
|
||||
// TODO(grobinson): routeID is used in Muted and SetMuted because groupKey
|
||||
// is not unique (see #3817). Once groupKey uniqueness is fixed routeID can
|
||||
// be removed from the GroupMarker interface.
|
||||
type GroupMarker interface {
|
||||
// Muted returns true if the group is muted, otherwise false. If the group
|
||||
// is muted then it also returns the names of the time intervals that muted
|
||||
// it.
|
||||
Muted(routeID, groupKey string) ([]string, bool)
|
||||
|
||||
// SetMuted marks the group as muted, and sets the names of the time
|
||||
// intervals that mute it. If the list of names is nil or the empty slice
|
||||
// then the muted marker is removed.
|
||||
SetMuted(routeID, groupKey string, timeIntervalNames []string)
|
||||
|
||||
// DeleteByGroupKey removes all markers for the GroupKey.
|
||||
DeleteByGroupKey(routeID, groupKey string)
|
||||
}
|
||||
|
||||
// NewMarker returns an instance of a AlertMarker implementation.
|
||||
func NewMarker(r prometheus.Registerer) *MemMarker {
|
||||
m := &MemMarker{
|
||||
alerts: map[model.Fingerprint]*AlertStatus{},
|
||||
groups: map[string]*groupStatus{},
|
||||
}
|
||||
|
||||
m.registerMetrics(r)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type memMarker struct {
|
||||
m map[model.Fingerprint]*AlertStatus
|
||||
type MemMarker struct {
|
||||
alerts map[model.Fingerprint]*AlertStatus
|
||||
groups map[string]*groupStatus
|
||||
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *memMarker) registerMetrics(r prometheus.Registerer) {
|
||||
// Muted implements GroupMarker.
|
||||
func (m *MemMarker) Muted(routeID, groupKey string) ([]string, bool) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
status, ok := m.groups[routeID+groupKey]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return status.mutedBy, len(status.mutedBy) > 0
|
||||
}
|
||||
|
||||
// SetMuted implements GroupMarker.
|
||||
func (m *MemMarker) SetMuted(routeID, groupKey string, timeIntervalNames []string) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
status, ok := m.groups[routeID+groupKey]
|
||||
if !ok {
|
||||
status = &groupStatus{}
|
||||
m.groups[routeID+groupKey] = status
|
||||
}
|
||||
status.mutedBy = timeIntervalNames
|
||||
}
|
||||
|
||||
func (m *MemMarker) DeleteByGroupKey(routeID, groupKey string) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
delete(m.groups, routeID+groupKey)
|
||||
}
|
||||
|
||||
func (m *MemMarker) registerMetrics(r prometheus.Registerer) {
|
||||
newMarkedAlertMetricByState := func(st AlertState) prometheus.GaugeFunc {
|
||||
return prometheus.NewGaugeFunc(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -132,17 +190,17 @@ func (m *memMarker) registerMetrics(r prometheus.Registerer) {
|
||||
r.MustRegister(alertStateUnprocessed)
|
||||
}
|
||||
|
||||
// Count implements Marker.
|
||||
func (m *memMarker) Count(states ...AlertState) int {
|
||||
// Count implements AlertMarker.
|
||||
func (m *MemMarker) Count(states ...AlertState) int {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
if len(states) == 0 {
|
||||
return len(m.m)
|
||||
return len(m.alerts)
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, status := range m.m {
|
||||
for _, status := range m.alerts {
|
||||
for _, state := range states {
|
||||
if status.State == state {
|
||||
count++
|
||||
@@ -152,15 +210,15 @@ func (m *memMarker) Count(states ...AlertState) int {
|
||||
return count
|
||||
}
|
||||
|
||||
// SetActiveOrSilenced implements Marker.
|
||||
func (m *memMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, activeIDs, pendingIDs []string) {
|
||||
// SetActiveOrSilenced implements AlertMarker.
|
||||
func (m *MemMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, activeIDs, pendingIDs []string) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
s, found := m.m[alert]
|
||||
s, found := m.alerts[alert]
|
||||
if !found {
|
||||
s = &AlertStatus{}
|
||||
m.m[alert] = s
|
||||
m.alerts[alert] = s
|
||||
}
|
||||
s.SilencedBy = activeIDs
|
||||
s.pendingSilences = pendingIDs
|
||||
@@ -177,15 +235,15 @@ func (m *memMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, ac
|
||||
s.State = AlertStateSuppressed
|
||||
}
|
||||
|
||||
// SetInhibited implements Marker.
|
||||
func (m *memMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
|
||||
// SetInhibited implements AlertMarker.
|
||||
func (m *MemMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
s, found := m.m[alert]
|
||||
s, found := m.alerts[alert]
|
||||
if !found {
|
||||
s = &AlertStatus{}
|
||||
m.m[alert] = s
|
||||
m.alerts[alert] = s
|
||||
}
|
||||
s.InhibitedBy = ids
|
||||
|
||||
@@ -200,12 +258,12 @@ func (m *memMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
|
||||
s.State = AlertStateSuppressed
|
||||
}
|
||||
|
||||
// Status implements Marker.
|
||||
func (m *memMarker) Status(alert model.Fingerprint) AlertStatus {
|
||||
// Status implements AlertMarker.
|
||||
func (m *MemMarker) Status(alert model.Fingerprint) AlertStatus {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
if s, found := m.m[alert]; found {
|
||||
if s, found := m.alerts[alert]; found {
|
||||
return *s
|
||||
}
|
||||
return AlertStatus{
|
||||
@@ -215,26 +273,26 @@ func (m *memMarker) Status(alert model.Fingerprint) AlertStatus {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete implements Marker.
|
||||
func (m *memMarker) Delete(alert model.Fingerprint) {
|
||||
// Delete implements AlertMarker.
|
||||
func (m *MemMarker) Delete(alert model.Fingerprint) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
delete(m.m, alert)
|
||||
delete(m.alerts, alert)
|
||||
}
|
||||
|
||||
// Unprocessed implements Marker.
|
||||
func (m *memMarker) Unprocessed(alert model.Fingerprint) bool {
|
||||
// Unprocessed implements AlertMarker.
|
||||
func (m *MemMarker) Unprocessed(alert model.Fingerprint) bool {
|
||||
return m.Status(alert).State == AlertStateUnprocessed
|
||||
}
|
||||
|
||||
// Active implements Marker.
|
||||
func (m *memMarker) Active(alert model.Fingerprint) bool {
|
||||
// Active implements AlertMarker.
|
||||
func (m *MemMarker) Active(alert model.Fingerprint) bool {
|
||||
return m.Status(alert).State == AlertStateActive
|
||||
}
|
||||
|
||||
// Inhibited implements Marker.
|
||||
func (m *memMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
|
||||
// Inhibited implements AlertMarker.
|
||||
func (m *MemMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
|
||||
s := m.Status(alert)
|
||||
return s.InhibitedBy,
|
||||
s.State == AlertStateSuppressed && len(s.InhibitedBy) > 0
|
||||
@@ -243,7 +301,7 @@ func (m *memMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
|
||||
// Silenced returns whether the alert for the given Fingerprint is in the
|
||||
// Silenced state, any associated silence IDs, and the silences state version
|
||||
// the result is based on.
|
||||
func (m *memMarker) Silenced(alert model.Fingerprint) (activeIDs, pendingIDs []string, version int, silenced bool) {
|
||||
func (m *MemMarker) Silenced(alert model.Fingerprint) (activeIDs, pendingIDs []string, version int, silenced bool) {
|
||||
s := m.Status(alert)
|
||||
return s.SilencedBy, s.pendingSilences, s.silencesVersion,
|
||||
s.State == AlertStateSuppressed && len(s.SilencedBy) > 0
|
||||
@@ -410,15 +468,17 @@ func (a *Alert) Merge(o *Alert) *Alert {
|
||||
}
|
||||
|
||||
// A Muter determines whether a given label set is muted. Implementers that
|
||||
// maintain an underlying Marker are expected to update it during a call of
|
||||
// maintain an underlying AlertMarker are expected to update it during a call of
|
||||
// Mutes.
|
||||
type Muter interface {
|
||||
Mutes(model.LabelSet) bool
|
||||
}
|
||||
|
||||
// TimeMuter determines if alerts should be muted based on the specified current time and active time interval on the route.
|
||||
// A TimeMuter determines if the time is muted by one or more active or mute
|
||||
// time intervals. If the time is muted, it returns true and the names of the
|
||||
// time intervals that muted it. Otherwise, it returns false and a nil slice.
|
||||
type TimeMuter interface {
|
||||
Mutes(timeIntervalName []string, now time.Time) (bool, error)
|
||||
Mutes(timeIntervalNames []string, now time.Time) (bool, []string, error)
|
||||
}
|
||||
|
||||
// A MuteFunc is a function that implements the Muter interface.
|
||||
@@ -458,7 +518,7 @@ type Silence struct {
|
||||
}
|
||||
|
||||
// Expired return if the silence is expired
|
||||
// meaning that both StartsAt and EndsAt are equal
|
||||
// meaning that both StartsAt and EndsAt are equal.
|
||||
func (s *Silence) Expired() bool {
|
||||
return s.StartsAt.Equal(s.EndsAt)
|
||||
}
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
var errNaNOrInf = errors.New("value is NaN or Inf")
|
||||
|
||||
func ConvertToFloat(i interface{}) (float64, error) {
|
||||
switch v := i.(type) {
|
||||
case float64:
|
||||
return v, nil
|
||||
case string:
|
||||
return strconv.ParseFloat(v, 64)
|
||||
case int:
|
||||
return float64(v), nil
|
||||
case uint:
|
||||
return float64(v), nil
|
||||
case int64:
|
||||
return float64(v), nil
|
||||
case uint64:
|
||||
return float64(v), nil
|
||||
case time.Duration:
|
||||
return v.Seconds(), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("can't convert %T to float", v)
|
||||
}
|
||||
}
|
||||
|
||||
func FloatToTime(v float64) (*time.Time, error) {
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
return nil, errNaNOrInf
|
||||
}
|
||||
timestamp := v * 1e9
|
||||
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 {
|
||||
return nil, fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v)
|
||||
}
|
||||
t := model.TimeFromUnixNano(int64(timestamp)).Time().UTC()
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func HumanizeDuration(i interface{}) (string, error) {
|
||||
v, err := ConvertToFloat(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
}
|
||||
if v == 0 {
|
||||
return fmt.Sprintf("%.4gs", v), nil
|
||||
}
|
||||
if math.Abs(v) >= 1 {
|
||||
sign := ""
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = -v
|
||||
}
|
||||
duration := int64(v)
|
||||
seconds := duration % 60
|
||||
minutes := (duration / 60) % 60
|
||||
hours := (duration / 60 / 60) % 24
|
||||
days := duration / 60 / 60 / 24
|
||||
// For days to minutes, we display seconds as an integer.
|
||||
if days != 0 {
|
||||
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
|
||||
}
|
||||
if hours != 0 {
|
||||
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
|
||||
}
|
||||
if minutes != 0 {
|
||||
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
|
||||
}
|
||||
// For seconds, we display 4 significant digits.
|
||||
return fmt.Sprintf("%s%.4gs", sign, v), nil
|
||||
}
|
||||
prefix := ""
|
||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||
if math.Abs(v) >= 1 {
|
||||
break
|
||||
}
|
||||
prefix = p
|
||||
v *= 1000
|
||||
}
|
||||
return fmt.Sprintf("%.4g%ss", v, prefix), nil
|
||||
}
|
||||
|
||||
func HumanizeTimestamp(i interface{}) (string, error) {
|
||||
v, err := ConvertToFloat(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tm, err := FloatToTime(v)
|
||||
switch {
|
||||
case errors.Is(err, errNaNOrInf):
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
case err != nil:
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprint(tm), nil
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// Copyright 2024 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package promslog defines standardised ways to initialize the Go standard
|
||||
// library's log/slog logger.
|
||||
// It should typically only ever be imported by main packages.
|
||||
|
||||
package promslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogStyle string
|
||||
|
||||
const (
|
||||
SlogStyle LogStyle = "slog"
|
||||
GoKitStyle LogStyle = "go-kit"
|
||||
)
|
||||
|
||||
var (
|
||||
LevelFlagOptions = []string{"debug", "info", "warn", "error"}
|
||||
FormatFlagOptions = []string{"logfmt", "json"}
|
||||
|
||||
callerAddFunc = false
|
||||
defaultWriter = os.Stderr
|
||||
goKitStyleReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
|
||||
key := a.Key
|
||||
switch key {
|
||||
case slog.TimeKey:
|
||||
a.Key = "ts"
|
||||
|
||||
// This timestamp format differs from RFC3339Nano by using .000 instead
|
||||
// of .999999999 which changes the timestamp from 9 variable to 3 fixed
|
||||
// decimals (.130 instead of .130987456).
|
||||
t := a.Value.Time()
|
||||
a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
|
||||
case slog.SourceKey:
|
||||
a.Key = "caller"
|
||||
src, _ := a.Value.Any().(*slog.Source)
|
||||
|
||||
switch callerAddFunc {
|
||||
case true:
|
||||
a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line))
|
||||
default:
|
||||
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
||||
}
|
||||
case slog.LevelKey:
|
||||
a.Value = slog.StringValue(strings.ToLower(a.Value.String()))
|
||||
default:
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
defaultReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
|
||||
key := a.Key
|
||||
switch key {
|
||||
case slog.TimeKey:
|
||||
t := a.Value.Time()
|
||||
a.Value = slog.TimeValue(t.UTC())
|
||||
case slog.SourceKey:
|
||||
src, _ := a.Value.Any().(*slog.Source)
|
||||
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
||||
default:
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
)
|
||||
|
||||
// AllowedLevel is a settable identifier for the minimum level a log entry
|
||||
// must be have.
|
||||
type AllowedLevel struct {
|
||||
s string
|
||||
lvl *slog.LevelVar
|
||||
}
|
||||
|
||||
func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var s string
|
||||
type plain string
|
||||
if err := unmarshal((*plain)(&s)); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
lo := &AllowedLevel{}
|
||||
if err := lo.Set(s); err != nil {
|
||||
return err
|
||||
}
|
||||
*l = *lo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *AllowedLevel) String() string {
|
||||
return l.s
|
||||
}
|
||||
|
||||
// Set updates the value of the allowed level.
|
||||
func (l *AllowedLevel) Set(s string) error {
|
||||
if l.lvl == nil {
|
||||
l.lvl = &slog.LevelVar{}
|
||||
}
|
||||
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
l.lvl.Set(slog.LevelDebug)
|
||||
callerAddFunc = true
|
||||
case "info":
|
||||
l.lvl.Set(slog.LevelInfo)
|
||||
callerAddFunc = false
|
||||
case "warn":
|
||||
l.lvl.Set(slog.LevelWarn)
|
||||
callerAddFunc = false
|
||||
case "error":
|
||||
l.lvl.Set(slog.LevelError)
|
||||
callerAddFunc = false
|
||||
default:
|
||||
return fmt.Errorf("unrecognized log level %s", s)
|
||||
}
|
||||
l.s = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllowedFormat is a settable identifier for the output format that the logger can have.
|
||||
type AllowedFormat struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (f *AllowedFormat) String() string {
|
||||
return f.s
|
||||
}
|
||||
|
||||
// Set updates the value of the allowed format.
|
||||
func (f *AllowedFormat) Set(s string) error {
|
||||
switch s {
|
||||
case "logfmt", "json":
|
||||
f.s = s
|
||||
default:
|
||||
return fmt.Errorf("unrecognized log format %s", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is a struct containing configurable settings for the logger
|
||||
type Config struct {
|
||||
Level *AllowedLevel
|
||||
Format *AllowedFormat
|
||||
Style LogStyle
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// New returns a new slog.Logger. Each logged line will be annotated
|
||||
// with a timestamp. The output always goes to stderr.
|
||||
func New(config *Config) *slog.Logger {
|
||||
if config.Level == nil {
|
||||
config.Level = &AllowedLevel{}
|
||||
_ = config.Level.Set("info")
|
||||
}
|
||||
|
||||
if config.Writer == nil {
|
||||
config.Writer = defaultWriter
|
||||
}
|
||||
|
||||
logHandlerOpts := &slog.HandlerOptions{
|
||||
Level: config.Level.lvl,
|
||||
AddSource: true,
|
||||
ReplaceAttr: defaultReplaceAttrFunc,
|
||||
}
|
||||
|
||||
if config.Style == GoKitStyle {
|
||||
logHandlerOpts.ReplaceAttr = goKitStyleReplaceAttrFunc
|
||||
}
|
||||
|
||||
if config.Format != nil && config.Format.s == "json" {
|
||||
return slog.New(slog.NewJSONHandler(config.Writer, logHandlerOpts))
|
||||
}
|
||||
return slog.New(slog.NewTextHandler(config.Writer, logHandlerOpts))
|
||||
}
|
||||
|
||||
// NewNopLogger is a convenience function to return an slog.Logger that writes
|
||||
// to io.Discard.
|
||||
func NewNopLogger() *slog.Logger {
|
||||
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
Reference in New Issue
Block a user