Repository: prometheus/common
Pull request #756 (2025-02-01 17:33:00)

build(deps): bump google.golang.org/protobuf from 1.36.3 to 1.36.4
Bumps google.golang.org/protobuf from 1.36.3 to 1.36.4.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/protobuf&package-manager=go_modules&previous-version=1.36.3&new-version=1.36.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 0c00c41..def9007 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -26,14 +26,14 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - name: Install Go
-        uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
+        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
         with:
           go-version: 1.23.x
       - name: Install snmp_exporter/generator dependencies
         run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
         if: github.repository == 'prometheus/snmp_exporter'
       - name: Lint
-        uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
+        uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0
         with:
           args: --verbose
           version: v1.63.4
diff --git a/go.mod b/go.mod
index 5d2f2cb..adf2297 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/stretchr/testify v1.10.0
 	golang.org/x/net v0.34.0
 	golang.org/x/oauth2 v0.25.0
-	google.golang.org/protobuf v1.36.3
+	google.golang.org/protobuf v1.36.4
 	gopkg.in/yaml.v2 v2.4.0
 )
 
diff --git a/go.sum b/go.sum
index 1750431..8766963 100644
--- a/go.sum
+++ b/go.sum
@@ -51,8 +51,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
-google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
+google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/model/labels.go b/model/labels.go
index 73b7aa3..f4a3876 100644
--- a/model/labels.go
+++ b/model/labels.go
@@ -22,7 +22,7 @@ import (
 )
 
 const (
-	// AlertNameLabel is the name of the label containing the an alert's name.
+	// AlertNameLabel is the name of the label containing the alert's name.
 	AlertNameLabel = "alertname"
 
 	// ExportedLabelPrefix is the prefix to prepend to the label names present in
diff --git a/promslog/flag/flag.go b/promslog/flag/flag.go
index 0a164fc..85c67b2 100644
--- a/promslog/flag/flag.go
+++ b/promslog/flag/flag.go
@@ -42,12 +42,12 @@ var FormatFlagHelp = "Output format of log messages. One of: [" + strings.Join(p
 // AddFlags adds the flags used by this package to the Kingpin application.
 // To use the default Kingpin application, call AddFlags(kingpin.CommandLine)
 func AddFlags(a *kingpin.Application, config *promslog.Config) {
-	config.Level = &promslog.AllowedLevel{}
+	config.Level = promslog.NewLevel()
 	a.Flag(LevelFlagName, LevelFlagHelp).
 		Default("info").HintOptions(promslog.LevelFlagOptions...).
 		SetValue(config.Level)
 
-	config.Format = &promslog.AllowedFormat{}
+	config.Format = promslog.NewFormat()
 	a.Flag(FormatFlagName, FormatFlagHelp).
 		Default("logfmt").HintOptions(promslog.FormatFlagOptions...).
 		SetValue(config.Format)
diff --git a/promslog/slog.go b/promslog/slog.go
index 11c33fb..f9f8996 100644
--- a/promslog/slog.go
+++ b/promslog/slog.go
@@ -28,6 +28,7 @@ import (
 	"time"
 )
 
+// LogStyle represents the common logging formats in the Prometheus ecosystem.
 type LogStyle string
 
 const (
@@ -38,115 +39,29 @@ const (
 )
 
 var (
-	LevelFlagOptions  = []string{"debug", "info", "warn", "error"}
+	// LevelFlagOptions represents allowed logging levels.
+	LevelFlagOptions = []string{"debug", "info", "warn", "error"}
+	// FormatFlagOptions represents allowed formats.
 	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, "ts":
-			if t, ok := a.Value.Any().(time.Time); ok {
-				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).
-				a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
-			} else {
-				// If we can't cast the any from the value to a
-				// time.Time, it means the caller logged
-				// another attribute with a key of `ts`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to `logged_ts`.
-				a.Key = reservedKeyPrefix + key
-			}
-		case slog.SourceKey, "caller":
-			if src, ok := a.Value.Any().(*slog.Source); ok {
-				a.Key = "caller"
-				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))
-				}
-			} else {
-				// If we can't cast the any from the value to
-				// an *slog.Source, it means the caller logged
-				// another attribute with a key of `caller`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to
-				// `logged_caller`.
-				a.Key = reservedKeyPrefix + key
-			}
-		case slog.LevelKey:
-			if lvl, ok := a.Value.Any().(slog.Level); ok {
-				a.Value = slog.StringValue(strings.ToLower(lvl.String()))
-			} else {
-				// If we can't cast the any from the value to
-				// an slog.Level, it means the caller logged
-				// another attribute with a key of `level`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to `logged_level`.
-				a.Key = reservedKeyPrefix + key
-			}
-		default:
-		}
-
-		return a
-	}
-	defaultReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
-		key := a.Key
-		switch key {
-		case slog.TimeKey:
-			if t, ok := a.Value.Any().(time.Time); ok {
-				a.Value = slog.TimeValue(t.UTC())
-			} else {
-				// If we can't cast the any from the value to a
-				// time.Time, it means the caller logged
-				// another attribute with a key of `time`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to `logged_time`.
-				a.Key = reservedKeyPrefix + key
-			}
-		case slog.SourceKey:
-			if src, ok := a.Value.Any().(*slog.Source); ok {
-				a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
-			} else {
-				// If we can't cast the any from the value to
-				// an *slog.Source, it means the caller logged
-				// another attribute with a key of `source`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to
-				// `logged_source`.
-				a.Key = reservedKeyPrefix + key
-			}
-		case slog.LevelKey:
-			if _, ok := a.Value.Any().(slog.Level); !ok {
-				// If we can't cast the any from the value to
-				// an slog.Level, it means the caller logged
-				// another attribute with a key of `level`.
-				// Prevent duplicate keys (necessary for proper
-				// JSON) by renaming the key to
-				// `logged_level`.
-				a.Key = reservedKeyPrefix + key
-			}
-		default:
-		}
-
-		return a
-	}
+	defaultWriter = os.Stderr
 )
 
-// AllowedLevel is a settable identifier for the minimum level a log entry
-// must be have.
-type AllowedLevel struct {
-	s   string
+// Level controls a logging level, with an info default.
+// It wraps slog.LevelVar with string-based level control.
+// Level is safe to be used concurrently.
+type Level struct {
 	lvl *slog.LevelVar
 }
 
-func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
+// NewLevel returns a new Level.
+func NewLevel() *Level {
+	return &Level{
+		lvl: &slog.LevelVar{},
+	}
+}
+
+func (l *Level) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var s string
 	type plain string
 	if err := unmarshal((*plain)(&s)); err != nil {
@@ -155,55 +70,60 @@ func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if s == "" {
 		return nil
 	}
-	lo := &AllowedLevel{}
-	if err := lo.Set(s); err != nil {
+	if err := l.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{}
+// String returns the current level.
+func (l *Level) String() string {
+	switch l.lvl.Level() {
+	case slog.LevelDebug:
+		return "debug"
+	case slog.LevelInfo:
+		return "info"
+	case slog.LevelWarn:
+		return "warn"
+	case slog.LevelError:
+		return "error"
+	default:
+		return ""
 	}
+}
 
+// Set updates the logging level with the validation.
+func (l *Level) Set(s string) error {
 	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 {
+// Format controls a logging output format.
+// Not concurrency-safe.
+type Format struct {
 	s string
 }
 
-func (f *AllowedFormat) String() string {
+// NewFormat creates a new Format.
+func NewFormat() *Format { return &Format{} }
+
+func (f *Format) String() string {
 	return f.s
 }
 
 // Set updates the value of the allowed format.
-func (f *AllowedFormat) Set(s string) error {
+func (f *Format) Set(s string) error {
 	switch s {
 	case "logfmt", "json":
 		f.s = s
@@ -215,18 +135,113 @@ func (f *AllowedFormat) Set(s string) error {
 
 // Config is a struct containing configurable settings for the logger
 type Config struct {
-	Level  *AllowedLevel
-	Format *AllowedFormat
+	Level  *Level
+	Format *Format
 	Style  LogStyle
 	Writer io.Writer
 }
 
+func newGoKitStyleReplaceAttrFunc(lvl *Level) func(groups []string, a slog.Attr) slog.Attr {
+	return func(groups []string, a slog.Attr) slog.Attr {
+		key := a.Key
+		switch key {
+		case slog.TimeKey, "ts":
+			if t, ok := a.Value.Any().(time.Time); ok {
+				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).
+				a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
+			} else {
+				// If we can't cast the any from the value to a
+				// time.Time, it means the caller logged
+				// another attribute with a key of `ts`.
+				// Prevent duplicate keys (necessary for proper
+				// JSON) by renaming the key to `logged_ts`.
+				a.Key = reservedKeyPrefix + key
+			}
+		case slog.SourceKey, "caller":
+			if src, ok := a.Value.Any().(*slog.Source); ok {
+				a.Key = "caller"
+				switch lvl.String() {
+				case "debug":
+					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))
+				}
+			} else {
+				// If we can't cast the any from the value to
+				// an *slog.Source, it means the caller logged
+				// another attribute with a key of `caller`.
+				// Prevent duplicate keys (necessary for proper
+				// JSON) by renaming the key to
+				// `logged_caller`.
+				a.Key = reservedKeyPrefix + key
+			}
+		case slog.LevelKey:
+			if lvl, ok := a.Value.Any().(slog.Level); ok {
+				a.Value = slog.StringValue(strings.ToLower(lvl.String()))
+			} else {
+				// If we can't cast the any from the value to
+				// an slog.Level, it means the caller logged
+				// another attribute with a key of `level`.
+				// Prevent duplicate keys (necessary for proper
+				// JSON) by renaming the key to `logged_level`.
+				a.Key = reservedKeyPrefix + key
+			}
+		default:
+		}
+		return a
+	}
+}
+
+func defaultReplaceAttr(_ []string, a slog.Attr) slog.Attr {
+	key := a.Key
+	switch key {
+	case slog.TimeKey:
+		if t, ok := a.Value.Any().(time.Time); ok {
+			a.Value = slog.TimeValue(t.UTC())
+		} else {
+			// If we can't cast the any from the value to a
+			// time.Time, it means the caller logged
+			// another attribute with a key of `time`.
+			// Prevent duplicate keys (necessary for proper
+			// JSON) by renaming the key to `logged_time`.
+			a.Key = reservedKeyPrefix + key
+		}
+	case slog.SourceKey:
+		if src, ok := a.Value.Any().(*slog.Source); ok {
+			a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
+		} else {
+			// If we can't cast the any from the value to
+			// an *slog.Source, it means the caller logged
+			// another attribute with a key of `source`.
+			// Prevent duplicate keys (necessary for proper
+			// JSON) by renaming the key to
+			// `logged_source`.
+			a.Key = reservedKeyPrefix + key
+		}
+	case slog.LevelKey:
+		if _, ok := a.Value.Any().(slog.Level); !ok {
+			// If we can't cast the any from the value to
+			// an slog.Level, it means the caller logged
+			// another attribute with a key of `level`.
+			// Prevent duplicate keys (necessary for proper
+			// JSON) by renaming the key to
+			// `logged_level`.
+			a.Key = reservedKeyPrefix + key
+		}
+	default:
+	}
+	return a
+}
+
 // 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")
+		config.Level = NewLevel()
 	}
 
 	if config.Writer == nil {
@@ -236,11 +251,11 @@ func New(config *Config) *slog.Logger {
 	logHandlerOpts := &slog.HandlerOptions{
 		Level:       config.Level.lvl,
 		AddSource:   true,
-		ReplaceAttr: defaultReplaceAttrFunc,
+		ReplaceAttr: defaultReplaceAttr,
 	}
 
 	if config.Style == GoKitStyle {
-		logHandlerOpts.ReplaceAttr = goKitStyleReplaceAttrFunc
+		logHandlerOpts.ReplaceAttr = newGoKitStyleReplaceAttrFunc(config.Level)
 	}
 
 	if config.Format != nil && config.Format.s == "json" {


Test patch:
diff --git a/promslog/slog_test.go b/promslog/slog_test.go
index 6da14d9..ea4e176 100644
--- a/promslog/slog_test.go
+++ b/promslog/slog_test.go
@@ -43,29 +43,29 @@ func TestDefaultConfig(t *testing.T) {
 }
 
 func TestUnmarshallLevel(t *testing.T) {
-	l := &AllowedLevel{}
+	l := NewLevel()
 	err := yaml.Unmarshal([]byte(`debug`), l)
 	if err != nil {
 		t.Error(err)
 	}
-	if l.s != "debug" {
-		t.Errorf("expected %s, got %s", "debug", l.s)
+	if got := l.String(); got != "debug" {
+		t.Errorf("expected %s, got %s", "debug", got)
 	}
 }
 
 func TestUnmarshallEmptyLevel(t *testing.T) {
-	l := &AllowedLevel{}
+	l := NewLevel()
 	err := yaml.Unmarshal([]byte(``), l)
 	if err != nil {
 		t.Error(err)
 	}
-	if l.s != "" {
-		t.Errorf("expected empty level, got %s", l.s)
+	if got := l.String(); got != "info" {
+		t.Errorf("expected info (default) level, got %s", got)
 	}
 }
 
 func TestUnmarshallBadLevel(t *testing.T) {
-	l := &AllowedLevel{}
+	l := NewLevel()
 	err := yaml.Unmarshal([]byte(`debugg`), l)
 	if err == nil {
 		t.Error("expected error")
@@ -74,8 +74,8 @@ func TestUnmarshallBadLevel(t *testing.T) {
 	if err.Error() != expErr {
 		t.Errorf("expected error %s, got %s", expErr, err.Error())
 	}
-	if l.s != "" {
-		t.Errorf("expected empty level, got %s", l.s)
+	if got := l.String(); got != "info" {
+		t.Errorf("expected info (default) level, got %s", got)
 	}
 }