Repository: prometheus/common
Pull request #726 (2024-11-20 17:48:35)

Synchronize common files from prometheus/prometheus
Propagating changes from prometheus/prometheus default branch.


Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 3051469..e645ba3 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -36,4 +36,4 @@ jobs:
         uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
         with:
           args: --verbose
-          version: v1.61.0
+          version: v1.62.0
diff --git a/Makefile.common b/Makefile.common
index 09e5bff..fc47bdb 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -61,7 +61,7 @@ PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_
 SKIP_GOLANGCI_LINT :=
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.61.0
+GOLANGCI_LINT_VERSION ?= v1.62.0
 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
 # windows isn't included here because of the path separator being different.
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
diff --git a/assets/go.mod b/assets/go.mod
index ec1105b..6e1afe3 100644
--- a/assets/go.mod
+++ b/assets/go.mod
@@ -2,7 +2,7 @@ module github.com/prometheus/common/assets
 
 go 1.21
 
-require github.com/stretchr/testify v1.9.0
+require github.com/stretchr/testify v1.10.0
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/assets/go.sum b/assets/go.sum
index e20fa14..713a0b4 100644
--- a/assets/go.sum
+++ b/assets/go.sum
@@ -2,8 +2,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/go.mod b/go.mod
index 6c87d62..3e89b1f 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/prometheus/client_model v0.6.1
 	github.com/stretchr/testify v1.9.0
 	golang.org/x/net v0.30.0
-	golang.org/x/oauth2 v0.23.0
+	golang.org/x/oauth2 v0.24.0
 	google.golang.org/protobuf v1.35.1
 	gopkg.in/yaml.v2 v2.4.0
 )
diff --git a/go.sum b/go.sum
index 395902c..235e85a 100644
--- a/go.sum
+++ b/go.sum
@@ -49,8 +49,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8
 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
 golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
-golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
-golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
 golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
diff --git a/model/metric.go b/model/metric.go
index 12593c7..0daca83 100644
--- a/model/metric.go
+++ b/model/metric.go
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"regexp"
 	"sort"
+	"strconv"
 	"strings"
 	"unicode/utf8"
 
@@ -270,10 +271,6 @@ func metricNeedsEscaping(m *dto.Metric) bool {
 	return false
 }
 
-const (
-	lowerhex = "0123456789abcdef"
-)
-
 // EscapeName escapes the incoming name according to the provided escaping
 // scheme. Depending on the rules of escaping, this may cause no change in the
 // string that is returned. (Especially NoEscaping, which by definition is a
@@ -308,7 +305,7 @@ func EscapeName(name string, scheme EscapingScheme) string {
 			} else if isValidLegacyRune(b, i) {
 				escaped.WriteRune(b)
 			} else {
-				escaped.WriteRune('_')
+				escaped.WriteString("__")
 			}
 		}
 		return escaped.String()
@@ -318,21 +315,15 @@ func EscapeName(name string, scheme EscapingScheme) string {
 		}
 		escaped.WriteString("U__")
 		for i, b := range name {
-			if isValidLegacyRune(b, i) {
+			if b == '_' {
+				escaped.WriteString("__")
+			} else if isValidLegacyRune(b, i) {
 				escaped.WriteRune(b)
 			} else if !utf8.ValidRune(b) {
 				escaped.WriteString("_FFFD_")
-			} else if b < 0x100 {
-				escaped.WriteRune('_')
-				for s := 4; s >= 0; s -= 4 {
-					escaped.WriteByte(lowerhex[b>>uint(s)&0xF])
-				}
-				escaped.WriteRune('_')
-			} else if b < 0x10000 {
+			} else {
 				escaped.WriteRune('_')
-				for s := 12; s >= 0; s -= 4 {
-					escaped.WriteByte(lowerhex[b>>uint(s)&0xF])
-				}
+				escaped.WriteString(strconv.FormatInt(int64(b), 16))
 				escaped.WriteRune('_')
 			}
 		}
@@ -390,8 +381,9 @@ func UnescapeName(name string, scheme EscapingScheme) string {
 			// We think we are in a UTF-8 code, process it.
 			var utf8Val uint
 			for j := 0; i < len(escapedName); j++ {
-				// This is too many characters for a utf8 value.
-				if j > 4 {
+				// This is too many characters for a utf8 value based on the MaxRune
+				// value of '\U0010FFFF'.
+				if j >= 6 {
 					return name
 				}
 				// Found a closing underscore, convert to a rune, check validity, and append.
diff --git a/promslog/slog.go b/promslog/slog.go
index 1677605..6e8fbab 100644
--- a/promslog/slog.go
+++ b/promslog/slog.go
@@ -68,13 +68,16 @@ var (
 
 		return a
 	}
-	truncateSourceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
-		if a.Key != slog.SourceKey {
-			return a
-		}
-
-		if src, ok := a.Value.Any().(*slog.Source); ok {
+	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
@@ -115,7 +118,7 @@ func (l *AllowedLevel) Set(s string) error {
 		l.lvl = &slog.LevelVar{}
 	}
 
-	switch s {
+	switch strings.ToLower(s) {
 	case "debug":
 		l.lvl.Set(slog.LevelDebug)
 		callerAddFunc = true
@@ -178,7 +181,7 @@ func New(config *Config) *slog.Logger {
 	logHandlerOpts := &slog.HandlerOptions{
 		Level:       config.Level.lvl,
 		AddSource:   true,
-		ReplaceAttr: truncateSourceAttrFunc,
+		ReplaceAttr: defaultReplaceAttrFunc,
 	}
 
 	if config.Style == GoKitStyle {
diff --git a/sigv4/go.mod b/sigv4/go.mod
index a4d6d27..531dcfe 100644
--- a/sigv4/go.mod
+++ b/sigv4/go.mod
@@ -25,7 +25,7 @@ require (
 	github.com/prometheus/client_model v0.6.1 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	golang.org/x/net v0.30.0 // indirect
-	golang.org/x/oauth2 v0.23.0 // indirect
+	golang.org/x/oauth2 v0.24.0 // indirect
 	golang.org/x/sys v0.26.0 // indirect
 	golang.org/x/text v0.19.0 // indirect
 	google.golang.org/protobuf v1.35.1 // indirect
diff --git a/sigv4/go.sum b/sigv4/go.sum
index 32025ad..79c16da 100644
--- a/sigv4/go.sum
+++ b/sigv4/go.sum
@@ -41,8 +41,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
 golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
-golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
-golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
 golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=


Test patch:
diff --git a/model/metric_test.go b/model/metric_test.go
index ef08a05..6152c54 100644
--- a/model/metric_test.go
+++ b/model/metric_test.go
@@ -261,6 +261,14 @@ func TestEscapeName(t *testing.T) {
 			expectedUnescapedDots: "mysystem.prod.west.cpu.load",
 			expectedValue:         "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load",
 		},
+		{
+			name:                  "name with dots and underscore",
+			input:                 "mysystem.prod.west.cpu.load_total",
+			expectedUnderscores:   "mysystem_prod_west_cpu_load_total",
+			expectedDots:          "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total",
+			expectedUnescapedDots: "mysystem.prod.west.cpu.load_total",
+			expectedValue:         "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total",
+		},
 		{
 			name:                  "name with dots and colon",
 			input:                 "http.status:sum",
@@ -269,16 +277,32 @@ func TestEscapeName(t *testing.T) {
 			expectedUnescapedDots: "http.status:sum",
 			expectedValue:         "U__http_2e_status:sum",
 		},
+		{
+			name:                  "name with spaces and emoji",
+			input:                 "label with 😱",
+			expectedUnderscores:   "label_with__",
+			expectedDots:          "label__with____",
+			expectedUnescapedDots: "label_with__",
+			expectedValue:         "U__label_20_with_20__1f631_",
+		},
 		{
 			name:                "name with unicode characters > 0x100",
 			input:               "花火",
 			expectedUnderscores: "__",
-			expectedDots:        "__",
+			expectedDots:        "____",
 			// Dots-replacement does not know the difference between two replaced
 			// characters and a single underscore.
-			expectedUnescapedDots: "_",
+			expectedUnescapedDots: "__",
 			expectedValue:         "U___82b1__706b_",
 		},
+		{
+			name:                  "name with spaces and edge-case value",
+			input:                 "label with \u0100",
+			expectedUnderscores:   "label_with__",
+			expectedDots:          "label__with____",
+			expectedUnescapedDots: "label_with__",
+			expectedValue:         "U__label_20_with_20__100_",
+		},
 	}
 
 	for _, scenario := range scenarios {
@@ -564,7 +588,7 @@ func TestEscapeMetricFamily(t *testing.T) {
 				},
 			},
 			expected: &dto.MetricFamily{
-				Name: proto.String("unicode_dot_and_dot_dots_dot___"),
+				Name: proto.String("unicode_dot_and_dot_dots_dot_____"),
 				Help: proto.String("some help text"),
 				Type: dto.MetricType_GAUGE.Enum(),
 				Metric: []*dto.Metric{
@@ -575,7 +599,7 @@ func TestEscapeMetricFamily(t *testing.T) {
 						Label: []*dto.LabelPair{
 							{
 								Name:  proto.String("__name__"),
-								Value: proto.String("unicode_dot_and_dot_dots_dot___"),
+								Value: proto.String("unicode_dot_and_dot_dots_dot_____"),
 							},
 							{
 								Name:  proto.String("some_label"),