Repository: prometheus/common
Pull request #549 (2023-12-08 23:00:30)

http_config: Add host
Follow-up: https://github.com/prometheus/prometheus/pull/13157

Host allows to override the hostname from URL. This can be useful if DNS is not stable but the endpoint required an valid host.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 21da9a4..66bc968 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -95,14 +95,14 @@ workflows:
           matrix:
             parameters:
               go_version:
-                - "1.20"
                 - "1.21"
+                - "1.22"
       - test-assets:
           name: assets-go-<< matrix.go_version >>
           matrix:
             parameters:
               go_version:
-                - "1.21"
+                - "1.22"
       - style:
           name: style
-          go_version: "1.21"
+          go_version: "1.22"
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index ffa6b30..0e85157 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -12,21 +12,27 @@ on:
       - ".golangci.yml"
   pull_request:
 
+permissions:  # added using https://github.com/step-security/secure-repo
+  contents: read
+
 jobs:
   golangci:
+    permissions:
+      contents: read  # for actions/checkout to fetch code
+      pull-requests: read  # for golangci/golangci-lint-action to fetch pull requests
     name: lint
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
         uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
       - name: install Go
-        uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
+        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
         with:
-          go-version: 1.21.x
+          go-version: 1.22.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@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
         with:
-          version: v1.54.2
+          version: v1.55.2
diff --git a/.golangci.yml b/.golangci.yml
index 1711d51..58142f4 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,18 +1,41 @@
 linters:
   enable:
+    - errorlint
+    - gofumpt
+    - goimports
     - misspell
     - revive
+    - testifylint
 
 issues:
   exclude-rules:
     - path: _test.go
       linters:
         - errcheck
+  max-issues-per-linter: 0
+  max-same-issues: 0
 
 linters-settings:
+  goimports:
+    local-prefixes: github.com/prometheus/common
   revive:
     rules:
       # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
       - name: unused-parameter
         severity: warning
         disabled: true
+  testifylint:
+    disable:
+      - float-compare
+      - go-require
+    enable:
+      - bool-compare
+      - compares
+      - empty
+      - error-is-as
+      - error-nil
+      - expected-actual
+      - len
+      - require-error
+      - suite-dont-use-pkg
+      - suite-extra-assert-call
diff --git a/Makefile.common b/Makefile.common
index 062a281..5fd1782 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -61,11 +61,11 @@ PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_
 SKIP_GOLANGCI_LINT :=
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.54.2
-# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
+GOLANGCI_LINT_VERSION ?= v1.55.2
+# 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))
-	ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386))
+	ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64))
 		# If we're in CI and there is an Actions file, that means the linter
 		# is being run in Actions, so we don't need to run it here.
 		ifneq (,$(SKIP_GOLANGCI_LINT))
diff --git a/assets/go.mod b/assets/go.mod
index 1f13afc..4f73955 100644
--- a/assets/go.mod
+++ b/assets/go.mod
@@ -1,3 +1,3 @@
 module github.com/prometheus/common/assets
 
-go 1.20
+go 1.21
diff --git a/config/generate.go b/config/generate.go
index 0033dd7..dbcf9be 100644
--- a/config/generate.go
+++ b/config/generate.go
@@ -71,7 +71,6 @@ func SerialNumber() *big.Int {
 	serialNumber.Add(&serial, big.NewInt(1))
 
 	return &serial
-
 }
 
 func GenerateCertificateAuthority(commonName string, parentCert *x509.Certificate, parentKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
@@ -170,7 +169,7 @@ func writeCertificateAndKey(path string, cert *x509.Certificate, key *rsa.Privat
 		return err
 	}
 
-	if err := os.WriteFile(fmt.Sprintf("%s.crt", path), b.Bytes(), 0644); err != nil {
+	if err := os.WriteFile(fmt.Sprintf("%s.crt", path), b.Bytes(), 0o644); err != nil {
 		return err
 	}
 
@@ -179,7 +178,7 @@ func writeCertificateAndKey(path string, cert *x509.Certificate, key *rsa.Privat
 		return err
 	}
 
-	if err := os.WriteFile(fmt.Sprintf("%s.key", path), b.Bytes(), 0644); err != nil {
+	if err := os.WriteFile(fmt.Sprintf("%s.key", path), b.Bytes(), 0o644); err != nil {
 		return err
 	}
 
@@ -239,7 +238,7 @@ func main() {
 		log.Fatal(err)
 	}
 
-	if err := os.WriteFile("testdata/tls-ca-chain.pem", b.Bytes(), 0644); err != nil {
+	if err := os.WriteFile("testdata/tls-ca-chain.pem", b.Bytes(), 0o644); err != nil {
 		log.Fatal(err)
 	}
 }
diff --git a/config/http_config.go b/config/http_config.go
index 14ee673..d8750bc 100644
--- a/config/http_config.go
+++ b/config/http_config.go
@@ -30,7 +30,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/mwitkow/go-conntrack"
+	conntrack "github.com/mwitkow/go-conntrack"
 	"golang.org/x/net/http/httpproxy"
 	"golang.org/x/net/http2"
 	"golang.org/x/oauth2"
@@ -309,6 +309,9 @@ type HTTPClientConfig struct {
 	// The omitempty flag is not set, because it would be hidden from the
 	// marshalled configuration when set to false.
 	EnableHTTP2 bool `yaml:"enable_http2" json:"enable_http2"`
+	// Host optionally overrides the Host header to send.
+	// If empty, the host from the URL will be used.
+	Host string `yaml:"host,omitempty" json:"host,omitempty"`
 	// Proxy configuration.
 	ProxyConfig `yaml:",inline"`
 }
@@ -378,9 +381,6 @@ func (c *HTTPClientConfig) Validate() error {
 		if len(c.OAuth2.ClientID) == 0 {
 			return fmt.Errorf("oauth2 client_id must be configured")
 		}
-		if len(c.OAuth2.ClientSecret) == 0 && len(c.OAuth2.ClientSecretFile) == 0 {
-			return fmt.Errorf("either oauth2 client_secret or client_secret_file must be configured")
-		}
 		if len(c.OAuth2.TokenURL) == 0 {
 			return fmt.Errorf("oauth2 token_url must be configured")
 		}
@@ -430,6 +430,7 @@ type httpClientOptions struct {
 	http2Enabled      bool
 	idleConnTimeout   time.Duration
 	userAgent         string
+	host              string
 }
 
 // HTTPClientOption defines an option that can be applied to the HTTP client.
@@ -470,6 +471,13 @@ func WithUserAgent(ua string) HTTPClientOption {
 	}
 }
 
+// WithHost allows setting the host header.
+func WithHost(host string) HTTPClientOption {
+	return func(opts *httpClientOptions) {
+		opts.host = host
+	}
+}
+
 // NewClient returns a http.Client using the specified http.RoundTripper.
 func newClient(rt http.RoundTripper) *http.Client {
 	return &http.Client{Transport: rt}
@@ -571,6 +579,10 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
 			rt = NewUserAgentRoundTripper(opts.userAgent, rt)
 		}
 
+		if opts.host != "" {
+			rt = NewHostRoundTripper(opts.host, rt)
+		}
+
 		// Return a new configured RoundTripper.
 		return rt, nil
 	}
@@ -630,7 +642,7 @@ func (rt *authorizationCredentialsFileRoundTripper) RoundTrip(req *http.Request)
 	if len(req.Header.Get("Authorization")) == 0 {
 		b, err := os.ReadFile(rt.authCredentialsFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read authorization credentials file %s: %s", rt.authCredentialsFile, err)
+			return nil, fmt.Errorf("unable to read authorization credentials file %s: %w", rt.authCredentialsFile, err)
 		}
 		authCredentials := strings.TrimSpace(string(b))
 
@@ -670,7 +682,7 @@ func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
 	if rt.usernameFile != "" {
 		usernameBytes, err := os.ReadFile(rt.usernameFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read basic auth username file %s: %s", rt.usernameFile, err)
+			return nil, fmt.Errorf("unable to read basic auth username file %s: %w", rt.usernameFile, err)
 		}
 		username = strings.TrimSpace(string(usernameBytes))
 	} else {
@@ -679,7 +691,7 @@ func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
 	if rt.passwordFile != "" {
 		passwordBytes, err := os.ReadFile(rt.passwordFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read basic auth password file %s: %s", rt.passwordFile, err)
+			return nil, fmt.Errorf("unable to read basic auth password file %s: %w", rt.passwordFile, err)
 		}
 		password = strings.TrimSpace(string(passwordBytes))
 	} else {
@@ -723,19 +735,18 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
 	if rt.config.ClientSecretFile != "" {
 		data, err := os.ReadFile(rt.config.ClientSecretFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read oauth2 client secret file %s: %s", rt.config.ClientSecretFile, err)
+			return nil, fmt.Errorf("unable to read oauth2 client secret file %s: %w", rt.config.ClientSecretFile, err)
 		}
 		secret = strings.TrimSpace(string(data))
 		rt.mtx.RLock()
 		changed = secret != rt.secret
 		rt.mtx.RUnlock()
+	} else {
+		// Either an inline secret or nothing (use an empty string) was provided.
+		secret = string(rt.config.ClientSecret)
 	}
 
 	if changed || rt.rt == nil {
-		if rt.config.ClientSecret != "" {
-			secret = string(rt.config.ClientSecret)
-		}
-
 		config := &clientcredentials.Config{
 			ClientID:       rt.config.ClientID,
 			ClientSecret:   secret,
@@ -977,7 +988,7 @@ func (c *TLSConfig) getClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Ce
 	if c.CertFile != "" {
 		certData, err = os.ReadFile(c.CertFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read specified client cert (%s): %s", c.CertFile, err)
+			return nil, fmt.Errorf("unable to read specified client cert (%s): %w", c.CertFile, err)
 		}
 	} else {
 		certData = []byte(c.Cert)
@@ -986,7 +997,7 @@ func (c *TLSConfig) getClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Ce
 	if c.KeyFile != "" {
 		keyData, err = os.ReadFile(c.KeyFile)
 		if err != nil {
-			return nil, fmt.Errorf("unable to read specified client key (%s): %s", c.KeyFile, err)
+			return nil, fmt.Errorf("unable to read specified client key (%s): %w", c.KeyFile, err)
 		}
 	} else {
 		keyData = []byte(c.Key)
@@ -994,7 +1005,7 @@ func (c *TLSConfig) getClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Ce
 
 	cert, err := tls.X509KeyPair(certData, keyData)
 	if err != nil {
-		return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
+		return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %w", c.CertFile, c.KeyFile, err)
 	}
 
 	return &cert, nil
@@ -1004,7 +1015,7 @@ func (c *TLSConfig) getClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Ce
 func readCAFile(f string) ([]byte, error) {
 	data, err := os.ReadFile(f)
 	if err != nil {
-		return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
+		return nil, fmt.Errorf("unable to load specified CA cert %s: %w", f, err)
 	}
 	return data, nil
 }
@@ -1168,11 +1179,21 @@ type userAgentRoundTripper struct {
 	rt        http.RoundTripper
 }
 
+type hostRoundTripper struct {
+	host string
+	rt   http.RoundTripper
+}
+
 // NewUserAgentRoundTripper adds the user agent every request header.
 func NewUserAgentRoundTripper(userAgent string, rt http.RoundTripper) http.RoundTripper {
 	return &userAgentRoundTripper{userAgent, rt}
 }
 
+// NewHostRoundTripper sets the [http.Request.Host] of every request.
+func NewHostRoundTripper(host string, rt http.RoundTripper) http.RoundTripper {
+	return &hostRoundTripper{host, rt}
+}
+
 func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 	req = cloneRequest(req)
 	req.Header.Set("User-Agent", rt.userAgent)
@@ -1185,6 +1206,19 @@ func (rt *userAgentRoundTripper) CloseIdleConnections() {
 	}
 }
 
+func (rt *hostRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	req = cloneRequest(req)
+	req.Host = rt.host
+	req.Header.Set("Host", rt.host)
+	return rt.rt.RoundTrip(req)
+}
+
+func (rt *hostRoundTripper) CloseIdleConnections() {
+	if ci, ok := rt.rt.(closeIdler); ok {
+		ci.CloseIdleConnections()
+	}
+}
+
 func (c HTTPClientConfig) String() string {
 	b, err := yaml.Marshal(c)
 	if err != nil {
diff --git a/expfmt/decode.go b/expfmt/decode.go
index 0ca86a3..b2b89b0 100644
--- a/expfmt/decode.go
+++ b/expfmt/decode.go
@@ -14,6 +14,7 @@
 package expfmt
 
 import (
+	"bufio"
 	"fmt"
 	"io"
 	"math"
@@ -21,8 +22,8 @@ import (
 	"net/http"
 
 	dto "github.com/prometheus/client_model/go"
+	"google.golang.org/protobuf/encoding/protodelim"
 
-	"github.com/matttproud/golang_protobuf_extensions/v2/pbutil"
 	"github.com/prometheus/common/model"
 )
 
@@ -44,7 +45,7 @@ func ResponseFormat(h http.Header) Format {
 
 	mediatype, params, err := mime.ParseMediaType(ct)
 	if err != nil {
-		return FmtUnknown
+		return fmtUnknown
 	}
 
 	const textType = "text/plain"
@@ -52,28 +53,28 @@ func ResponseFormat(h http.Header) Format {
 	switch mediatype {
 	case ProtoType:
 		if p, ok := params["proto"]; ok && p != ProtoProtocol {
-			return FmtUnknown
+			return fmtUnknown
 		}
 		if e, ok := params["encoding"]; ok && e != "delimited" {
-			return FmtUnknown
+			return fmtUnknown
 		}
-		return FmtProtoDelim
+		return fmtProtoDelim
 
 	case textType:
 		if v, ok := params["version"]; ok && v != TextVersion {
-			return FmtUnknown
+			return fmtUnknown
 		}
-		return FmtText
+		return fmtText
 	}
 
-	return FmtUnknown
+	return fmtUnknown
 }
 
 // NewDecoder returns a new decoder based on the given input format.
 // If the input format does not imply otherwise, a text format decoder is returned.
 func NewDecoder(r io.Reader, format Format) Decoder {
-	switch format {
-	case FmtProtoDelim:
+	switch format.FormatType() {
+	case TypeProtoDelim:
 		return &protoDecoder{r: r}
 	}
 	return &textDecoder{r: r}
@@ -86,8 +87,10 @@ type protoDecoder struct {
 
 // Decode implements the Decoder interface.
 func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
-	_, err := pbutil.ReadDelimited(d.r, v)
-	if err != nil {
+	opts := protodelim.UnmarshalOptions{
+		MaxSize: -1,
+	}
+	if err := opts.UnmarshalFrom(bufio.NewReader(d.r), v); err != nil {
 		return err
 	}
 	if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
diff --git a/expfmt/encode.go b/expfmt/encode.go
index ca21406..7f6cbe7 100644
--- a/expfmt/encode.go
+++ b/expfmt/encode.go
@@ -18,10 +18,12 @@ import (
 	"io"
 	"net/http"
 
-	"github.com/matttproud/golang_protobuf_extensions/v2/pbutil"
-	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
+	"google.golang.org/protobuf/encoding/protodelim"
 	"google.golang.org/protobuf/encoding/prototext"
 
+	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
+	"github.com/prometheus/common/model"
+
 	dto "github.com/prometheus/client_model/go"
 )
 
@@ -60,23 +62,32 @@ func (ec encoderCloser) Close() error {
 // as the support is still experimental. To include the option to negotiate
 // FmtOpenMetrics, use NegotiateOpenMetrics.
 func Negotiate(h http.Header) Format {
+	escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
 	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
+		if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
+			switch Format(escapeParam) {
+			case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
+				escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
+			default:
+				// If the escaping parameter is unknown, ignore it.
+			}
+		}
 		ver := ac.Params["version"]
 		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
 			switch ac.Params["encoding"] {
 			case "delimited":
-				return FmtProtoDelim
+				return fmtProtoDelim + escapingScheme
 			case "text":
-				return FmtProtoText
+				return fmtProtoText + escapingScheme
 			case "compact-text":
-				return FmtProtoCompact
+				return fmtProtoCompact + escapingScheme
 			}
 		}
 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
-			return FmtText
+			return fmtText + escapingScheme
 		}
 	}
-	return FmtText
+	return fmtText + escapingScheme
 }
 
 // NegotiateIncludingOpenMetrics works like Negotiate but includes
@@ -84,29 +95,40 @@ func Negotiate(h http.Header) Format {
 // temporary and will disappear once FmtOpenMetrics is fully supported and as
 // such may be negotiated by the normal Negotiate function.
 func NegotiateIncludingOpenMetrics(h http.Header) Format {
+	escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
 	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
+		if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
+			switch Format(escapeParam) {
+			case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
+				escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
+			default:
+				// If the escaping parameter is unknown, ignore it.
+			}
+		}
 		ver := ac.Params["version"]
 		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
 			switch ac.Params["encoding"] {
 			case "delimited":
-				return FmtProtoDelim
+				return fmtProtoDelim + escapingScheme
 			case "text":
-				return FmtProtoText
+				return fmtProtoText + escapingScheme
 			case "compact-text":
-				return FmtProtoCompact
+				return fmtProtoCompact + escapingScheme
 			}
 		}
 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
-			return FmtText
+			return fmtText + escapingScheme
 		}
 		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
-			if ver == OpenMetricsVersion_1_0_0 {
-				return FmtOpenMetrics_1_0_0
+			switch ver {
+			case OpenMetricsVersion_1_0_0:
+				return fmtOpenMetrics_1_0_0 + escapingScheme
+			default:
+				return fmtOpenMetrics_0_0_1 + escapingScheme
 			}
-			return FmtOpenMetrics_0_0_1
 		}
 	}
-	return FmtText
+	return fmtText + escapingScheme
 }
 
 // NewEncoder returns a new encoder based on content type negotiation. All
@@ -115,44 +137,54 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
 // for FmtOpenMetrics, but a future (breaking) release will add the Close method
 // to the Encoder interface directly. The current version of the Encoder
 // interface is kept for backwards compatibility.
-func NewEncoder(w io.Writer, format Format) Encoder {
-	switch format {
-	case FmtProtoDelim:
+// In cases where the Format does not allow for UTF-8 names, the global
+// NameEscapingScheme will be applied.
+//
+// NewEncoder can be called with additional options to customize the OpenMetrics text output.
+// For example:
+// NewEncoder(w, FmtOpenMetrics_1_0_0, WithCreatedLines())
+//
+// Extra options are ignored for all other formats.
+func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder {
+	escapingScheme := format.ToEscapingScheme()
+
+	switch format.FormatType() {
+	case TypeProtoDelim:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
-				_, err := pbutil.WriteDelimited(w, v)
+				_, err := protodelim.MarshalTo(w, v)
 				return err
 			},
 			close: func() error { return nil },
 		}
-	case FmtProtoCompact:
+	case TypeProtoCompact:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
-				_, err := fmt.Fprintln(w, v.String())
+				_, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())
 				return err
 			},
 			close: func() error { return nil },
 		}
-	case FmtProtoText:
+	case TypeProtoText:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
-				_, err := fmt.Fprintln(w, prototext.Format(v))
+				_, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))
 				return err
 			},
 			close: func() error { return nil },
 		}
-	case FmtText:
+	case TypeTextPlain:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
-				_, err := MetricFamilyToText(w, v)
+				_, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))
 				return err
 			},
 			close: func() error { return nil },
 		}
-	case FmtOpenMetrics_0_0_1, FmtOpenMetrics_1_0_0:
+	case TypeOpenMetrics:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
-				_, err := MetricFamilyToOpenMetrics(w, v)
+				_, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...)
 				return err
 			},
 			close: func() error {
diff --git a/expfmt/expfmt.go b/expfmt/expfmt.go
index c4cb20f..6fc9555 100644
--- a/expfmt/expfmt.go
+++ b/expfmt/expfmt.go
@@ -14,30 +14,154 @@
 // Package expfmt contains tools for reading and writing Prometheus metrics.
 package expfmt
 
+import (
+	"strings"
+
+	"github.com/prometheus/common/model"
+)
+
 // Format specifies the HTTP content type of the different wire protocols.
 type Format string
 
-// Constants to assemble the Content-Type values for the different wire protocols.
+// Constants to assemble the Content-Type values for the different wire
+// protocols. The Content-Type strings here are all for the legacy exposition
+// formats, where valid characters for metric names and label names are limited.
+// Support for arbitrary UTF-8 characters in those names is already partially
+// implemented in this module (see model.ValidationScheme), but to actually use
+// it on the wire, new content-type strings will have to be agreed upon and
+// added here.
 const (
 	TextVersion              = "0.0.4"
 	ProtoType                = `application/vnd.google.protobuf`
 	ProtoProtocol            = `io.prometheus.client.MetricFamily`
-	ProtoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";"
+	protoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";"
 	OpenMetricsType          = `application/openmetrics-text`
 	OpenMetricsVersion_0_0_1 = "0.0.1"
 	OpenMetricsVersion_1_0_0 = "1.0.0"
 
-	// The Content-Type values for the different wire protocols.
-	FmtUnknown           Format = `<unknown>`
-	FmtText              Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
-	FmtProtoDelim        Format = ProtoFmt + ` encoding=delimited`
-	FmtProtoText         Format = ProtoFmt + ` encoding=text`
-	FmtProtoCompact      Format = ProtoFmt + ` encoding=compact-text`
-	FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
-	FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
+	// The Content-Type values for the different wire protocols. Note that these
+	// values are now unexported. If code was relying on comparisons to these
+	// constants, instead use FormatType().
+	fmtUnknown           Format = `<unknown>`
+	fmtText              Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
+	fmtProtoDelim        Format = protoFmt + ` encoding=delimited`
+	fmtProtoText         Format = protoFmt + ` encoding=text`
+	fmtProtoCompact      Format = protoFmt + ` encoding=compact-text`
+	fmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
+	fmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
 )
 
 const (
 	hdrContentType = "Content-Type"
 	hdrAccept      = "Accept"
 )
+
+// FormatType is a Go enum representing the overall category for the given
+// Format. As the number of Format permutations increases, doing basic string
+// comparisons are not feasible, so this enum captures the most useful
+// high-level attribute of the Format string.
+type FormatType int
+
+const (
+	TypeUnknown = iota
+	TypeProtoCompact
+	TypeProtoDelim
+	TypeProtoText
+	TypeTextPlain
+	TypeOpenMetrics
+)
+
+// NewFormat generates a new Format from the type provided. Mostly used for
+// tests, most Formats should be generated as part of content negotiation in
+// encode.go.
+func NewFormat(t FormatType) Format {
+	switch t {
+	case TypeProtoCompact:
+		return fmtProtoCompact
+	case TypeProtoDelim:
+		return fmtProtoDelim
+	case TypeProtoText:
+		return fmtProtoText
+	case TypeTextPlain:
+		return fmtText
+	case TypeOpenMetrics:
+		return fmtOpenMetrics_1_0_0
+	default:
+		return fmtUnknown
+	}
+}
+
+// FormatType deduces an overall FormatType for the given format.
+func (f Format) FormatType() FormatType {
+	toks := strings.Split(string(f), ";")
+	if len(toks) < 2 {
+		return TypeUnknown
+	}
+
+	params := make(map[string]string)
+	for i, t := range toks {
+		if i == 0 {
+			continue
+		}
+		args := strings.Split(t, "=")
+		if len(args) != 2 {
+			continue
+		}
+		params[strings.TrimSpace(args[0])] = strings.TrimSpace(args[1])
+	}
+
+	switch strings.TrimSpace(toks[0]) {
+	case ProtoType:
+		if params["proto"] != ProtoProtocol {
+			return TypeUnknown
+		}
+		switch params["encoding"] {
+		case "delimited":
+			return TypeProtoDelim
+		case "text":
+			return TypeProtoText
+		case "compact-text":
+			return TypeProtoCompact
+		default:
+			return TypeUnknown
+		}
+	case OpenMetricsType:
+		if params["charset"] != "utf-8" {
+			return TypeUnknown
+		}
+		return TypeOpenMetrics
+	case "text/plain":
+		v, ok := params["version"]
+		if !ok {
+			return TypeTextPlain
+		}
+		if v == TextVersion {
+			return TypeTextPlain
+		}
+		return TypeUnknown
+	default:
+		return TypeUnknown
+	}
+}
+
+// ToEscapingScheme returns an EscapingScheme depending on the Format. Iff the
+// Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid
+// "escaping" term exists, that will be used. Otherwise, the global default will
+// be returned.
+func (format Format) ToEscapingScheme() model.EscapingScheme {
+	for _, p := range strings.Split(string(format), ";") {
+		toks := strings.Split(p, "=")
+		if len(toks) != 2 {
+			continue
+		}
+		key, value := strings.TrimSpace(toks[0]), strings.TrimSpace(toks[1])
+		if key == model.EscapingKey {
+			scheme, err := model.ToEscapingScheme(value)
+			if err != nil {
+				return model.NameEscapingScheme
+			}
+			return scheme
+		}
+	}
+	return model.NameEscapingScheme
+}
diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go
index 21cdddc..63fc1f4 100644
--- a/expfmt/openmetrics_create.go
+++ b/expfmt/openmetrics_create.go
@@ -22,11 +22,35 @@ import (
 	"strconv"
 	"strings"
 
+	"google.golang.org/protobuf/types/known/timestamppb"
+
 	"github.com/prometheus/common/model"
 
 	dto "github.com/prometheus/client_model/go"
 )
 
+type encoderOption struct {
+	withCreatedLines bool
+}
+
+type EncoderOption func(*encoderOption)
+
+// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
+// to include _created lines (See
+// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1).
+// Created timestamps can improve the accuracy of series reset detection, but
+// come with a bandwidth cost.
+//
+// At the time of writing, created timestamp ingestion is still experimental in
+// Prometheus and need to be enabled with the feature-flag
+// `--feature-flag=created-timestamp-zero-ingestion`, and breaking changes are
+// still possible. Therefore, it is recommended to use this feature with caution.
+func WithCreatedLines() EncoderOption {
+	return func(t *encoderOption) {
+		t.withCreatedLines = true
+	}
+}
+
 // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
 // OpenMetrics text format and writes the resulting lines to 'out'. It returns
 // the number of bytes written and any error encountered. The output will have
@@ -35,6 +59,18 @@ import (
 // sanity checks. If the input contains duplicate metrics or invalid metric or
 // label names, the conversion will result in invalid text format output.
 //
+// If metric names conform to the legacy validation pattern, they will be placed
+// outside the brackets in the traditional way, like `foo{}`. If the metric name
+// fails the legacy validation check, it will be placed quoted inside the
+// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
+// no error will be thrown in this case.
+//
+// Similar to metric names, if label names conform to the legacy validation
+// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
+// name fails the legacy validation check, it will be quoted:
+// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
+// no error will be thrown in this case.
+//
 // This function fulfills the type 'expfmt.encoder'.
 //
 // Note that OpenMetrics requires a final `# EOF` line. Since this function acts
@@ -52,15 +88,20 @@ import (
 //     its type will be set to `unknown` in that case to avoid invalid OpenMetrics
 //     output.
 //
-//   - No support for the following (optional) features: `# UNIT` line, `_created`
-//     line, info type, stateset type, gaugehistogram type.
+//   - No support for the following (optional) features: `# UNIT` line, info type,
+//     stateset type, gaugehistogram type.
 //
 //   - The size of exemplar labels is not checked (i.e. it's possible to create
 //     exemplars that are larger than allowed by the OpenMetrics specification).
 //
 //   - The value of Counters is not checked. (OpenMetrics doesn't allow counters
 //     with a `NaN` value.)
-func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
+func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) {
+	toOM := encoderOption{}
+	for _, option := range options {
+		option(&toOM)
+	}
+
 	name := in.GetName()
 	if name == "" {
 		return 0, fmt.Errorf("MetricFamily has no name: %s", in)
@@ -98,7 +139,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 		if err != nil {
 			return
 		}
-		n, err = w.WriteString(shortName)
+		n, err = writeName(w, shortName)
 		written += n
 		if err != nil {
 			return
@@ -124,7 +165,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 	if err != nil {
 		return
 	}
-	n, err = w.WriteString(shortName)
+	n, err = writeName(w, shortName)
 	written += n
 	if err != nil {
 		return
@@ -152,6 +193,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 		return
 	}
 
+	var createdTsBytesWritten int
 	// Finally the samples, one line for each.
 	for _, metric := range in.Metric {
 		switch metricType {
@@ -169,6 +211,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 				metric.Counter.GetValue(), 0, false,
 				metric.Counter.Exemplar,
 			)
+			if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
+				createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
+				n += createdTsBytesWritten
+			}
 		case dto.MetricType_GAUGE:
 			if metric.Gauge == nil {
 				return written, fmt.Errorf(
@@ -223,6 +269,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 				0, metric.Summary.GetSampleCount(), true,
 				nil,
 			)
+			if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil {
+				createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
+				n += createdTsBytesWritten
+			}
 		case dto.MetricType_HISTOGRAM:
 			if metric.Histogram == nil {
 				return written, fmt.Errorf(
@@ -271,6 +321,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
 				0, metric.Histogram.GetSampleCount(), true,
 				nil,
 			)
+			if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil {
+				createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
+				n += createdTsBytesWritten
+			}
 		default:
 			return written, fmt.Errorf(
 				"unexpected type in metric %s %s", name, metric,
@@ -303,21 +357,9 @@ func writeOpenMetricsSample(
 	floatValue float64, intValue uint64, useIntValue bool,
 	exemplar *dto.Exemplar,
 ) (int, error) {
-	var written int
-	n, err := w.WriteString(name)
-	written += n
-	if err != nil {
-		return written, err
-	}
-	if suffix != "" {
-		n, err = w.WriteString(suffix)
-		written += n
-		if err != nil {
-			return written, err
-		}
-	}
-	n, err = writeOpenMetricsLabelPairs(
-		w, metric.Label, additionalLabelName, additionalLabelValue,
+	written := 0
+	n, err := writeOpenMetricsNameAndLabelPairs(
+		w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
 	)
 	written += n
 	if err != nil {
@@ -350,7 +392,7 @@ func writeOpenMetricsSample(
 			return written, err
 		}
 	}
-	if exemplar != nil {
+	if exemplar != nil && len(exemplar.Label) > 0 {
 		n, err = writeExemplar(w, exemplar)
 		written += n
 		if err != nil {
@@ -365,27 +407,58 @@ func writeOpenMetricsSample(
 	return written, nil
 }
 
-// writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
-// in OpenMetrics style.
-func writeOpenMetricsLabelPairs(
+// writeOpenMetricsNameAndLabelPairs works like writeOpenMetricsSample but
+// formats the float in OpenMetrics style.
+func writeOpenMetricsNameAndLabelPairs(
 	w enhancedWriter,
+	name string,
 	in []*dto.LabelPair,
 	additionalLabelName string, additionalLabelValue float64,
 ) (int, error) {
-	if len(in) == 0 && additionalLabelName == "" {
-		return 0, nil
-	}
 	var (
-		written   int
-		separator byte = '{'
+		written            int
+		separator          byte = '{'
+		metricInsideBraces      = false
 	)
+
+	if name != "" {
+		// If the name does not pass the legacy validity check, we must put the
+		// metric name inside the braces, quoted.
+		if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
+			metricInsideBraces = true
+			err := w.WriteByte(separator)
+			written++
+			if err != nil {
+				return written, err
+			}
+			separator = ','
+		}
+
+		n, err := writeName(w, name)
+		written += n
+		if err != nil {
+			return written, err
+		}
+	}
+
+	if len(in) == 0 && additionalLabelName == "" {
+		if metricInsideBraces {
+			err := w.WriteByte('}')
+			written++
+			if err != nil {
+				return written, err
+			}
+		}
+		return written, nil
+	}
+
 	for _, lp := range in {
 		err := w.WriteByte(separator)
 		written++
 		if err != nil {
 			return written, err
 		}
-		n, err := w.WriteString(lp.GetName())
+		n, err := writeName(w, lp.GetName())
 		written += n
 		if err != nil {
 			return written, err
@@ -442,6 +515,49 @@ func writeOpenMetricsLabelPairs(
 	return written, nil
 }
 
+// writeOpenMetricsCreated writes the created timestamp for a single time series
+// following OpenMetrics text format to w, given the metric name, the metric proto
+// message itself, optionally a suffix to be removed, e.g. '_total' for counters,
+// an additional label name with a float64 value (use empty string as label name if
+// not required) and the timestamp that represents the created timestamp.
+// The function returns the number of bytes written and any error encountered.
+func writeOpenMetricsCreated(w enhancedWriter,
+	name, suffixToTrim string, metric *dto.Metric,
+	additionalLabelName string, additionalLabelValue float64,
+	createdTimestamp *timestamppb.Timestamp,
+) (int, error) {
+	written := 0
+	n, err := writeOpenMetricsNameAndLabelPairs(
+		w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue,
+	)
+	written += n
+	if err != nil {
+		return written, err
+	}
+
+	err = w.WriteByte(' ')
+	written++
+	if err != nil {
+		return written, err
+	}
+
+	// TODO(beorn7): Format this directly from components of ts to
+	// avoid overflow/underflow and precision issues of the float
+	// conversion.
+	n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9)
+	written += n
+	if err != nil {
+		return written, err
+	}
+
+	err = w.WriteByte('\n')
+	written++
+	if err != nil {
+		return written, err
+	}
+	return written, nil
+}
+
 // writeExemplar writes the provided exemplar in OpenMetrics format to w. The
 // function returns the number of bytes written and any error encountered.
 func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
@@ -451,7 +567,7 @@ func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
 	if err != nil {
 		return written, err
 	}
-	n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
+	n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)
 	written += n
 	if err != nil {
 		return written, err
diff --git a/expfmt/text_create.go b/expfmt/text_create.go
index 2946b8f..f9b8265 100644
--- a/expfmt/text_create.go
+++ b/expfmt/text_create.go
@@ -62,6 +62,18 @@ var (
 // contains duplicate metrics or invalid metric or label names, the conversion
 // will result in invalid text format output.
 //
+// If metric names conform to the legacy validation pattern, they will be placed
+// outside the brackets in the traditional way, like `foo{}`. If the metric name
+// fails the legacy validation check, it will be placed quoted inside the
+// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
+// no error will be thrown in this case.
+//
+// Similar to metric names, if label names conform to the legacy validation
+// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
+// name fails the legacy validation check, it will be quoted:
+// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
+// no error will be thrown in this case.
+//
 // This method fulfills the type 'prometheus.encoder'.
 func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
 	// Fail-fast checks.
@@ -98,7 +110,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
 		if err != nil {
 			return
 		}
-		n, err = w.WriteString(name)
+		n, err = writeName(w, name)
 		written += n
 		if err != nil {
 			return
@@ -124,7 +136,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
 	if err != nil {
 		return
 	}
-	n, err = w.WriteString(name)
+	n, err = writeName(w, name)
 	written += n
 	if err != nil {
 		return
@@ -280,21 +292,9 @@ func writeSample(
 	additionalLabelName string, additionalLabelValue float64,
 	value float64,
 ) (int, error) {
-	var written int
-	n, err := w.WriteString(name)
-	written += n
-	if err != nil {
-		return written, err
-	}
-	if suffix != "" {
-		n, err = w.WriteString(suffix)
-		written += n
-		if err != nil {
-			return written, err
-		}
-	}
-	n, err = writeLabelPairs(
-		w, metric.Label, additionalLabelName, additionalLabelValue,
+	written := 0
+	n, err := writeNameAndLabelPairs(
+		w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
 	)
 	written += n
 	if err != nil {
@@ -330,32 +330,64 @@ func writeSample(
 	return written, nil
 }
 
-// writeLabelPairs converts a slice of LabelPair proto messages plus the
-// explicitly given additional label pair into text formatted as required by the
-// text format and writes it to 'w'. An empty slice in combination with an empty
-// string 'additionalLabelName' results in nothing being written. Otherwise, the
-// label pairs are written, escaped as required by the text format, and enclosed
-// in '{...}'. The function returns the number of bytes written and any error
-// encountered.
-func writeLabelPairs(
+// writeNameAndLabelPairs converts a slice of LabelPair proto messages plus the
+// explicitly given metric name and additional label pair into text formatted as
+// required by the text format and writes it to 'w'. An empty slice in
+// combination with an empty string 'additionalLabelName' results in nothing
+// being written. Otherwise, the label pairs are written, escaped as required by
+// the text format, and enclosed in '{...}'. The function returns the number of
+// bytes written and any error encountered. If the metric name is not
+// legacy-valid, it will be put inside the brackets as well. Legacy-invalid
+// label names will also be quoted.
+func writeNameAndLabelPairs(
 	w enhancedWriter,
+	name string,
 	in []*dto.LabelPair,
 	additionalLabelName string, additionalLabelValue float64,
 ) (int, error) {
-	if len(in) == 0 && additionalLabelName == "" {
-		return 0, nil
-	}
 	var (
-		written   int
-		separator byte = '{'
+		written            int
+		separator          byte = '{'
+		metricInsideBraces      = false
 	)
+
+	if name != "" {
+		// If the name does not pass the legacy validity check, we must put the
+		// metric name inside the braces.
+		if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
+			metricInsideBraces = true
+			err := w.WriteByte(separator)
+			written++
+			if err != nil {
+				return written, err
+			}
+			separator = ','
+		}
+		n, err := writeName(w, name)
+		written += n
+		if err != nil {
+			return written, err
+		}
+	}
+
+	if len(in) == 0 && additionalLabelName == "" {
+		if metricInsideBraces {
+			err := w.WriteByte('}')
+			written++
+			if err != nil {
+				return written, err
+			}
+		}
+		return written, nil
+	}
+
 	for _, lp := range in {
 		err := w.WriteByte(separator)
 		written++
 		if err != nil {
 			return written, err
 		}
-		n, err := w.WriteString(lp.GetName())
+		n, err := writeName(w, lp.GetName())
 		written += n
 		if err != nil {
 			return written, err
@@ -462,3 +494,27 @@ func writeInt(w enhancedWriter, i int64) (int, error) {
 	numBufPool.Put(bp)
 	return written, err
 }
+
+// writeName writes a string as-is if it complies with the legacy naming
+// scheme, or escapes it in double quotes if not.
+func writeName(w enhancedWriter, name string) (int, error) {
+	if model.IsValidLegacyMetricName(model.LabelValue(name)) {
+		return w.WriteString(name)
+	}
+	var written int
+	var err error
+	err = w.WriteByte('"')
+	written++
+	if err != nil {
+		return written, err
+	}
+	var n int
+	n, err = writeEscapedString(w, name, true)
+	written += n
+	if err != nil {
+		return written, err
+	}
+	err = w.WriteByte('"')
+	written++
+	return written, err
+}
diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go
index 35db1cc..2649021 100644
--- a/expfmt/text_parse.go
+++ b/expfmt/text_parse.go
@@ -16,6 +16,7 @@ package expfmt
 import (
 	"bufio"
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
 	"math"
@@ -24,8 +25,9 @@ import (
 
 	dto "github.com/prometheus/client_model/go"
 
-	"github.com/prometheus/common/model"
 	"google.golang.org/protobuf/proto"
+
+	"github.com/prometheus/common/model"
 )
 
 // A stateFn is a function that represents a state in a state machine. By
@@ -112,7 +114,7 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF
 	// stream. Turn this error into something nicer and more
 	// meaningful. (io.EOF is often used as a signal for the legitimate end
 	// of an input stream.)
-	if p.err == io.EOF {
+	if p.err != nil && errors.Is(p.err, io.EOF) {
 		p.parseError("unexpected end of input stream")
 	}
 	return p.metricFamiliesByName, p.err
@@ -146,7 +148,7 @@ func (p *TextParser) startOfLine() stateFn {
 		// which is not an error but the signal that we are done.
 		// Any other error that happens to align with the start of
 		// a line is still an error.
-		if p.err == io.EOF {
+		if errors.Is(p.err, io.EOF) {
 			p.err = nil
 		}
 		return nil
diff --git a/go.mod b/go.mod
index 05a518b..d932073 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,18 @@
 module github.com/prometheus/common
 
-go 1.20
+go 1.21
 
 require (
-	github.com/alecthomas/kingpin/v2 v2.3.2
+	github.com/alecthomas/kingpin/v2 v2.4.0
 	github.com/go-kit/log v0.2.1
+	github.com/google/go-cmp v0.6.0
 	github.com/julienschmidt/httprouter v1.3.0
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
 	github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
-	github.com/prometheus/client_golang v1.17.0
-	github.com/prometheus/client_model v0.5.0
-	golang.org/x/net v0.18.0
-	golang.org/x/oauth2 v0.14.0
-	google.golang.org/protobuf v1.31.0
+	github.com/prometheus/client_golang v1.19.0
+	github.com/prometheus/client_model v0.6.0
+	golang.org/x/net v0.21.0
+	golang.org/x/oauth2 v0.17.0
+	google.golang.org/protobuf v1.32.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -23,10 +23,10 @@ require (
 	github.com/go-logfmt/logfmt v0.5.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/jpillora/backoff v1.0.0 // indirect
-	github.com/prometheus/procfs v0.11.1 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/rogpeppe/go-internal v1.10.0 // indirect
 	github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
-	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
diff --git a/go.sum b/go.sum
index 4e1d3b0..dc358a8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=
-github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
+github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
+github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
 github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -8,6 +8,7 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 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/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
 github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
@@ -17,44 +18,45 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 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/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
-github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
-github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
-github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
-github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
+github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
+github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
+github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
+github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
-golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
-golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
-golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
@@ -65,8 +67,8 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 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=
@@ -74,3 +76,4 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 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/model/alert.go b/model/alert.go
index 35e739c..178fdba 100644
--- a/model/alert.go
+++ b/model/alert.go
@@ -90,13 +90,13 @@ func (a *Alert) Validate() error {
 		return fmt.Errorf("start time must be before end time")
 	}
 	if err := a.Labels.Validate(); err != nil {
-		return fmt.Errorf("invalid label set: %s", err)
+		return fmt.Errorf("invalid label set: %w", err)
 	}
 	if len(a.Labels) == 0 {
 		return fmt.Errorf("at least one label pair required")
 	}
 	if err := a.Annotations.Validate(); err != nil {
-		return fmt.Errorf("invalid annotations: %s", err)
+		return fmt.Errorf("invalid annotations: %w", err)
 	}
 	return nil
 }
diff --git a/model/labels.go b/model/labels.go
index ef89563..3317ce2 100644
--- a/model/labels.go
+++ b/model/labels.go
@@ -97,17 +97,25 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
 // therewith.
 type LabelName string
 
-// IsValid is true iff the label name matches the pattern of LabelNameRE. This
-// method, however, does not use LabelNameRE for the check but a much faster
-// hardcoded implementation.
+// IsValid returns true iff name matches the pattern of LabelNameRE for legacy
+// names, and iff it's valid UTF-8 if NameValidationScheme is set to
+// UTF8Validation. For the legacy matching, it does not use LabelNameRE for the
+// check but a much faster hardcoded implementation.
 func (ln LabelName) IsValid() bool {
 	if len(ln) == 0 {
 		return false
 	}
-	for i, b := range ln {
-		if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
-			return false
+	switch NameValidationScheme {
+	case LegacyValidation:
+		for i, b := range ln {
+			if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
+				return false
+			}
 		}
+	case UTF8Validation:
+		return utf8.ValidString(string(ln))
+	default:
+		panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
 	}
 	return true
 }
@@ -164,7 +172,7 @@ func (l LabelNames) String() string {
 // A LabelValue is an associated value for a LabelName.
 type LabelValue string
 
-// IsValid returns true iff the string is a valid UTF8.
+// IsValid returns true iff the string is a valid UTF-8.
 func (lv LabelValue) IsValid() bool {
 	return utf8.ValidString(string(lv))
 }
diff --git a/model/metric.go b/model/metric.go
index 00804b7..0bd29b3 100644
--- a/model/metric.go
+++ b/model/metric.go
@@ -18,15 +18,84 @@ import (
 	"regexp"
 	"sort"
 	"strings"
+	"unicode/utf8"
+
+	dto "github.com/prometheus/client_model/go"
+	"google.golang.org/protobuf/proto"
 )
 
 var (
-	// MetricNameRE is a regular expression matching valid metric
-	// names. Note that the IsValidMetricName function performs the same
-	// check but faster than a match with this regular expression.
-	MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`)
+	// NameValidationScheme determines the method of name validation to be used by
+	// all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 mode
+	// in isolation from other components that don't support UTF-8 may result in
+	// bugs or other undefined behavior. This value is intended to be set by
+	// UTF-8-aware binaries as part of their startup. To avoid need for locking,
+	// this value should be set once, ideally in an init(), before multiple
+	// goroutines are started.
+	NameValidationScheme = LegacyValidation
+
+	// NameEscapingScheme defines the default way that names will be
+	// escaped when presented to systems that do not support UTF-8 names. If the
+	// Content-Type "escaping" term is specified, that will override this value.
+	NameEscapingScheme = ValueEncodingEscaping
+)
+
+// ValidationScheme is a Go enum for determining how metric and label names will
+// be validated by this library.
+type ValidationScheme int
+
+const (
+	// LegacyValidation is a setting that requirets that metric and label names
+	// conform to the original Prometheus character requirements described by
+	// MetricNameRE and LabelNameRE.
+	LegacyValidation ValidationScheme = iota
+
+	// UTF8Validation only requires that metric and label names be valid UTF-8
+	// strings.
+	UTF8Validation
+)
+
+type EscapingScheme int
+
+const (
+	// NoEscaping indicates that a name will not be escaped. Unescaped names that
+	// do not conform to the legacy validity check will use a new exposition
+	// format syntax that will be officially standardized in future versions.
+	NoEscaping EscapingScheme = iota
+
+	// UnderscoreEscaping replaces all legacy-invalid characters with underscores.
+	UnderscoreEscaping
+
+	// DotsEscaping is similar to UnderscoreEscaping, except that dots are
+	// converted to `_dot_` and pre-existing underscores are converted to `__`.
+	DotsEscaping
+
+	// ValueEncodingEscaping prepends the name with `U__` and replaces all invalid
+	// characters with the unicode value, surrounded by underscores. Single
+	// underscores are replaced with double underscores.
+	ValueEncodingEscaping
+)
+
+const (
+	// EscapingKey is the key in an Accept or Content-Type header that defines how
+	// metric and label names that do not conform to the legacy character
+	// requirements should be escaped when being scraped by a legacy prometheus
+	// system. If a system does not explicitly pass an escaping parameter in the
+	// Accept header, the default NameEscapingScheme will be used.
+	EscapingKey = "escaping"
+
+	// Possible values for Escaping Key:
+	AllowUTF8         = "allow-utf-8" // No escaping required.
+	EscapeUnderscores = "underscores"
+	EscapeDots        = "dots"
+	EscapeValues      = "values"
 )
 
+// MetricNameRE is a regular expression matching valid metric
+// names. Note that the IsValidMetricName function performs the same
+// check but faster than a match with this regular expression.
+var MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`)
+
 // A Metric is similar to a LabelSet, but the key difference is that a Metric is
 // a singleton and refers to one and only one stream of samples.
 type Metric LabelSet
@@ -86,17 +155,302 @@ func (m Metric) FastFingerprint() Fingerprint {
 	return LabelSet(m).FastFingerprint()
 }
 
-// IsValidMetricName returns true iff name matches the pattern of MetricNameRE.
+// IsValidMetricName returns true iff name matches the pattern of MetricNameRE
+// for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is
+// selected.
+func IsValidMetricName(n LabelValue) bool {
+	switch NameValidationScheme {
+	case LegacyValidation:
+		return IsValidLegacyMetricName(n)
+	case UTF8Validation:
+		if len(n) == 0 {
+			return false
+		}
+		return utf8.ValidString(string(n))
+	default:
+		panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
+	}
+}
+
+// IsValidLegacyMetricName is similar to IsValidMetricName but always uses the
+// legacy validation scheme regardless of the value of NameValidationScheme.
 // This function, however, does not use MetricNameRE for the check but a much
 // faster hardcoded implementation.
-func IsValidMetricName(n LabelValue) bool {
+func IsValidLegacyMetricName(n LabelValue) bool {
 	if len(n) == 0 {
 		return false
 	}
 	for i, b := range n {
-		if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) {
+		if !isValidLegacyRune(b, i) {
 			return false
 		}
 	}
 	return true
 }
+
+// EscapeMetricFamily escapes the given metric names and labels with the given
+// escaping scheme. Returns a new object that uses the same pointers to fields
+// when possible and creates new escaped versions so as not to mutate the
+// input.
+func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricFamily {
+	if v == nil {
+		return nil
+	}
+
+	if scheme == NoEscaping {
+		return v
+	}
+
+	out := &dto.MetricFamily{
+		Help: v.Help,
+		Type: v.Type,
+	}
+
+	// If the name is nil, copy as-is, don't try to escape.
+	if v.Name == nil || IsValidLegacyMetricName(LabelValue(v.GetName())) {
+		out.Name = v.Name
+	} else {
+		out.Name = proto.String(EscapeName(v.GetName(), scheme))
+	}
+	for _, m := range v.Metric {
+		if !metricNeedsEscaping(m) {
+			out.Metric = append(out.Metric, m)
+			continue
+		}
+
+		escaped := &dto.Metric{
+			Gauge:       m.Gauge,
+			Counter:     m.Counter,
+			Summary:     m.Summary,
+			Untyped:     m.Untyped,
+			Histogram:   m.Histogram,
+			TimestampMs: m.TimestampMs,
+		}
+
+		for _, l := range m.Label {
+			if l.GetName() == MetricNameLabel {
+				if l.Value == nil || IsValidLegacyMetricName(LabelValue(l.GetValue())) {
+					escaped.Label = append(escaped.Label, l)
+					continue
+				}
+				escaped.Label = append(escaped.Label, &dto.LabelPair{
+					Name:  proto.String(MetricNameLabel),
+					Value: proto.String(EscapeName(l.GetValue(), scheme)),
+				})
+				continue
+			}
+			if l.Name == nil || IsValidLegacyMetricName(LabelValue(l.GetName())) {
+				escaped.Label = append(escaped.Label, l)
+				continue
+			}
+			escaped.Label = append(escaped.Label, &dto.LabelPair{
+				Name:  proto.String(EscapeName(l.GetName(), scheme)),
+				Value: l.Value,
+			})
+		}
+		out.Metric = append(out.Metric, escaped)
+	}
+	return out
+}
+
+func metricNeedsEscaping(m *dto.Metric) bool {
+	for _, l := range m.Label {
+		if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(LabelValue(l.GetValue())) {
+			return true
+		}
+		if !IsValidLegacyMetricName(LabelValue(l.GetName())) {
+			return true
+		}
+	}
+	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
+// noop). This function does not do any validation of the name.
+func EscapeName(name string, scheme EscapingScheme) string {
+	if len(name) == 0 {
+		return name
+	}
+	var escaped strings.Builder
+	switch scheme {
+	case NoEscaping:
+		return name
+	case UnderscoreEscaping:
+		if IsValidLegacyMetricName(LabelValue(name)) {
+			return name
+		}
+		for i, b := range name {
+			if isValidLegacyRune(b, i) {
+				escaped.WriteRune(b)
+			} else {
+				escaped.WriteRune('_')
+			}
+		}
+		return escaped.String()
+	case DotsEscaping:
+		// Do not early return for legacy valid names, we still escape underscores.
+		for i, b := range name {
+			if b == '_' {
+				escaped.WriteString("__")
+			} else if b == '.' {
+				escaped.WriteString("_dot_")
+			} else if isValidLegacyRune(b, i) {
+				escaped.WriteRune(b)
+			} else {
+				escaped.WriteRune('_')
+			}
+		}
+		return escaped.String()
+	case ValueEncodingEscaping:
+		if IsValidLegacyMetricName(LabelValue(name)) {
+			return name
+		}
+		escaped.WriteString("U__")
+		for i, b := range name {
+			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 {
+				escaped.WriteRune('_')
+				for s := 12; s >= 0; s -= 4 {
+					escaped.WriteByte(lowerhex[b>>uint(s)&0xF])
+				}
+				escaped.WriteRune('_')
+			}
+		}
+		return escaped.String()
+	default:
+		panic(fmt.Sprintf("invalid escaping scheme %d", scheme))
+	}
+}
+
+// lower function taken from strconv.atoi
+func lower(c byte) byte {
+	return c | ('x' - 'X')
+}
+
+// UnescapeName unescapes the incoming name according to the provided escaping
+// scheme if possible. Some schemes are partially or totally non-roundtripable.
+// If any error is enountered, returns the original input.
+func UnescapeName(name string, scheme EscapingScheme) string {
+	if len(name) == 0 {
+		return name
+	}
+	switch scheme {
+	case NoEscaping:
+		return name
+	case UnderscoreEscaping:
+		// It is not possible to unescape from underscore replacement.
+		return name
+	case DotsEscaping:
+		name = strings.ReplaceAll(name, "_dot_", ".")
+		name = strings.ReplaceAll(name, "__", "_")
+		return name
+	case ValueEncodingEscaping:
+		escapedName, found := strings.CutPrefix(name, "U__")
+		if !found {
+			return name
+		}
+
+		var unescaped strings.Builder
+	TOP:
+		for i := 0; i < len(escapedName); i++ {
+			// All non-underscores are treated normally.
+			if escapedName[i] != '_' {
+				unescaped.WriteByte(escapedName[i])
+				continue
+			}
+			i++
+			if i >= len(escapedName) {
+				return name
+			}
+			// A double underscore is a single underscore.
+			if escapedName[i] == '_' {
+				unescaped.WriteByte('_')
+				continue
+			}
+			// 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 {
+					return name
+				}
+				// Found a closing underscore, convert to a rune, check validity, and append.
+				if escapedName[i] == '_' {
+					utf8Rune := rune(utf8Val)
+					if !utf8.ValidRune(utf8Rune) {
+						return name
+					}
+					unescaped.WriteRune(utf8Rune)
+					continue TOP
+				}
+				r := lower(escapedName[i])
+				utf8Val *= 16
+				if r >= '0' && r <= '9' {
+					utf8Val += uint(r) - '0'
+				} else if r >= 'a' && r <= 'f' {
+					utf8Val += uint(r) - 'a' + 10
+				} else {
+					return name
+				}
+				i++
+			}
+			// Didn't find closing underscore, invalid.
+			return name
+		}
+		return unescaped.String()
+	default:
+		panic(fmt.Sprintf("invalid escaping scheme %d", scheme))
+	}
+}
+
+func isValidLegacyRune(b rune, i int) bool {
+	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)
+}
+
+func (e EscapingScheme) String() string {
+	switch e {
+	case NoEscaping:
+		return AllowUTF8
+	case UnderscoreEscaping:
+		return EscapeUnderscores
+	case DotsEscaping:
+		return EscapeDots
+	case ValueEncodingEscaping:
+		return EscapeValues
+	default:
+		panic(fmt.Sprintf("unknown format scheme %d", e))
+	}
+}
+
+func ToEscapingScheme(s string) (EscapingScheme, error) {
+	if s == "" {
+		return NoEscaping, fmt.Errorf("got empty string instead of escaping scheme")
+	}
+	switch s {
+	case AllowUTF8:
+		return NoEscaping, nil
+	case EscapeUnderscores:
+		return UnderscoreEscaping, nil
+	case EscapeDots:
+		return DotsEscaping, nil
+	case EscapeValues:
+		return ValueEncodingEscaping, nil
+	default:
+		return NoEscaping, fmt.Errorf("unknown format scheme " + s)
+	}
+}
diff --git a/model/signature.go b/model/signature.go
index 8762b13..dc8a002 100644
--- a/model/signature.go
+++ b/model/signature.go
@@ -22,10 +22,8 @@ import (
 // when calculating their combined hash value (aka signature aka fingerprint).
 const SeparatorByte byte = 255
 
-var (
-	// cache the signature of an empty label set.
-	emptyLabelSignature = hashNew()
-)
+// cache the signature of an empty label set.
+var emptyLabelSignature = hashNew()
 
 // LabelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a
 // given label set. (Collisions are possible but unlikely if the number of label
diff --git a/model/silence.go b/model/silence.go
index bb99889..910b0b7 100644
--- a/model/silence.go
+++ b/model/silence.go
@@ -81,7 +81,7 @@ func (s *Silence) Validate() error {
 	}
 	for _, m := range s.Matchers {
 		if err := m.Validate(); err != nil {
-			return fmt.Errorf("invalid matcher: %s", err)
+			return fmt.Errorf("invalid matcher: %w", err)
 		}
 	}
 	if s.StartsAt.IsZero() {
diff --git a/model/value.go b/model/value.go
index 9eb4404..8050637 100644
--- a/model/value.go
+++ b/model/value.go
@@ -21,14 +21,12 @@ import (
 	"strings"
 )
 
-var (
-	// ZeroSample is the pseudo zero-value of Sample used to signal a
-	// non-existing sample. It is a Sample with timestamp Earliest, value 0.0,
-	// and metric nil. Note that the natural zero value of Sample has a timestamp
-	// of 0, which is possible to appear in a real Sample and thus not suitable
-	// to signal a non-existing Sample.
-	ZeroSample = Sample{Timestamp: Earliest}
-)
+// ZeroSample is the pseudo zero-value of Sample used to signal a
+// non-existing sample. It is a Sample with timestamp Earliest, value 0.0,
+// and metric nil. Note that the natural zero value of Sample has a timestamp
+// of 0, which is possible to appear in a real Sample and thus not suitable
+// to signal a non-existing Sample.
+var ZeroSample = Sample{Timestamp: Earliest}
 
 // Sample is a sample pair associated with a metric. A single sample must either
 // define Value or Histogram but not both. Histogram == nil implies the Value
@@ -274,7 +272,7 @@ func (s *Scalar) UnmarshalJSON(b []byte) error {
 
 	value, err := strconv.ParseFloat(f, 64)
 	if err != nil {
-		return fmt.Errorf("error parsing sample value: %s", err)
+		return fmt.Errorf("error parsing sample value: %w", err)
 	}
 	s.Value = SampleValue(value)
 	return nil
diff --git a/model/value_float.go b/model/value_float.go
index 0f615a7..ae35cc2 100644
--- a/model/value_float.go
+++ b/model/value_float.go
@@ -20,14 +20,12 @@ import (
 	"strconv"
 )
 
-var (
-	// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a
-	// non-existing sample pair. It is a SamplePair with timestamp Earliest and
-	// value 0.0. Note that the natural zero value of SamplePair has a timestamp
-	// of 0, which is possible to appear in a real SamplePair and thus not
-	// suitable to signal a non-existing SamplePair.
-	ZeroSamplePair = SamplePair{Timestamp: Earliest}
-)
+// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a
+// non-existing sample pair. It is a SamplePair with timestamp Earliest and
+// value 0.0. Note that the natural zero value of SamplePair has a timestamp
+// of 0, which is possible to appear in a real SamplePair and thus not
+// suitable to signal a non-existing SamplePair.
+var ZeroSamplePair = SamplePair{Timestamp: Earliest}
 
 // A SampleValue is a representation of a value for a given sample at a given
 // time.
diff --git a/promlog/flag/flag.go b/promlog/flag/flag.go
index c25d818..e045b97 100644
--- a/promlog/flag/flag.go
+++ b/promlog/flag/flag.go
@@ -14,7 +14,10 @@
 package flag
 
 import (
+	"strings"
+
 	kingpin "github.com/alecthomas/kingpin/v2"
+
 	"github.com/prometheus/common/promlog"
 )
 
@@ -23,23 +26,25 @@ import (
 const LevelFlagName = "log.level"
 
 // LevelFlagHelp is the help description for the log.level flag.
-const LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]"
+var LevelFlagHelp = "Only log messages with the given severity or above. One of: [" + strings.Join(promlog.LevelFlagOptions, ", ") + "]"
 
 // FormatFlagName is the canonical flag name to configure the log format
 // within Prometheus projects.
 const FormatFlagName = "log.format"
 
 // FormatFlagHelp is the help description for the log.format flag.
-const FormatFlagHelp = "Output format of log messages. One of: [logfmt, json]"
+var FormatFlagHelp = "Output format of log messages. One of: [" + strings.Join(promlog.FormatFlagOptions, ", ") + "]"
 
 // 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 *promlog.Config) {
 	config.Level = &promlog.AllowedLevel{}
 	a.Flag(LevelFlagName, LevelFlagHelp).
-		Default("info").SetValue(config.Level)
+		Default("info").HintOptions(promlog.LevelFlagOptions...).
+		SetValue(config.Level)
 
 	config.Format = &promlog.AllowedFormat{}
 	a.Flag(FormatFlagName, FormatFlagHelp).
-		Default("logfmt").SetValue(config.Format)
+		Default("logfmt").HintOptions(promlog.FormatFlagOptions...).
+		SetValue(config.Format)
 }
diff --git a/promlog/log.go b/promlog/log.go
index 3ac7b3f..2449c90 100644
--- a/promlog/log.go
+++ b/promlog/log.go
@@ -34,6 +34,9 @@ var (
 		func() time.Time { return time.Now().UTC() },
 		"2006-01-02T15:04:05.000Z07:00",
 	)
+
+	LevelFlagOptions  = []string{"debug", "info", "warn", "error"}
+	FormatFlagOptions = []string{"logfmt, json"}
 )
 
 // AllowedLevel is a settable identifier for the minimum level a log entry
diff --git a/sigv4/go.mod b/sigv4/go.mod
index a111689..cd07193 100644
--- a/sigv4/go.mod
+++ b/sigv4/go.mod
@@ -1,13 +1,13 @@
 module github.com/prometheus/common/sigv4
 
-go 1.20
+go 1.21
 
 replace github.com/prometheus/common => ../
 
 require (
-	github.com/aws/aws-sdk-go v1.47.0
-	github.com/prometheus/client_golang v1.17.0
-	github.com/prometheus/common v0.44.0
+	github.com/aws/aws-sdk-go v1.50.27
+	github.com/prometheus/client_golang v1.19.0
+	github.com/prometheus/common v0.48.0
 	github.com/stretchr/testify v1.8.4
 	gopkg.in/yaml.v2 v2.4.0
 )
@@ -20,16 +20,15 @@ require (
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jpillora/backoff v1.0.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
 	github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/procfs v0.11.1 // indirect
-	golang.org/x/net v0.18.0 // indirect
-	golang.org/x/oauth2 v0.14.0 // indirect
-	golang.org/x/sys v0.14.0 // indirect
+	github.com/prometheus/client_model v0.6.0 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/oauth2 v0.17.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/protobuf v1.31.0 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/sigv4/go.sum b/sigv4/go.sum
index 51fb9ba..355d3f4 100644
--- a/sigv4/go.sum
+++ b/sigv4/go.sum
@@ -1,5 +1,5 @@
-github.com/aws/aws-sdk-go v1.47.0 h1:/JUg9V1+xh+qBn8A6ec/l15ETPaMaBqxkjz+gg63dNk=
-github.com/aws/aws-sdk-go v1.47.0/go.mod h1:DlEaEbWKZmsITVbqlSVvekPARM1HzeV9PMYg15ymSDA=
+github.com/aws/aws-sdk-go v1.50.27 h1:96ifhrSuja+AzdP3W/T2337igqVQ2FcSIJYkk+0rCeA=
+github.com/aws/aws-sdk-go v1.50.27/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -13,7 +13,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -21,33 +22,33 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
 github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 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/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
-github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
-github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
-github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
-github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
+github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
+github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
+github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
+github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
-golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
-golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
-golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
@@ -58,10 +59,11 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 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=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/version/info.go b/version/info.go
index 00caa0b..6b526d9 100644
--- a/version/info.go
+++ b/version/info.go
@@ -35,6 +35,8 @@ var (
 	GoArch    = runtime.GOARCH
 )
 
+// Deprecated: Use github.com/prometheus/client_golang/prometheus/collectors/version.NewCollector instead.
+//
 // NewCollector returns a collector that exports metrics about current version
 // information.
 func NewCollector(program string) prometheus.Collector {
@@ -48,12 +50,12 @@ func NewCollector(program string) prometheus.Collector {
 			),
 			ConstLabels: prometheus.Labels{
 				"version":   Version,
-				"revision":  getRevision(),
+				"revision":  GetRevision(),
 				"branch":    Branch,
 				"goversion": GoVersion,
 				"goos":      GoOS,
 				"goarch":    GoArch,
-				"tags":      getTags(),
+				"tags":      GetTags(),
 			},
 		},
 		func() float64 { return 1 },
@@ -75,13 +77,13 @@ func Print(program string) string {
 	m := map[string]string{
 		"program":   program,
 		"version":   Version,
-		"revision":  getRevision(),
+		"revision":  GetRevision(),
 		"branch":    Branch,
 		"buildUser": BuildUser,
 		"buildDate": BuildDate,
 		"goVersion": GoVersion,
 		"platform":  GoOS + "/" + GoArch,
-		"tags":      getTags(),
+		"tags":      GetTags(),
 	}
 	t := template.Must(template.New("version").Parse(versionInfoTmpl))
 
@@ -94,10 +96,10 @@ func Print(program string) string {
 
 // Info returns version, branch and revision information.
 func Info() string {
-	return fmt.Sprintf("(version=%s, branch=%s, revision=%s)", Version, Branch, getRevision())
+	return fmt.Sprintf("(version=%s, branch=%s, revision=%s)", Version, Branch, GetRevision())
 }
 
 // BuildContext returns goVersion, platform, buildUser and buildDate information.
 func BuildContext() string {
-	return fmt.Sprintf("(go=%s, platform=%s, user=%s, date=%s, tags=%s)", GoVersion, GoOS+"/"+GoArch, BuildUser, BuildDate, getTags())
+	return fmt.Sprintf("(go=%s, platform=%s, user=%s, date=%s, tags=%s)", GoVersion, GoOS+"/"+GoArch, BuildUser, BuildDate, GetTags())
 }
diff --git a/version/info_default.go b/version/info_default.go
index 8eb3a0b..684996f 100644
--- a/version/info_default.go
+++ b/version/info_default.go
@@ -16,7 +16,7 @@
 
 package version
 
-func getRevision() string {
+func GetRevision() string {
 	return Revision
 }
 
diff --git a/version/info_go118.go b/version/info_go118.go
index bfc7d41..992623c 100644
--- a/version/info_go118.go
+++ b/version/info_go118.go
@@ -18,17 +18,19 @@ package version
 
 import "runtime/debug"
 
-var computedRevision string
-var computedTags string
+var (
+	computedRevision string
+	computedTags     string
+)
 
-func getRevision() string {
+func GetRevision() string {
 	if Revision != "" {
 		return Revision
 	}
 	return computedRevision
 }
 
-func getTags() string {
+func GetTags() string {
 	return computedTags
 }
 


Test patch:
diff --git a/config/http_config_test.go b/config/http_config_test.go
index 72d1e79..cd13a18 100644
--- a/config/http_config_test.go
+++ b/config/http_config_test.go
@@ -35,7 +35,7 @@ import (
 	"testing"
 	"time"
 
-	yaml "gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v2"
 )
 
 const (
@@ -115,10 +115,6 @@ var invalidHTTPClientConfigs = []struct {
 		httpClientConfigFile: "testdata/http.conf.oauth2-no-client-id.bad.yaml",
 		errMsg:               "oauth2 client_id must be configured",
 	},
-	{
-		httpClientConfigFile: "testdata/http.conf.oauth2-no-client-secret.bad.yaml",
-		errMsg:               "either oauth2 client_secret or client_secret_file must be configured",
-	},
 	{
 		httpClientConfigFile: "testdata/http.conf.oauth2-no-token-url.bad.yaml",
 		errMsg:               "oauth2 token_url must be configured",
@@ -156,7 +152,8 @@ func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httpt
 		Certificates: make([]tls.Certificate, 1),
 		RootCAs:      rootCAs,
 		ClientAuth:   tls.RequireAndVerifyClientCert,
-		ClientCAs:    rootCAs}
+		ClientCAs:    rootCAs,
+	}
 	testServer.TLS.Certificates[0] = serverCertificate
 
 	testServer.StartTLS()
@@ -165,7 +162,7 @@ func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httpt
 }
 
 func TestNewClientFromConfig(t *testing.T) {
-	var newClientValidConfig = []struct {
+	newClientValidConfig := []struct {
 		clientConfig HTTPClientConfig
 		handler      func(w http.ResponseWriter, r *http.Request)
 	}{
@@ -176,24 +173,28 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: true},
+					InsecureSkipVerify: true,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				fmt.Fprint(w, ExpectedMessage)
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				TLSConfig: TLSConfig{
 					CAFile:             TLSCAChainPath,
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				fmt.Fprint(w, ExpectedMessage)
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				BearerToken: BearerToken,
 				TLSConfig: TLSConfig{
@@ -201,7 +202,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -212,7 +214,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				BearerTokenFile: BearerTokenFile,
 				TLSConfig: TLSConfig{
@@ -220,7 +223,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -231,7 +235,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				Authorization: &Authorization{Credentials: BearerToken},
 				TLSConfig: TLSConfig{
@@ -239,7 +244,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -250,7 +256,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				Authorization: &Authorization{CredentialsFile: AuthorizationCredentialsFile, Type: AuthorizationType},
 				TLSConfig: TLSConfig{
@@ -258,7 +265,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -269,7 +277,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				Authorization: &Authorization{
 					Type: AuthorizationType,
@@ -279,7 +288,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -290,7 +300,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				Authorization: &Authorization{
 					Credentials: AuthorizationCredentials,
@@ -301,7 +312,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -312,7 +324,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				Authorization: &Authorization{
 					CredentialsFile: BearerTokenFile,
@@ -322,7 +335,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				bearer := r.Header.Get("Authorization")
@@ -333,7 +347,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				BasicAuth: &BasicAuth{
 					Username: ExpectedUsername,
@@ -344,7 +359,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				username, password, ok := r.BasicAuth()
@@ -358,7 +374,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, ExpectedMessage)
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				FollowRedirects: true,
 				TLSConfig: TLSConfig{
@@ -366,7 +383,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				switch r.URL.Path {
@@ -378,7 +396,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					fmt.Fprint(w, "It should follow the redirect.")
 				}
 			},
-		}, {
+		},
+		{
 			clientConfig: HTTPClientConfig{
 				FollowRedirects: false,
 				TLSConfig: TLSConfig{
@@ -386,7 +405,8 @@ func TestNewClientFromConfig(t *testing.T) {
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				switch r.URL.Path {
@@ -399,6 +419,46 @@ func TestNewClientFromConfig(t *testing.T) {
 				}
 			},
 		},
+		{
+			clientConfig: HTTPClientConfig{
+				OAuth2: &OAuth2{
+					ClientID: "ExpectedUsername",
+					TLSConfig: TLSConfig{
+						CAFile:             TLSCAChainPath,
+						CertFile:           ClientCertificatePath,
+						KeyFile:            ClientKeyNoPassPath,
+						ServerName:         "",
+						InsecureSkipVerify: false,
+					},
+				},
+				TLSConfig: TLSConfig{
+					CAFile:             TLSCAChainPath,
+					CertFile:           ClientCertificatePath,
+					KeyFile:            ClientKeyNoPassPath,
+					ServerName:         "",
+					InsecureSkipVerify: false,
+				},
+			},
+			handler: func(w http.ResponseWriter, r *http.Request) {
+				switch r.URL.Path {
+				case "/token":
+					res, _ := json.Marshal(oauth2TestServerResponse{
+						AccessToken: ExpectedAccessToken,
+						TokenType:   "Bearer",
+					})
+					w.Header().Add("Content-Type", "application/json")
+					_, _ = w.Write(res)
+
+				default:
+					authorization := r.Header.Get("Authorization")
+					if authorization != "Bearer "+ExpectedAccessToken {
+						fmt.Fprintf(w, "Expected Authorization header %q, got %q", "Bearer "+ExpectedAccessToken, authorization)
+					} else {
+						fmt.Fprint(w, ExpectedMessage)
+					}
+				}
+			},
+		},
 		{
 			clientConfig: HTTPClientConfig{
 				OAuth2: &OAuth2{
@@ -409,14 +469,16 @@ func TestNewClientFromConfig(t *testing.T) {
 						CertFile:           ClientCertificatePath,
 						KeyFile:            ClientKeyNoPassPath,
 						ServerName:         "",
-						InsecureSkipVerify: false},
+						InsecureSkipVerify: false,
+					},
 				},
 				TLSConfig: TLSConfig{
 					CAFile:             TLSCAChainPath,
 					CertFile:           ClientCertificatePath,
 					KeyFile:            ClientKeyNoPassPath,
 					ServerName:         "",
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			},
 			handler: func(w http.ResponseWriter, r *http.Request) {
 				switch r.URL.Path {
@@ -529,7 +591,7 @@ func TestProxyConfiguration(t *testing.T) {
 }
 
 func TestNewClientFromInvalidConfig(t *testing.T) {
-	var newClientInvalidConfig = []struct {
+	newClientInvalidConfig := []struct {
 		clientConfig HTTPClientConfig
 		errorMsg     string
 	}{
@@ -537,7 +599,8 @@ func TestNewClientFromInvalidConfig(t *testing.T) {
 			clientConfig: HTTPClientConfig{
 				TLSConfig: TLSConfig{
 					CAFile:             MissingCA,
-					InsecureSkipVerify: true},
+					InsecureSkipVerify: true,
+				},
 			},
 			errorMsg: fmt.Sprintf("unable to load specified CA cert %s:", MissingCA),
 		},
@@ -545,7 +608,8 @@ func TestNewClientFromInvalidConfig(t *testing.T) {
 			clientConfig: HTTPClientConfig{
 				TLSConfig: TLSConfig{
 					CAFile:             InvalidCA,
-					InsecureSkipVerify: true},
+					InsecureSkipVerify: true,
+				},
 			},
 			errorMsg: fmt.Sprintf("unable to use specified CA cert %s", InvalidCA),
 		},
@@ -609,7 +673,8 @@ func TestMissingBearerAuthFile(t *testing.T) {
 			CertFile:           ClientCertificatePath,
 			KeyFile:            ClientKeyNoPassPath,
 			ServerName:         "",
-			InsecureSkipVerify: false},
+			InsecureSkipVerify: false,
+		},
 	}
 	handler := func(w http.ResponseWriter, r *http.Request) {
 		bearer := r.Header.Get("Authorization")
@@ -780,7 +845,7 @@ func TestTLSConfigEmpty(t *testing.T) {
 }
 
 func TestTLSConfigInvalidCA(t *testing.T) {
-	var invalidTLSConfig = []struct {
+	invalidTLSConfig := []struct {
 		configTLSConfig TLSConfig
 		errorMessage    string
 	}{
@@ -790,23 +855,28 @@ func TestTLSConfigInvalidCA(t *testing.T) {
 				CertFile:           "",
 				KeyFile:            "",
 				ServerName:         "",
-				InsecureSkipVerify: false},
+				InsecureSkipVerify: false,
+			},
 			errorMessage: fmt.Sprintf("unable to load specified CA cert %s:", MissingCA),
-		}, {
+		},
+		{
 			configTLSConfig: TLSConfig{
 				CAFile:             "",
 				CertFile:           MissingCert,
 				KeyFile:            ClientKeyNoPassPath,
 				ServerName:         "",
-				InsecureSkipVerify: false},
+				InsecureSkipVerify: false,
+			},
 			errorMessage: fmt.Sprintf("unable to read specified client cert (%s):", MissingCert),
-		}, {
+		},
+		{
 			configTLSConfig: TLSConfig{
 				CAFile:             "",
 				CertFile:           ClientCertificatePath,
 				KeyFile:            MissingKey,
 				ServerName:         "",
-				InsecureSkipVerify: false},
+				InsecureSkipVerify: false,
+			},
 			errorMessage: fmt.Sprintf("unable to read specified client key (%s):", MissingKey),
 		},
 		{
@@ -816,7 +886,8 @@ func TestTLSConfigInvalidCA(t *testing.T) {
 				CertFile:           ClientCertificatePath,
 				KeyFile:            ClientKeyNoPassPath,
 				ServerName:         "",
-				InsecureSkipVerify: false},
+				InsecureSkipVerify: false,
+			},
 			errorMessage: "at most one of cert and cert_file must be configured",
 		},
 		{
@@ -826,7 +897,8 @@ func TestTLSConfigInvalidCA(t *testing.T) {
 				Key:                Secret(readFile(t, ClientKeyNoPassPath)),
 				KeyFile:            ClientKeyNoPassPath,
 				ServerName:         "",
-				InsecureSkipVerify: false},
+				InsecureSkipVerify: false,
+			},
 			errorMessage: "at most one of key and key_file must be configured",
 		},
 	}
@@ -975,7 +1047,7 @@ func writeCertificate(bs map[string][]byte, src string, dst string) {
 	if !ok {
 		panic(fmt.Sprintf("Couldn't find %q in bs", src))
 	}
-	if err := os.WriteFile(dst, b, 0664); err != nil {
+	if err := os.WriteFile(dst, b, 0o664); err != nil {
 		panic(err)
 	}
 }
@@ -1066,7 +1138,8 @@ func TestTLSRoundTripper(t *testing.T) {
 			CAFile:             ca,
 			CertFile:           cert,
 			KeyFile:            key,
-			InsecureSkipVerify: false},
+			InsecureSkipVerify: false,
+		},
 	}
 
 	var c *http.Client
@@ -1202,7 +1275,8 @@ func TestTLSRoundTripper_Inline(t *testing.T) {
 					CertFile:           tc.certFile,
 					Key:                Secret(tc.keyText),
 					KeyFile:            tc.keyFile,
-					InsecureSkipVerify: false},
+					InsecureSkipVerify: false,
+				},
 			}
 
 			c, err := NewClientFromConfig(cfg, "test")
@@ -1263,7 +1337,8 @@ func TestTLSRoundTripperRaces(t *testing.T) {
 			CAFile:             ca,
 			CertFile:           cert,
 			KeyFile:            key,
-			InsecureSkipVerify: false},
+			InsecureSkipVerify: false,
+		},
 	}
 
 	var c *http.Client
@@ -1399,7 +1474,9 @@ func NewRoundTripCheckRequest(checkRequest func(*http.Request), theResponse *htt
 		checkRequest: checkRequest,
 		roundTrip: roundTrip{
 			theResponse: theResponse,
-			theError:    theError}}
+			theError:    theError,
+		},
+	}
 }
 
 type oauth2TestServerResponse struct {
@@ -1407,8 +1484,23 @@ type oauth2TestServerResponse struct {
 	TokenType   string `json:"token_type"`
 }
 
-func TestOAuth2(t *testing.T) {
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+type testOAuthServer struct {
+	tokenTS *httptest.Server
+	ts      *httptest.Server
+}
+
+// newTestOAuthServer returns a new test server with the expected base64 encoded client ID and secret.
+func newTestOAuthServer(t testing.TB, expectedAuth *string) testOAuthServer {
+	var previousAuth string
+	tokenTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		auth := r.Header.Get("Authorization")
+		if auth != *expectedAuth {
+			t.Fatalf("bad auth, expected %s, got %s", *expectedAuth, auth)
+		}
+		if auth == previousAuth {
+			t.Fatal("token endpoint called twice")
+		}
+		previousAuth = auth
 		res, _ := json.Marshal(oauth2TestServerResponse{
 			AccessToken: "12345",
 			TokenType:   "Bearer",
@@ -1416,29 +1508,57 @@ func TestOAuth2(t *testing.T) {
 		w.Header().Add("Content-Type", "application/json")
 		_, _ = w.Write(res)
 	}))
-	defer ts.Close()
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		auth := r.Header.Get("Authorization")
+		if auth != "Bearer 12345" {
+			t.Fatalf("bad auth, expected %s, got %s", "Bearer 12345", auth)
+		}
+		fmt.Fprintln(w, "Hello, client")
+	}))
+	return testOAuthServer{
+		tokenTS: tokenTS,
+		ts:      ts,
+	}
+}
+
+func (s *testOAuthServer) url() string {
+	return s.ts.URL
+}
+
+func (s *testOAuthServer) tokenURL() string {
+	return s.tokenTS.URL
+}
+
+func (s *testOAuthServer) close() {
+	s.tokenTS.Close()
+	s.ts.Close()
+}
 
-	var yamlConfig = fmt.Sprintf(`
+func TestOAuth2(t *testing.T) {
+	var expectedAuth string
+	ts := newTestOAuthServer(t, &expectedAuth)
+	defer ts.close()
+
+	yamlConfig := fmt.Sprintf(`
 client_id: 1
 client_secret: 2
 scopes:
  - A
  - B
-token_url: %s/token
+token_url: %s
 endpoint_params:
  hi: hello
-`, ts.URL)
+`, ts.tokenURL())
 	expectedConfig := OAuth2{
 		ClientID:       "1",
 		ClientSecret:   "2",
 		Scopes:         []string{"A", "B"},
 		EndpointParams: map[string]string{"hi": "hello"},
-		TokenURL:       fmt.Sprintf("%s/token", ts.URL),
+		TokenURL:       ts.tokenURL(),
 	}
 
 	var unmarshalledConfig OAuth2
-	err := yaml.Unmarshal([]byte(yamlConfig), &unmarshalledConfig)
-	if err != nil {
+	if err := yaml.Unmarshal([]byte(yamlConfig), &unmarshalledConfig); err != nil {
 		t.Fatalf("Expected no error unmarshalling yaml, got %v", err)
 	}
 	if !reflect.DeepEqual(unmarshalledConfig, expectedConfig) {
@@ -1450,9 +1570,59 @@ endpoint_params:
 	client := http.Client{
 		Transport: rt,
 	}
-	resp, _ := client.Get(ts.URL)
+
+	// Default secret.
+	expectedAuth = "Basic MToy"
+	resp, err := client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	authorization := resp.Request.Header.Get("Authorization")
+	if authorization != "Bearer 12345" {
+		t.Fatalf("Expected authorization header to be 'Bearer', got '%s'", authorization)
+	}
+
+	// Making a second request with the same secret should not re-call the token API.
+	_, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Empty secret.
+	expectedAuth = "Basic MTo="
+	expectedConfig.ClientSecret = ""
+	resp, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	authorization = resp.Request.Header.Get("Authorization")
+	if authorization != "Bearer 12345" {
+		t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
+	}
+
+	// Making a second request with the same secret should not re-call the token API.
+	resp, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Update secret.
+	expectedAuth = "Basic MToxMjM0NTY3"
+	expectedConfig.ClientSecret = "1234567"
+	_, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Making a second request with the same secret should not re-call the token API.
+	_, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	authorization = resp.Request.Header.Get("Authorization")
 	if authorization != "Bearer 12345" {
 		t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
 	}
@@ -1501,42 +1671,44 @@ func TestOAuth2UserAgent(t *testing.T) {
 	}
 }
 
-func TestOAuth2WithFile(t *testing.T) {
-	var expectedAuth *string
-	var previousAuth string
-	tokenTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		auth := r.Header.Get("Authorization")
-		if auth != *expectedAuth {
-			t.Fatalf("bad auth, expected %s, got %s", *expectedAuth, auth)
-		}
-		if auth == previousAuth {
-			t.Fatal("token endpoint called twice")
-		}
-		previousAuth = auth
-		res, _ := json.Marshal(oauth2TestServerResponse{
-			AccessToken: "12345",
-			TokenType:   "Bearer",
-		})
-		w.Header().Add("Content-Type", "application/json")
-		_, _ = w.Write(res)
-	}))
-	defer tokenTS.Close()
+func TestHost(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		auth := r.Header.Get("Authorization")
-		if auth != "Bearer 12345" {
-			t.Fatalf("bad auth, expected %s, got %s", "Bearer 12345", auth)
+		if r.Host != "localhost.localdomain" {
+			t.Fatalf("Expected Host header in request to be 'localhost.localdomain', got '%s'", r.Host)
 		}
-		fmt.Fprintln(w, "Hello, client")
+
+		w.Header().Add("Content-Type", "application/json")
 	}))
 	defer ts.Close()
 
+	config := DefaultHTTPClientConfig
+
+	rt, err := NewRoundTripperFromConfig(config, "test_host", WithHost("localhost.localdomain"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client := http.Client{
+		Transport: rt,
+	}
+	_, err = client.Get(ts.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestOAuth2WithFile(t *testing.T) {
+	var expectedAuth string
+	ts := newTestOAuthServer(t, &expectedAuth)
+	defer ts.close()
+
 	secretFile, err := os.CreateTemp("", "oauth2_secret")
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer os.Remove(secretFile.Name())
 
-	var yamlConfig = fmt.Sprintf(`
+	yamlConfig := fmt.Sprintf(`
 client_id: 1
 client_secret_file: %s
 scopes:
@@ -1545,13 +1717,13 @@ scopes:
 token_url: %s
 endpoint_params:
  hi: hello
-`, secretFile.Name(), tokenTS.URL)
+`, secretFile.Name(), ts.tokenURL())
 	expectedConfig := OAuth2{
 		ClientID:         "1",
 		ClientSecretFile: secretFile.Name(),
 		Scopes:           []string{"A", "B"},
 		EndpointParams:   map[string]string{"hi": "hello"},
-		TokenURL:         tokenTS.URL,
+		TokenURL:         ts.tokenURL(),
 	}
 
 	var unmarshalledConfig OAuth2
@@ -1569,40 +1741,57 @@ endpoint_params:
 		Transport: rt,
 	}
 
-	tk := "Basic MToxMjM0NTY="
-	expectedAuth = &tk
+	// Empty secret file.
+	expectedAuth = "Basic MTo="
+	resp, err := client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	authorization := resp.Request.Header.Get("Authorization")
+	if authorization != "Bearer 12345" {
+		t.Fatalf("Expected authorization header to be 'Bearer', got '%s'", authorization)
+	}
+
+	// Making a second request with the same file content should not re-call the token API.
+	_, err = client.Get(ts.url())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// File populated.
+	expectedAuth = "Basic MToxMjM0NTY="
 	if _, err := secretFile.Write([]byte("123456")); err != nil {
 		t.Fatal(err)
 	}
-	resp, err := client.Get(ts.URL)
+	resp, err = client.Get(ts.url())
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	authorization := resp.Request.Header.Get("Authorization")
+	authorization = resp.Request.Header.Get("Authorization")
 	if authorization != "Bearer 12345" {
 		t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
 	}
 
 	// Making a second request with the same file content should not re-call the token API.
-	resp, err = client.Get(ts.URL)
+	resp, err = client.Get(ts.url())
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	tk = "Basic MToxMjM0NTY3"
-	expectedAuth = &tk
+	// Update file.
+	expectedAuth = "Basic MToxMjM0NTY3"
 	if _, err := secretFile.Write([]byte("7")); err != nil {
 		t.Fatal(err)
 	}
-
-	_, err = client.Get(ts.URL)
+	_, err = client.Get(ts.url())
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Making a second request with the same file content should not re-call the token API.
-	_, err = client.Get(ts.URL)
+	_, err = client.Get(ts.url())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1820,7 +2009,8 @@ func TestModifyTLSCertificates(t *testing.T) {
 			CAFile:             ca,
 			CertFile:           cert,
 			KeyFile:            key,
-			InsecureSkipVerify: false},
+			InsecureSkipVerify: false,
+		},
 	}
 
 	var c *http.Client
@@ -2011,7 +2201,6 @@ no_proxy: promcon.io,cncf.io`, proxyServer.URL),
 
 			proxyFunc := proxyConfig.Proxy()
 			resultURL, err := proxyFunc(req)
-
 			if err != nil {
 				t.Fatalf("expected no error, but got: %v", err)
 				return
diff --git a/config/testdata/http.conf.oauth2-no-client-secret.bad.yaml b/config/testdata/http.conf.oauth2-no-client-secret.bad.yaml
deleted file mode 100644
index 774a599..0000000
--- a/config/testdata/http.conf.oauth2-no-client-secret.bad.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-oauth2:
-  client_id: "myclientid"
-  token_url: "http://auth"
diff --git a/config/tls_config_test.go b/config/tls_config_test.go
index a1fcda7..150c561 100644
--- a/config/tls_config_test.go
+++ b/config/tls_config_test.go
@@ -16,6 +16,7 @@ package config
 import (
 	"bytes"
 	"crypto/tls"
+	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -23,8 +24,6 @@ import (
 	"strings"
 	"testing"
 
-	"encoding/json"
-
 	"gopkg.in/yaml.v2"
 )
 
@@ -59,32 +58,40 @@ var expectedTLSConfigs = []struct {
 	{
 		filename: "tls_config.empty.good.json",
 		config:   &tls.Config{},
-	}, {
+	},
+	{
 		filename: "tls_config.insecure.good.json",
 		config:   &tls.Config{InsecureSkipVerify: true},
-	}, {
+	},
+	{
 		filename: "tls_config.tlsversion.good.json",
 		config:   &tls.Config{MinVersion: tls.VersionTLS11},
-	}, {
+	},
+	{
 		filename: "tls_config.max_version.good.json",
 		config:   &tls.Config{MaxVersion: tls.VersionTLS12},
 	},
 	{
 		filename: "tls_config.empty.good.yml",
 		config:   &tls.Config{},
-	}, {
+	},
+	{
 		filename: "tls_config.insecure.good.yml",
 		config:   &tls.Config{InsecureSkipVerify: true},
-	}, {
+	},
+	{
 		filename: "tls_config.tlsversion.good.yml",
 		config:   &tls.Config{MinVersion: tls.VersionTLS11},
-	}, {
+	},
+	{
 		filename: "tls_config.max_version.good.yml",
 		config:   &tls.Config{MaxVersion: tls.VersionTLS12},
-	}, {
+	},
+	{
 		filename: "tls_config.max_and_min_version.good.yml",
 		config:   &tls.Config{MaxVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS11},
-	}, {
+	},
+	{
 		filename: "tls_config.max_and_min_version_same.good.yml",
 		config:   &tls.Config{MaxVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12},
 	},
@@ -134,7 +141,7 @@ func TestTLSVersionStringer(t *testing.T) {
 }
 
 func TestTLSVersionMarshalYAML(t *testing.T) {
-	var tests = []struct {
+	tests := []struct {
 		input    TLSVersion
 		expected string
 		err      error
@@ -174,7 +181,7 @@ func TestTLSVersionMarshalYAML(t *testing.T) {
 }
 
 func TestTLSVersionMarshalJSON(t *testing.T) {
-	var tests = []struct {
+	tests := []struct {
 		input    TLSVersion
 		expected string
 		err      error
diff --git a/expfmt/bench_test.go b/expfmt/bench_test.go
index 3d7d68e..6857f9a 100644
--- a/expfmt/bench_test.go
+++ b/expfmt/bench_test.go
@@ -14,13 +14,15 @@
 package expfmt
 
 import (
+	"bufio"
 	"bytes"
 	"compress/gzip"
+	"errors"
 	"io"
 	"os"
 	"testing"
 
-	"github.com/matttproud/golang_protobuf_extensions/v2/pbutil"
+	"google.golang.org/protobuf/encoding/protodelim"
 
 	dto "github.com/prometheus/client_model/go"
 )
@@ -97,11 +99,14 @@ func BenchmarkParseProto(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		family := &dto.MetricFamily{}
-		in := bytes.NewReader(data)
+		in := bufio.NewReader(bytes.NewReader(data))
+		unmarshaler := protodelim.UnmarshalOptions{
+			MaxSize: -1,
+		}
 		for {
 			family.Reset()
-			if _, err := pbutil.ReadDelimited(in, family); err != nil {
-				if err == io.EOF {
+			if err := unmarshaler.UnmarshalFrom(in, family); err != nil {
+				if errors.Is(err, io.EOF) {
 					break
 				}
 				b.Fatal(err)
@@ -122,14 +127,18 @@ func BenchmarkParseProtoGzip(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		family := &dto.MetricFamily{}
-		in, err := gzip.NewReader(bytes.NewReader(data))
+		gz, err := gzip.NewReader(bytes.NewReader(data))
 		if err != nil {
 			b.Fatal(err)
 		}
+		in := bufio.NewReader(gz)
+		unmarshaler := protodelim.UnmarshalOptions{
+			MaxSize: -1,
+		}
 		for {
 			family.Reset()
-			if _, err := pbutil.ReadDelimited(in, family); err != nil {
-				if err == io.EOF {
+			if err := unmarshaler.UnmarshalFrom(in, family); err != nil {
+				if errors.Is(err, io.EOF) {
 					break
 				}
 				b.Fatal(err)
@@ -152,11 +161,14 @@ func BenchmarkParseProtoMap(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		families := map[string]*dto.MetricFamily{}
-		in := bytes.NewReader(data)
+		in := bufio.NewReader(bytes.NewReader(data))
+		unmarshaler := protodelim.UnmarshalOptions{
+			MaxSize: -1,
+		}
 		for {
 			family := &dto.MetricFamily{}
-			if _, err := pbutil.ReadDelimited(in, family); err != nil {
-				if err == io.EOF {
+			if err := unmarshaler.UnmarshalFrom(in, family); err != nil {
+				if errors.Is(err, io.EOF) {
 					break
 				}
 				b.Fatal(err)
diff --git a/expfmt/decode_test.go b/expfmt/decode_test.go
index 56c8838..e5e245d 100644
--- a/expfmt/decode_test.go
+++ b/expfmt/decode_test.go
@@ -15,7 +15,9 @@ package expfmt
 
 import (
 	"bufio"
+	"errors"
 	"io"
+	"math"
 	"net/http"
 	"reflect"
 	"sort"
@@ -84,7 +86,7 @@ mf2 4
 	for {
 		var smpls model.Vector
 		err := dec.Decode(&smpls)
-		if err == io.EOF {
+		if err != nil && errors.Is(err, io.EOF) {
 			break
 		}
 		if err != nil {
@@ -100,13 +102,13 @@ mf2 4
 }
 
 func TestProtoDecoder(t *testing.T) {
-
-	var testTime = model.Now()
+	testTime := model.Now()
 
 	scenarios := []struct {
-		in       string
-		expected model.Vector
-		fail     bool
+		in             string
+		expected       model.Vector
+		legacyNameFail bool
+		fail           bool
 	}{
 		{
 			in: "",
@@ -332,6 +334,30 @@ func TestProtoDecoder(t *testing.T) {
 				},
 			},
 		},
+		{
+			in:             "\xa8\x01\n\ngauge.name\x12\x11gauge\ndoc\nstr\"ing\x18\x01\"T\n\x1b\n\x06name.1\x12\x11val with\nnew line\n*\n\x06name*2\x12 val with \\backslash and \"quotes\"\x12\t\t\x00\x00\x00\x00\x00\x00\xf0\x7f\"/\n\x10\n\x06name.1\x12\x06Björn\n\x10\n\x06name*2\x12\x06佖佥\x12\t\t\xd1\xcfD\xb9\xd0\x05\xc2H",
+			legacyNameFail: true,
+			expected: model.Vector{
+				&model.Sample{
+					Metric: model.Metric{
+						model.MetricNameLabel: "gauge.name",
+						"name.1":              "val with\nnew line",
+						"name*2":              "val with \\backslash and \"quotes\"",
+					},
+					Value:     model.SampleValue(math.Inf(+1)),
+					Timestamp: testTime,
+				},
+				&model.Sample{
+					Metric: model.Metric{
+						model.MetricNameLabel: "gauge.name",
+						"name.1":              "Björn",
+						"name*2":              "佖佥",
+					},
+					Value:     3.14e42,
+					Timestamp: testTime,
+				},
+			},
+		},
 	}
 
 	for i, scenario := range scenarios {
@@ -344,11 +370,31 @@ func TestProtoDecoder(t *testing.T) {
 
 		var all model.Vector
 		for {
+			model.NameValidationScheme = model.LegacyValidation
 			var smpls model.Vector
 			err := dec.Decode(&smpls)
-			if err == io.EOF {
+			if err != nil && errors.Is(err, io.EOF) {
 				break
 			}
+			if scenario.legacyNameFail {
+				if err == nil {
+					t.Fatal("Expected error when decoding without UTF-8 support enabled but got none")
+				}
+				model.NameValidationScheme = model.UTF8Validation
+				dec = &SampleDecoder{
+					Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
+					Opts: &DecodeOptions{
+						Timestamp: testTime,
+					},
+				}
+				err = dec.Decode(&smpls)
+				if errors.Is(err, io.EOF) {
+					break
+				}
+				if err != nil {
+					t.Fatalf("Unexpected error when decoding with UTF-8 support: %v", err)
+				}
+			}
 			if scenario.fail {
 				if err == nil {
 					t.Fatal("Expected error but got none")
@@ -369,33 +415,33 @@ func TestProtoDecoder(t *testing.T) {
 }
 
 func testDiscriminatorHTTPHeader(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		input  map[string]string
 		output Format
 	}{
 		{
 			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
-			output: FmtProtoDelim,
+			output: fmtProtoDelim,
 		},
 		{
 			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
-			output: FmtUnknown,
+			output: fmtUnknown,
 		},
 		{
 			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
-			output: FmtUnknown,
+			output: fmtUnknown,
 		},
 		{
 			input:  map[string]string{"Content-Type": `text/plain; version=0.0.4`},
-			output: FmtText,
+			output: fmtText,
 		},
 		{
 			input:  map[string]string{"Content-Type": `text/plain`},
-			output: FmtText,
+			output: fmtText,
 		},
 		{
 			input:  map[string]string{"Content-Type": `text/plain; version=0.0.3`},
-			output: FmtUnknown,
+			output: fmtUnknown,
 		},
 	}
 
@@ -435,7 +481,7 @@ func TestExtractSamples(t *testing.T) {
 			Help: proto.String("Help for foo."),
 			Type: dto.MetricType_COUNTER.Enum(),
 			Metric: []*dto.Metric{
-				&dto.Metric{
+				{
 					Counter: &dto.Counter{
 						Value: proto.Float64(4711),
 					},
@@ -447,7 +493,7 @@ func TestExtractSamples(t *testing.T) {
 			Help: proto.String("Help for bar."),
 			Type: dto.MetricType_GAUGE.Enum(),
 			Metric: []*dto.Metric{
-				&dto.Metric{
+				{
 					Gauge: &dto.Gauge{
 						Value: proto.Float64(3.14),
 					},
@@ -459,7 +505,7 @@ func TestExtractSamples(t *testing.T) {
 			Help: proto.String("Help for bad."),
 			Type: dto.MetricType(42).Enum(),
 			Metric: []*dto.Metric{
-				&dto.Metric{
+				{
 					Gauge: &dto.Gauge{
 						Value: proto.Float64(2.7),
 					},
@@ -501,11 +547,11 @@ func TestTextDecoderWithBufioReader(t *testing.T) {
 
 	var decoded bool
 	r := bufio.NewReader(strings.NewReader(example))
-	dec := NewDecoder(r, FmtText)
+	dec := NewDecoder(r, fmtText)
 	for {
 		var mf dto.MetricFamily
 		if err := dec.Decode(&mf); err != nil {
-			if err == io.EOF {
+			if errors.Is(err, io.EOF) {
 				break
 			}
 			t.Fatalf("Unexpected error: %v", err)
diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go
index 70a829c..c5e4146 100644
--- a/expfmt/encode_test.go
+++ b/expfmt/encode_test.go
@@ -18,8 +18,11 @@ import (
 	"net/http"
 	"testing"
 
-	dto "github.com/prometheus/client_model/go"
 	"google.golang.org/protobuf/proto"
+
+	"github.com/prometheus/common/model"
+
+	dto "github.com/prometheus/client_model/go"
 )
 
 func TestNegotiate(t *testing.T) {
@@ -32,38 +35,70 @@ func TestNegotiate(t *testing.T) {
 		{
 			name:              "delimited format",
 			acceptHeaderValue: acceptValuePrefix + ";encoding=delimited",
-			expectedFmt:       string(FmtProtoDelim),
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores",
 		},
 		{
 			name:              "text format",
 			acceptHeaderValue: acceptValuePrefix + ";encoding=text",
-			expectedFmt:       string(FmtProtoText),
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=underscores",
 		},
 		{
 			name:              "compact text format",
 			acceptHeaderValue: acceptValuePrefix + ";encoding=compact-text",
-			expectedFmt:       string(FmtProtoCompact),
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=underscores",
 		},
 		{
 			name:              "plain text format",
 			acceptHeaderValue: "text/plain;version=0.0.4",
-			expectedFmt:       string(FmtText),
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
+		},
+		{
+			name:              "delimited format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=delimited; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8",
+		},
+		{
+			name:              "text format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=text; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=allow-utf-8",
+		},
+		{
+			name:              "compact text format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=compact-text; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=allow-utf-8",
+		},
+		{
+			name:              "plain text format 0.0.4 with utf-8 not valid, falls back",
+			acceptHeaderValue: "text/plain;version=0.0.4;",
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=underscores",
+		},
+		{
+			name:              "plain text format 0.0.4 with utf-8 not valid, falls back",
+			acceptHeaderValue: "text/plain;version=0.0.4; escaping=values;",
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=values",
 		},
 	}
 
-	for _, test := range tests {
+	oldDefault := model.NameEscapingScheme
+	model.NameEscapingScheme = model.UnderscoreEscaping
+	defer func() {
+		model.NameEscapingScheme = oldDefault
+	}()
+
+	for i, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			h := http.Header{}
 			h.Add(hdrAccept, test.acceptHeaderValue)
 			actualFmt := string(Negotiate(h))
 			if actualFmt != test.expectedFmt {
-				t.Errorf("expected Negotiate to return format %s, but got %s instead", test.expectedFmt, actualFmt)
+				t.Errorf("case %d: expected Negotiate to return format %s, but got %s instead", i, test.expectedFmt, actualFmt)
 			}
 		})
 	}
 }
 
 func TestNegotiateOpenMetrics(t *testing.T) {
+	acceptValuePrefix := "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily"
 	tests := []struct {
 		name              string
 		acceptHeaderValue string
@@ -72,39 +107,101 @@ func TestNegotiateOpenMetrics(t *testing.T) {
 		{
 			name:              "OM format, no version",
 			acceptHeaderValue: "application/openmetrics-text",
-			expectedFmt:       string(FmtOpenMetrics_0_0_1),
+			expectedFmt:       "application/openmetrics-text; version=0.0.1; charset=utf-8; escaping=values",
 		},
 		{
 			name:              "OM format, 0.0.1 version",
-			acceptHeaderValue: "application/openmetrics-text;version=0.0.1",
-			expectedFmt:       string(FmtOpenMetrics_0_0_1),
+			acceptHeaderValue: "application/openmetrics-text;version=0.0.1; escaping=underscores",
+			expectedFmt:       "application/openmetrics-text; version=0.0.1; charset=utf-8; escaping=underscores",
 		},
 		{
 			name:              "OM format, 1.0.0 version",
 			acceptHeaderValue: "application/openmetrics-text;version=1.0.0",
-			expectedFmt:       string(FmtOpenMetrics_1_0_0),
+			expectedFmt:       "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values",
+		},
+		{
+			name:              "OM format, 0.0.1 version with utf-8 is not valid, falls back",
+			acceptHeaderValue: "application/openmetrics-text;version=0.0.1",
+			expectedFmt:       "application/openmetrics-text; version=0.0.1; charset=utf-8; escaping=values",
+		},
+		{
+			name:              "OM format, 1.0.0 version with utf-8 is not valid, falls back",
+			acceptHeaderValue: "application/openmetrics-text;version=1.0.0; escaping=values;",
+			expectedFmt:       "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values",
 		},
 		{
 			name:              "OM format, invalid version",
 			acceptHeaderValue: "application/openmetrics-text;version=0.0.4",
-			expectedFmt:       string(FmtText),
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=values",
+		},
+		{
+			name:              "compact text format",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=compact-text; escaping=underscores",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=underscores",
+		},
+		{
+			name:              "plain text format",
+			acceptHeaderValue: "text/plain;version=0.0.4",
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=values",
+		},
+		{
+			name:              "plain text format 0.0.4",
+			acceptHeaderValue: "text/plain;version=0.0.4; escaping=allow-utf-8",
+			expectedFmt:       "text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8",
+		},
+		{
+			name:              "delimited format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=delimited; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8",
+		},
+		{
+			name:              "text format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=text; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=allow-utf-8",
+		},
+		{
+			name:              "compact text format utf-8",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=compact-text; escaping=allow-utf-8;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=allow-utf-8",
+		},
+		{
+			name:              "delimited format escaped",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=delimited; escaping=underscores;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores",
+		},
+		{
+			name:              "text format escaped",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=text; escaping=underscores;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=underscores",
+		},
+		{
+			name:              "compact text format escaped",
+			acceptHeaderValue: acceptValuePrefix + ";encoding=compact-text; escaping=underscores;",
+			expectedFmt:       "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=underscores",
 		},
 	}
 
-	for _, test := range tests {
+	oldDefault := model.NameEscapingScheme
+	model.NameEscapingScheme = model.ValueEncodingEscaping
+	defer func() {
+		model.NameEscapingScheme = oldDefault
+	}()
+
+	for i, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			h := http.Header{}
 			h.Add(hdrAccept, test.acceptHeaderValue)
 			actualFmt := string(NegotiateIncludingOpenMetrics(h))
 			if actualFmt != test.expectedFmt {
-				t.Errorf("expected Negotiate to return format %s, but got %s instead", test.expectedFmt, actualFmt)
+				t.Errorf("case %d: expected Negotiate to return format %s, but got %s instead", i, test.expectedFmt, actualFmt)
 			}
 		})
 	}
 }
+
 func TestEncode(t *testing.T) {
 	var buff bytes.Buffer
-	delimEncoder := NewEncoder(&buff, FmtProtoDelim)
+	delimEncoder := NewEncoder(&buff, fmtProtoDelim)
 	metric := &dto.MetricFamily{
 		Name: proto.String("foo_metric"),
 		Type: dto.MetricType_UNTYPED.Enum(),
@@ -129,7 +226,7 @@ func TestEncode(t *testing.T) {
 
 	buff.Reset()
 
-	compactEncoder := NewEncoder(&buff, FmtProtoCompact)
+	compactEncoder := NewEncoder(&buff, fmtProtoCompact)
 	err = compactEncoder.Encode(metric)
 	if err != nil {
 		t.Errorf("unexpected error during encode: %s", err.Error())
@@ -142,7 +239,7 @@ func TestEncode(t *testing.T) {
 
 	buff.Reset()
 
-	protoTextEncoder := NewEncoder(&buff, FmtProtoText)
+	protoTextEncoder := NewEncoder(&buff, fmtProtoText)
 	err = protoTextEncoder.Encode(metric)
 	if err != nil {
 		t.Errorf("unexpected error during encode: %s", err.Error())
@@ -155,7 +252,7 @@ func TestEncode(t *testing.T) {
 
 	buff.Reset()
 
-	textEncoder := NewEncoder(&buff, FmtText)
+	textEncoder := NewEncoder(&buff, fmtText)
 	err = textEncoder.Encode(metric)
 	if err != nil {
 		t.Errorf("unexpected error during encode: %s", err.Error())
@@ -173,3 +270,88 @@ func TestEncode(t *testing.T) {
 		t.Errorf("expected TextEncoder to return %s, but got %s instead", expected, string(out))
 	}
 }
+
+func TestEscapedEncode(t *testing.T) {
+	var buff bytes.Buffer
+	delimEncoder := NewEncoder(&buff, fmtProtoDelim+"; escaping=underscores")
+	metric := &dto.MetricFamily{
+		Name: proto.String("foo.metric"),
+		Type: dto.MetricType_UNTYPED.Enum(),
+		Metric: []*dto.Metric{
+			{
+				Untyped: &dto.Untyped{
+					Value: proto.Float64(1.234),
+				},
+			},
+			{
+				Label: []*dto.LabelPair{
+					{
+						Name:  proto.String("dotted.label.name"),
+						Value: proto.String("my.label.value"),
+					},
+				},
+				Untyped: &dto.Untyped{
+					Value: proto.Float64(8),
+				},
+			},
+		},
+	}
+
+	err := delimEncoder.Encode(metric)
+	if err != nil {
+		t.Errorf("unexpected error during encode: %s", err.Error())
+	}
+
+	out := buff.Bytes()
+	if len(out) == 0 {
+		t.Errorf("expected the output bytes buffer to be non-empty")
+	}
+
+	buff.Reset()
+
+	compactEncoder := NewEncoder(&buff, fmtProtoCompact)
+	err = compactEncoder.Encode(metric)
+	if err != nil {
+		t.Errorf("unexpected error during encode: %s", err.Error())
+	}
+
+	out = buff.Bytes()
+	if len(out) == 0 {
+		t.Errorf("expected the output bytes buffer to be non-empty")
+	}
+
+	buff.Reset()
+
+	protoTextEncoder := NewEncoder(&buff, fmtProtoText)
+	err = protoTextEncoder.Encode(metric)
+	if err != nil {
+		t.Errorf("unexpected error during encode: %s", err.Error())
+	}
+
+	out = buff.Bytes()
+	if len(out) == 0 {
+		t.Errorf("expected the output bytes buffer to be non-empty")
+	}
+
+	buff.Reset()
+
+	textEncoder := NewEncoder(&buff, fmtText)
+	err = textEncoder.Encode(metric)
+	if err != nil {
+		t.Errorf("unexpected error during encode: %s", err.Error())
+	}
+
+	out = buff.Bytes()
+	if len(out) == 0 {
+		t.Errorf("expected the output bytes buffer to be non-empty")
+	}
+
+	expected := `# TYPE U__foo_2e_metric untyped
+U__foo_2e_metric 1.234
+U__foo_2e_metric{U__dotted_2e_label_2e_name="my.label.value"} 8
+`
+
+	if string(out) != expected {
+		t.Errorf("expected TextEncoder to return %s, but got %s instead", expected, string(out))
+	}
+}
diff --git a/expfmt/openmetrics_create_test.go b/expfmt/openmetrics_create_test.go
index 455e5e5..c56ef22 100644
--- a/expfmt/openmetrics_create_test.go
+++ b/expfmt/openmetrics_create_test.go
@@ -24,6 +24,8 @@ import (
 	"google.golang.org/protobuf/types/known/timestamppb"
 
 	dto "github.com/prometheus/client_model/go"
+
+	"github.com/prometheus/common/model"
 )
 
 func TestCreateOpenMetrics(t *testing.T) {
@@ -32,9 +34,16 @@ func TestCreateOpenMetrics(t *testing.T) {
 		t.Error(err)
 	}
 
-	var scenarios = []struct {
-		in  *dto.MetricFamily
-		out string
+	oldDefaultScheme := model.NameEscapingScheme
+	model.NameEscapingScheme = model.NoEscaping
+	defer func() {
+		model.NameEscapingScheme = oldDefaultScheme
+	}()
+
+	scenarios := []struct {
+		in      *dto.MetricFamily
+		options []EncoderOption
+		out     string
 	}{
 		// 0: Counter, timestamp given, no _total suffix.
 		{
@@ -43,13 +52,13 @@ func TestCreateOpenMetrics(t *testing.T) {
 				Help: proto.String("two-line\n doc  str\\ing"),
 				Type: dto.MetricType_COUNTER.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("labelname"),
 								Value: proto.String("val1"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("basename"),
 								Value: proto.String("basevalue"),
 							},
@@ -58,13 +67,13 @@ func TestCreateOpenMetrics(t *testing.T) {
 							Value: proto.Float64(42),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("labelname"),
 								Value: proto.String("val2"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("basename"),
 								Value: proto.String("basevalue"),
 							},
@@ -82,20 +91,92 @@ name{labelname="val1",basename="basevalue"} 42.0
 name{labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
 `,
 		},
-		// 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
+		// 1: Dots in name
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("name.with.dots"),
+				Help: proto.String("boring help"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("labelname"),
+								Value: proto.String("val1"),
+							},
+							{
+								Name:  proto.String("basename"),
+								Value: proto.String("basevalue"),
+							},
+						},
+						Counter: &dto.Counter{
+							Value: proto.Float64(42),
+						},
+					},
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("labelname"),
+								Value: proto.String("val2"),
+							},
+							{
+								Name:  proto.String("basename"),
+								Value: proto.String("basevalue"),
+							},
+						},
+						Counter: &dto.Counter{
+							Value: proto.Float64(.23),
+						},
+						TimestampMs: proto.Int64(1234567890),
+					},
+				},
+			},
+			out: `# HELP "name.with.dots" boring help
+# TYPE "name.with.dots" unknown
+{"name.with.dots",labelname="val1",basename="basevalue"} 42.0
+{"name.with.dots",labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
+`,
+		},
+		// 2: Dots in name, no labels
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("name.with.dots"),
+				Help: proto.String("boring help"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(42),
+						},
+					},
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(.23),
+						},
+						TimestampMs: proto.Int64(1234567890),
+					},
+				},
+			},
+			out: `# HELP "name.with.dots" boring help
+# TYPE "name.with.dots" unknown
+{"name.with.dots"} 42.0
+{"name.with.dots"} 0.23 1.23456789e+06
+`,
+		},
+		// 3: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("gauge_name"),
 				Help: proto.String("gauge\ndoc\nstr\"ing"),
 				Type: dto.MetricType_GAUGE.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("val with\nnew line"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("val with \\backslash and \"quotes\""),
 							},
@@ -104,13 +185,13 @@ name{labelname="val2",basename="basevalue"} 0.23 1.23456789e+06
 							Value: proto.Float64(math.Inf(+1)),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("Björn"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("佖佥"),
 							},
@@ -127,20 +208,65 @@ gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes
 gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
 `,
 		},
-		// 2: Unknown, no help, one sample with no labels and -Inf as value, another sample with one label.
+		// 4: Gauge, utf-8, some escaping required, +Inf as value, multi-byte characters in label values.
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("gauge.name\""),
+				Help: proto.String("gauge\ndoc\nstr\"ing"),
+				Type: dto.MetricType_GAUGE.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("name.1"),
+								Value: proto.String("val with\nnew line"),
+							},
+							{
+								Name:  proto.String("name*2"),
+								Value: proto.String("val with \\backslash and \"quotes\""),
+							},
+						},
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(math.Inf(+1)),
+						},
+					},
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("name.1"),
+								Value: proto.String("Björn"),
+							},
+							{
+								Name:  proto.String("name*2"),
+								Value: proto.String("佖佥"),
+							},
+						},
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(3.14e42),
+						},
+					},
+				},
+			},
+			out: `# HELP "gauge.name\"" gauge\ndoc\nstr\"ing
+# TYPE "gauge.name\"" gauge
+{"gauge.name\"","name.1"="val with\nnew line","name*2"="val with \\backslash and \"quotes\""} +Inf
+{"gauge.name\"","name.1"="Björn","name*2"="佖佥"} 3.14e+42
+`,
+		},
+		// 5: Unknown, no help, one sample with no labels and -Inf as value, another sample with one label.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("unknown_name"),
 				Type: dto.MetricType_UNTYPED.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("value 1"),
 							},
@@ -156,40 +282,41 @@ unknown_name -Inf
 unknown_name{name_1="value 1"} -1.23e-45
 `,
 		},
-		// 3: Summary.
+		// 6: Summary.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("summary_name"),
 				Help: proto.String("summary docstring"),
 				Type: dto.MetricType_SUMMARY.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Summary: &dto.Summary{
 							SampleCount: proto.Uint64(42),
 							SampleSum:   proto.Float64(-3.4567),
 							Quantile: []*dto.Quantile{
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.5),
 									Value:    proto.Float64(-1.23),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.9),
 									Value:    proto.Float64(.2342354),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.99),
 									Value:    proto.Float64(0),
 								},
 							},
+							CreatedTimestamp: openMetricsTimestamp,
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("value 1"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("value 2"),
 							},
@@ -198,23 +325,25 @@ unknown_name{name_1="value 1"} -1.23e-45
 							SampleCount: proto.Uint64(4711),
 							SampleSum:   proto.Float64(2010.1971),
 							Quantile: []*dto.Quantile{
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.5),
 									Value:    proto.Float64(1),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.9),
 									Value:    proto.Float64(2),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.99),
 									Value:    proto.Float64(3),
 								},
 							},
+							CreatedTimestamp: openMetricsTimestamp,
 						},
 					},
 				},
 			},
+			options: []EncoderOption{WithCreatedLines()},
 			out: `# HELP summary_name summary docstring
 # TYPE summary_name summary
 summary_name{quantile="0.5"} -1.23
@@ -222,50 +351,54 @@ summary_name{quantile="0.9"} 0.2342354
 summary_name{quantile="0.99"} 0.0
 summary_name_sum -3.4567
 summary_name_count 42
+summary_name_created 12345.6
 summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1.0
 summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2.0
 summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3.0
 summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
 summary_name_count{name_1="value 1",name_2="value 2"} 4711
+summary_name_created{name_1="value 1",name_2="value 2"} 12345.6
 `,
 		},
-		// 4: Histogram
+		// 7: Histogram
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("request_duration_microseconds"),
 				Help: proto.String("The response latency."),
 				Type: dto.MetricType_HISTOGRAM.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Histogram: &dto.Histogram{
 							SampleCount: proto.Uint64(2693),
 							SampleSum:   proto.Float64(1756047.3),
 							Bucket: []*dto.Bucket{
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(100),
 									CumulativeCount: proto.Uint64(123),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(120),
 									CumulativeCount: proto.Uint64(412),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(144),
 									CumulativeCount: proto.Uint64(592),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(172.8),
 									CumulativeCount: proto.Uint64(1524),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(math.Inf(+1)),
 									CumulativeCount: proto.Uint64(2693),
 								},
 							},
+							CreatedTimestamp: openMetricsTimestamp,
 						},
 					},
 				},
 			},
+			options: []EncoderOption{WithCreatedLines()},
 			out: `# HELP request_duration_microseconds The response latency.
 # TYPE request_duration_microseconds histogram
 request_duration_microseconds_bucket{le="100.0"} 123
@@ -275,33 +408,34 @@ request_duration_microseconds_bucket{le="172.8"} 1524
 request_duration_microseconds_bucket{le="+Inf"} 2693
 request_duration_microseconds_sum 1.7560473e+06
 request_duration_microseconds_count 2693
+request_duration_microseconds_created 12345.6
 `,
 		},
-		// 5: Histogram with missing +Inf bucket.
+		// 8: Histogram with missing +Inf bucket.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("request_duration_microseconds"),
 				Help: proto.String("The response latency."),
 				Type: dto.MetricType_HISTOGRAM.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Histogram: &dto.Histogram{
 							SampleCount: proto.Uint64(2693),
 							SampleSum:   proto.Float64(1756047.3),
 							Bucket: []*dto.Bucket{
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(100),
 									CumulativeCount: proto.Uint64(123),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(120),
 									CumulativeCount: proto.Uint64(412),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(144),
 									CumulativeCount: proto.Uint64(592),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(172.8),
 									CumulativeCount: proto.Uint64(1524),
 								},
@@ -321,28 +455,28 @@ request_duration_microseconds_sum 1.7560473e+06
 request_duration_microseconds_count 2693
 `,
 		},
-		// 6: Histogram with missing +Inf bucket but with different exemplars.
+		// 9: Histogram with missing +Inf bucket but with different exemplars.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("request_duration_microseconds"),
 				Help: proto.String("The response latency."),
 				Type: dto.MetricType_HISTOGRAM.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Histogram: &dto.Histogram{
 							SampleCount: proto.Uint64(2693),
 							SampleSum:   proto.Float64(1756047.3),
 							Bucket: []*dto.Bucket{
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(100),
 									CumulativeCount: proto.Uint64(123),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(120),
 									CumulativeCount: proto.Uint64(412),
 									Exemplar: &dto.Exemplar{
 										Label: []*dto.LabelPair{
-											&dto.LabelPair{
+											{
 												Name:  proto.String("foo"),
 												Value: proto.String("bar"),
 											},
@@ -351,16 +485,16 @@ request_duration_microseconds_count 2693
 										Timestamp: openMetricsTimestamp,
 									},
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(144),
 									CumulativeCount: proto.Uint64(592),
 									Exemplar: &dto.Exemplar{
 										Label: []*dto.LabelPair{
-											&dto.LabelPair{
+											{
 												Name:  proto.String("foo"),
 												Value: proto.String("baz"),
 											},
-											&dto.LabelPair{
+											{
 												Name:  proto.String("dings"),
 												Value: proto.String("bums"),
 											},
@@ -368,7 +502,7 @@ request_duration_microseconds_count 2693
 										Value: proto.Float64(140.14),
 									},
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(172.8),
 									CumulativeCount: proto.Uint64(1524),
 								},
@@ -388,26 +522,49 @@ request_duration_microseconds_sum 1.7560473e+06
 request_duration_microseconds_count 2693
 `,
 		},
-		// 7: Simple Counter.
+		// 10: Simple Counter.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("foos_total"),
 				Help: proto.String("Number of foos."),
 				Type: dto.MetricType_COUNTER.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Counter: &dto.Counter{
-							Value: proto.Float64(42),
+							Value:            proto.Float64(42),
+							CreatedTimestamp: openMetricsTimestamp,
 						},
 					},
 				},
 			},
+			options: []EncoderOption{WithCreatedLines()},
 			out: `# HELP foos Number of foos.
 # TYPE foos counter
 foos_total 42.0
+foos_created 12345.6
 `,
 		},
-		// 8: No metric.
+		// 11: Simple Counter without created line.
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("foos_total"),
+				Help: proto.String("Number of foos."),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value:            proto.Float64(42),
+							CreatedTimestamp: openMetricsTimestamp,
+						},
+					},
+				},
+			},
+			out: `# HELP foos Number of foos.
+# TYPE foos counter
+foos_total 42.0
+`,
+		},
+		// 12: No metric.
 		{
 			in: &dto.MetricFamily{
 				Name:   proto.String("name_total"),
@@ -417,13 +574,38 @@ foos_total 42.0
 			},
 			out: `# HELP name doc string
 # TYPE name counter
+`,
+		},
+		// 9: Simple Counter with exemplar that has empty label set:
+		// ignore the exemplar, since OpenMetrics spec requires labels.
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("foos_total"),
+				Help: proto.String("Number of foos."),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(42),
+							Exemplar: &dto.Exemplar{
+								Label:     []*dto.LabelPair{},
+								Value:     proto.Float64(1),
+								Timestamp: openMetricsTimestamp,
+							},
+						},
+					},
+				},
+			},
+			out: `# HELP foos Number of foos.
+# TYPE foos counter
+foos_total 42.0
 `,
 		},
 	}
 
 	for i, scenario := range scenarios {
 		out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
-		n, err := MetricFamilyToOpenMetrics(out, scenario.in)
+		n, err := MetricFamilyToOpenMetrics(out, scenario.in, scenario.options...)
 		if err != nil {
 			t.Errorf("%d. error: %s", i, err)
 			continue
@@ -441,7 +623,6 @@ foos_total 42.0
 			)
 		}
 	}
-
 }
 
 func BenchmarkOpenMetricsCreate(b *testing.B) {
@@ -450,17 +631,17 @@ func BenchmarkOpenMetricsCreate(b *testing.B) {
 		Help: proto.String("The response latency."),
 		Type: dto.MetricType_HISTOGRAM.Enum(),
 		Metric: []*dto.Metric{
-			&dto.Metric{
+			{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_1"),
 						Value: proto.String("val with\nnew line"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_2"),
 						Value: proto.String("val with \\backslash and \"quotes\""),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_3"),
 						Value: proto.String("Just a quite long label value to test performance."),
 					},
@@ -469,40 +650,40 @@ func BenchmarkOpenMetricsCreate(b *testing.B) {
 					SampleCount: proto.Uint64(2693),
 					SampleSum:   proto.Float64(1756047.3),
 					Bucket: []*dto.Bucket{
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(100),
 							CumulativeCount: proto.Uint64(123),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(120),
 							CumulativeCount: proto.Uint64(412),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(144),
 							CumulativeCount: proto.Uint64(592),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(172.8),
 							CumulativeCount: proto.Uint64(1524),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(math.Inf(+1)),
 							CumulativeCount: proto.Uint64(2693),
 						},
 					},
 				},
 			},
-			&dto.Metric{
+			{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_1"),
 						Value: proto.String("Björn"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_2"),
 						Value: proto.String("佖佥"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_3"),
 						Value: proto.String("Just a quite long label value to test performance."),
 					},
@@ -511,19 +692,19 @@ func BenchmarkOpenMetricsCreate(b *testing.B) {
 					SampleCount: proto.Uint64(5699),
 					SampleSum:   proto.Float64(49484343543.4343),
 					Bucket: []*dto.Bucket{
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(100),
 							CumulativeCount: proto.Uint64(120),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(120),
 							CumulativeCount: proto.Uint64(412),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(144),
 							CumulativeCount: proto.Uint64(596),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(172.8),
 							CumulativeCount: proto.Uint64(1535),
 						},
@@ -545,7 +726,7 @@ func BenchmarkOpenMetricsCreate(b *testing.B) {
 }
 
 func TestOpenMetricsCreateError(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  *dto.MetricFamily
 		err string
 	}{
@@ -555,7 +736,7 @@ func TestOpenMetricsCreateError(t *testing.T) {
 				Help: proto.String("doc string"),
 				Type: dto.MetricType_UNTYPED.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
@@ -571,7 +752,7 @@ func TestOpenMetricsCreateError(t *testing.T) {
 				Help: proto.String("doc string"),
 				Type: dto.MetricType_COUNTER.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
@@ -596,5 +777,4 @@ func TestOpenMetricsCreateError(t *testing.T) {
 			)
 		}
 	}
-
 }
diff --git a/expfmt/text_create_test.go b/expfmt/text_create_test.go
index e607166..7cf0428 100644
--- a/expfmt/text_create_test.go
+++ b/expfmt/text_create_test.go
@@ -22,10 +22,18 @@ import (
 	"google.golang.org/protobuf/proto"
 
 	dto "github.com/prometheus/client_model/go"
+
+	"github.com/prometheus/common/model"
 )
 
 func TestCreate(t *testing.T) {
-	var scenarios = []struct {
+	oldDefaultScheme := model.NameEscapingScheme
+	model.NameEscapingScheme = model.NoEscaping
+	defer func() {
+		model.NameEscapingScheme = oldDefaultScheme
+	}()
+
+	scenarios := []struct {
 		in  *dto.MetricFamily
 		out string
 	}{
@@ -36,13 +44,13 @@ func TestCreate(t *testing.T) {
 				Help: proto.String("two-line\n doc  str\\ing"),
 				Type: dto.MetricType_COUNTER.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("labelname"),
 								Value: proto.String("val1"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("basename"),
 								Value: proto.String("basevalue"),
 							},
@@ -51,13 +59,13 @@ func TestCreate(t *testing.T) {
 							Value: proto.Float64(math.NaN()),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("labelname"),
 								Value: proto.String("val2"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("basename"),
 								Value: proto.String("basevalue"),
 							},
@@ -82,13 +90,13 @@ name{labelname="val2",basename="basevalue"} 0.23 1234567890
 				Help: proto.String("gauge\ndoc\nstr\"ing"),
 				Type: dto.MetricType_GAUGE.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("val with\nnew line"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("val with \\backslash and \"quotes\""),
 							},
@@ -97,13 +105,13 @@ name{labelname="val2",basename="basevalue"} 0.23 1234567890
 							Value: proto.Float64(math.Inf(+1)),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("Björn"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("佖佥"),
 							},
@@ -120,20 +128,65 @@ gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes
 gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
 `,
 		},
-		// 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
+		// 2: Gauge, utf-8, +Inf as value, multi-byte characters in label values.
+		{
+			in: &dto.MetricFamily{
+				Name: proto.String("gauge.name"),
+				Help: proto.String("gauge\ndoc\nstr\"ing"),
+				Type: dto.MetricType_GAUGE.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("name.1"),
+								Value: proto.String("val with\nnew line"),
+							},
+							{
+								Name:  proto.String("name*2"),
+								Value: proto.String("val with \\backslash and \"quotes\""),
+							},
+						},
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(math.Inf(+1)),
+						},
+					},
+					{
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("name.1"),
+								Value: proto.String("Björn"),
+							},
+							{
+								Name:  proto.String("name*2"),
+								Value: proto.String("佖佥"),
+							},
+						},
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(3.14e42),
+						},
+					},
+				},
+			},
+			out: `# HELP "gauge.name" gauge\ndoc\nstr"ing
+# TYPE "gauge.name" gauge
+{"gauge.name","name.1"="val with\nnew line","name*2"="val with \\backslash and \"quotes\""} +Inf
+{"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14e+42
+`,
+		},
+		// 3: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("untyped_name"),
 				Type: dto.MetricType_UNTYPED.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("value 1"),
 							},
@@ -149,40 +202,40 @@ untyped_name -Inf
 untyped_name{name_1="value 1"} -1.23e-45
 `,
 		},
-		// 3: Summary.
+		// 4: Summary.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("summary_name"),
 				Help: proto.String("summary docstring"),
 				Type: dto.MetricType_SUMMARY.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Summary: &dto.Summary{
 							SampleCount: proto.Uint64(42),
 							SampleSum:   proto.Float64(-3.4567),
 							Quantile: []*dto.Quantile{
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.5),
 									Value:    proto.Float64(-1.23),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.9),
 									Value:    proto.Float64(.2342354),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.99),
 									Value:    proto.Float64(0),
 								},
 							},
 						},
 					},
-					&dto.Metric{
+					{
 						Label: []*dto.LabelPair{
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_1"),
 								Value: proto.String("value 1"),
 							},
-							&dto.LabelPair{
+							{
 								Name:  proto.String("name_2"),
 								Value: proto.String("value 2"),
 							},
@@ -191,15 +244,15 @@ untyped_name{name_1="value 1"} -1.23e-45
 							SampleCount: proto.Uint64(4711),
 							SampleSum:   proto.Float64(2010.1971),
 							Quantile: []*dto.Quantile{
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.5),
 									Value:    proto.Float64(1),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.9),
 									Value:    proto.Float64(2),
 								},
-								&dto.Quantile{
+								{
 									Quantile: proto.Float64(0.99),
 									Value:    proto.Float64(3),
 								},
@@ -222,35 +275,35 @@ summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
 summary_name_count{name_1="value 1",name_2="value 2"} 4711
 `,
 		},
-		// 4: Histogram
+		// 5: Histogram
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("request_duration_microseconds"),
 				Help: proto.String("The response latency."),
 				Type: dto.MetricType_HISTOGRAM.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Histogram: &dto.Histogram{
 							SampleCount: proto.Uint64(2693),
 							SampleSum:   proto.Float64(1756047.3),
 							Bucket: []*dto.Bucket{
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(100),
 									CumulativeCount: proto.Uint64(123),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(120),
 									CumulativeCount: proto.Uint64(412),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(144),
 									CumulativeCount: proto.Uint64(592),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(172.8),
 									CumulativeCount: proto.Uint64(1524),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(math.Inf(+1)),
 									CumulativeCount: proto.Uint64(2693),
 								},
@@ -270,31 +323,31 @@ request_duration_microseconds_sum 1.7560473e+06
 request_duration_microseconds_count 2693
 `,
 		},
-		// 5: Histogram with missing +Inf bucket.
+		// 6: Histogram with missing +Inf bucket.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("request_duration_microseconds"),
 				Help: proto.String("The response latency."),
 				Type: dto.MetricType_HISTOGRAM.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Histogram: &dto.Histogram{
 							SampleCount: proto.Uint64(2693),
 							SampleSum:   proto.Float64(1756047.3),
 							Bucket: []*dto.Bucket{
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(100),
 									CumulativeCount: proto.Uint64(123),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(120),
 									CumulativeCount: proto.Uint64(412),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(144),
 									CumulativeCount: proto.Uint64(592),
 								},
-								&dto.Bucket{
+								{
 									UpperBound:      proto.Float64(172.8),
 									CumulativeCount: proto.Uint64(1524),
 								},
@@ -314,13 +367,13 @@ request_duration_microseconds_sum 1.7560473e+06
 request_duration_microseconds_count 2693
 `,
 		},
-		// 6: No metric type, should result in default type Counter.
+		// 7: No metric type, should result in default type Counter.
 		{
 			in: &dto.MetricFamily{
 				Name: proto.String("name"),
 				Help: proto.String("doc string"),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Counter: &dto.Counter{
 							Value: proto.Float64(math.Inf(-1)),
 						},
@@ -354,7 +407,6 @@ name -Inf
 			)
 		}
 	}
-
 }
 
 func BenchmarkCreate(b *testing.B) {
@@ -363,17 +415,17 @@ func BenchmarkCreate(b *testing.B) {
 		Help: proto.String("The response latency."),
 		Type: dto.MetricType_HISTOGRAM.Enum(),
 		Metric: []*dto.Metric{
-			&dto.Metric{
+			{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_1"),
 						Value: proto.String("val with\nnew line"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_2"),
 						Value: proto.String("val with \\backslash and \"quotes\""),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_3"),
 						Value: proto.String("Just a quite long label value to test performance."),
 					},
@@ -382,40 +434,40 @@ func BenchmarkCreate(b *testing.B) {
 					SampleCount: proto.Uint64(2693),
 					SampleSum:   proto.Float64(1756047.3),
 					Bucket: []*dto.Bucket{
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(100),
 							CumulativeCount: proto.Uint64(123),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(120),
 							CumulativeCount: proto.Uint64(412),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(144),
 							CumulativeCount: proto.Uint64(592),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(172.8),
 							CumulativeCount: proto.Uint64(1524),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(math.Inf(+1)),
 							CumulativeCount: proto.Uint64(2693),
 						},
 					},
 				},
 			},
-			&dto.Metric{
+			{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_1"),
 						Value: proto.String("Björn"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_2"),
 						Value: proto.String("佖佥"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("name_3"),
 						Value: proto.String("Just a quite long label value to test performance."),
 					},
@@ -424,19 +476,19 @@ func BenchmarkCreate(b *testing.B) {
 					SampleCount: proto.Uint64(5699),
 					SampleSum:   proto.Float64(49484343543.4343),
 					Bucket: []*dto.Bucket{
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(100),
 							CumulativeCount: proto.Uint64(120),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(120),
 							CumulativeCount: proto.Uint64(412),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(144),
 							CumulativeCount: proto.Uint64(596),
 						},
-						&dto.Bucket{
+						{
 							UpperBound:      proto.Float64(172.8),
 							CumulativeCount: proto.Uint64(1535),
 						},
@@ -463,17 +515,17 @@ func BenchmarkCreateBuildInfo(b *testing.B) {
 		Help: proto.String("Test the creation of constant 1-value build_info metric."),
 		Type: dto.MetricType_GAUGE.Enum(),
 		Metric: []*dto.Metric{
-			&dto.Metric{
+			{
 				Label: []*dto.LabelPair{
-					&dto.LabelPair{
+					{
 						Name:  proto.String("version"),
 						Value: proto.String("1.2.3"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("revision"),
 						Value: proto.String("2e84f5e4eacdffb574035810305191ff390360fe"),
 					},
-					&dto.LabelPair{
+					{
 						Name:  proto.String("go_version"),
 						Value: proto.String("1.11.1"),
 					},
@@ -496,7 +548,7 @@ func BenchmarkCreateBuildInfo(b *testing.B) {
 }
 
 func TestCreateError(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  *dto.MetricFamily
 		err string
 	}{
@@ -516,7 +568,7 @@ func TestCreateError(t *testing.T) {
 				Help: proto.String("doc string"),
 				Type: dto.MetricType_UNTYPED.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
@@ -532,7 +584,7 @@ func TestCreateError(t *testing.T) {
 				Help: proto.String("doc string"),
 				Type: dto.MetricType_COUNTER.Enum(),
 				Metric: []*dto.Metric{
-					&dto.Metric{
+					{
 						Untyped: &dto.Untyped{
 							Value: proto.Float64(math.Inf(-1)),
 						},
@@ -557,5 +609,4 @@ func TestCreateError(t *testing.T) {
 			)
 		}
 	}
-
 }
diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go
index 893a857..0540546 100644
--- a/expfmt/text_parse_test.go
+++ b/expfmt/text_parse_test.go
@@ -24,7 +24,7 @@ import (
 )
 
 func testTextParse(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  string
 		out []*dto.MetricFamily
 	}{
@@ -45,22 +45,22 @@ no_labels{} 3
 # HELP line for non-existing metric will be ignored.
 `,
 			out: []*dto.MetricFamily{
-				&dto.MetricFamily{
+				{
 					Name: proto.String("minimal_metric"),
 					Type: dto.MetricType_UNTYPED.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Untyped: &dto.Untyped{
 								Value: proto.Float64(1.234),
 							},
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("another_metric"),
 					Type: dto.MetricType_UNTYPED.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Untyped: &dto.Untyped{
 								Value: proto.Float64(-3e3),
 							},
@@ -68,11 +68,11 @@ no_labels{} 3
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("no_labels"),
 					Type: dto.MetricType_UNTYPED.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Untyped: &dto.Untyped{
 								Value: proto.Float64(3),
 							},
@@ -97,18 +97,18 @@ name2{labelname="val2"	,basename   =   "basevalue2"		} +Inf 54321
 name2{ labelname = "val1" , }-Inf
 `,
 			out: []*dto.MetricFamily{
-				&dto.MetricFamily{
+				{
 					Name: proto.String("name"),
 					Help: proto.String("two-line\n doc  str\\ing"),
 					Type: dto.MetricType_COUNTER.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("labelname"),
 									Value: proto.String("val1"),
 								},
-								&dto.LabelPair{
+								{
 									Name:  proto.String("basename"),
 									Value: proto.String("basevalue"),
 								},
@@ -117,13 +117,13 @@ name2{ labelname = "val1" , }-Inf
 								Value: proto.Float64(math.NaN()),
 							},
 						},
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("labelname"),
 									Value: proto.String("val2"),
 								},
-								&dto.LabelPair{
+								{
 									Name:  proto.String("basename"),
 									Value: proto.String("base\"v\\al\nue"),
 								},
@@ -135,18 +135,18 @@ name2{ labelname = "val1" , }-Inf
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("name2"),
 					Help: proto.String("doc str\"ing 2"),
 					Type: dto.MetricType_GAUGE.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("labelname"),
 									Value: proto.String("val2"),
 								},
-								&dto.LabelPair{
+								{
 									Name:  proto.String("basename"),
 									Value: proto.String("basevalue2"),
 								},
@@ -156,9 +156,9 @@ name2{ labelname = "val1" , }-Inf
 							},
 							TimestampMs: proto.Int64(54321),
 						},
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("labelname"),
 									Value: proto.String("val1"),
 								},
@@ -197,13 +197,13 @@ my_summary{n1="val3", quantile="0.2"} 4711
 # HELP my_summary 
 `,
 			out: []*dto.MetricFamily{
-				&dto.MetricFamily{
+				{
 					Name: proto.String("fake_sum"),
 					Type: dto.MetricType_UNTYPED.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val1"),
 								},
@@ -214,11 +214,11 @@ my_summary{n1="val3", quantile="0.2"} 4711
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("decoy"),
 					Type: dto.MetricType_UNTYPED.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Untyped: &dto.Untyped{
 								Value: proto.Float64(-1),
 							},
@@ -226,13 +226,13 @@ my_summary{n1="val3", quantile="0.2"} 4711
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("my_summary"),
 					Type: dto.MetricType_SUMMARY.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val1"),
 								},
@@ -241,11 +241,11 @@ my_summary{n1="val3", quantile="0.2"} 4711
 								SampleCount: proto.Uint64(42),
 								SampleSum:   proto.Float64(4711),
 								Quantile: []*dto.Quantile{
-									&dto.Quantile{
+									{
 										Quantile: proto.Float64(0.5),
 										Value:    proto.Float64(110),
 									},
-									&dto.Quantile{
+									{
 										Quantile: proto.Float64(0.9),
 										Value:    proto.Float64(140),
 									},
@@ -253,13 +253,13 @@ my_summary{n1="val3", quantile="0.2"} 4711
 							},
 							TimestampMs: proto.Int64(2),
 						},
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n2"),
 									Value: proto.String("val2"),
 								},
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val1"),
 								},
@@ -267,7 +267,7 @@ my_summary{n1="val3", quantile="0.2"} 4711
 							Summary: &dto.Summary{
 								SampleCount: proto.Uint64(5),
 								Quantile: []*dto.Quantile{
-									&dto.Quantile{
+									{
 										Quantile: proto.Float64(-12.34),
 										Value:    proto.Float64(math.NaN()),
 									},
@@ -275,9 +275,9 @@ my_summary{n1="val3", quantile="0.2"} 4711
 							},
 							TimestampMs: proto.Int64(5),
 						},
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val2"),
 								},
@@ -287,16 +287,16 @@ my_summary{n1="val3", quantile="0.2"} 4711
 							},
 							TimestampMs: proto.Int64(15),
 						},
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val3"),
 								},
 							},
 							Summary: &dto.Summary{
 								Quantile: []*dto.Quantile{
-									&dto.Quantile{
+									{
 										Quantile: proto.Float64(0.2),
 										Value:    proto.Float64(4711),
 									},
@@ -305,17 +305,17 @@ my_summary{n1="val3", quantile="0.2"} 4711
 						},
 					},
 				},
-				&dto.MetricFamily{
+				{
 					Name: proto.String("another_summary"),
 					Type: dto.MetricType_SUMMARY.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Label: []*dto.LabelPair{
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n2"),
 									Value: proto.String("val2"),
 								},
-								&dto.LabelPair{
+								{
 									Name:  proto.String("n1"),
 									Value: proto.String("val1"),
 								},
@@ -323,7 +323,7 @@ my_summary{n1="val3", quantile="0.2"} 4711
 							Summary: &dto.Summary{
 								SampleCount: proto.Uint64(20),
 								Quantile: []*dto.Quantile{
-									&dto.Quantile{
+									{
 										Quantile: proto.Float64(0.3),
 										Value:    proto.Float64(-1.2),
 									},
@@ -353,28 +353,28 @@ request_duration_microseconds_count 2693
 					Help: proto.String("The response latency."),
 					Type: dto.MetricType_HISTOGRAM.Enum(),
 					Metric: []*dto.Metric{
-						&dto.Metric{
+						{
 							Histogram: &dto.Histogram{
 								SampleCount: proto.Uint64(2693),
 								SampleSum:   proto.Float64(1756047.3),
 								Bucket: []*dto.Bucket{
-									&dto.Bucket{
+									{
 										UpperBound:      proto.Float64(100),
 										CumulativeCount: proto.Uint64(123),
 									},
-									&dto.Bucket{
+									{
 										UpperBound:      proto.Float64(120),
 										CumulativeCount: proto.Uint64(412),
 									},
-									&dto.Bucket{
+									{
 										UpperBound:      proto.Float64(144),
 										CumulativeCount: proto.Uint64(592),
 									},
-									&dto.Bucket{
+									{
 										UpperBound:      proto.Float64(172.8),
 										CumulativeCount: proto.Uint64(1524),
 									},
-									&dto.Bucket{
+									{
 										UpperBound:      proto.Float64(math.Inf(+1)),
 										CumulativeCount: proto.Uint64(2693),
 									},
@@ -429,7 +429,7 @@ func BenchmarkTextParse(b *testing.B) {
 }
 
 func testTextParseError(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  string
 		err string
 	}{
@@ -656,7 +656,6 @@ metric{quantile="0x1p-3"} 3.14
 			)
 		}
 	}
-
 }
 
 func TestTextParseError(t *testing.T) {
@@ -691,7 +690,7 @@ func TestTextParserStartOfLine(t *testing.T) {
 		if fn != nil {
 			t.Errorf("Unexpected non-nil function: %v", fn)
 		}
-		if p.err != in.err {
+		if p.err != nil && !errors.Is(p.err, in.err) {
 			t.Errorf("Unexpected error: %v, expected %v", p.err, in.err)
 		}
 	})
diff --git a/model/alert_test.go b/model/alert_test.go
index 8910da7..c140b15 100644
--- a/model/alert_test.go
+++ b/model/alert_test.go
@@ -24,7 +24,7 @@ import (
 func TestAlertValidate(t *testing.T) {
 	ts := time.Now()
 
-	var cases = []struct {
+	cases := []struct {
 		alert *Alert
 		err   string
 	}{
diff --git a/model/fingerprinting_test.go b/model/fingerprinting_test.go
index b123751..98f4971 100644
--- a/model/fingerprinting_test.go
+++ b/model/fingerprinting_test.go
@@ -22,7 +22,6 @@ func TestFingerprintFromString(t *testing.T) {
 	fs := "4294967295"
 
 	f, err := FingerprintFromString(fs)
-
 	if err != nil {
 		t.Errorf("unexpected error while getting Fingerprint from string: %s", err.Error())
 	}
diff --git a/model/labels_test.go b/model/labels_test.go
index 2ee5b31..2339543 100644
--- a/model/labels_test.go
+++ b/model/labels_test.go
@@ -19,7 +19,7 @@ import (
 )
 
 func testLabelNames(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  LabelNames
 		out LabelNames
 	}{
@@ -55,7 +55,7 @@ func BenchmarkLabelNames(b *testing.B) {
 }
 
 func testLabelValues(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  LabelValues
 		out LabelValues
 	}{
@@ -91,50 +91,69 @@ func BenchmarkLabelValues(b *testing.B) {
 }
 
 func TestLabelNameIsValid(t *testing.T) {
-	var scenarios = []struct {
-		ln    LabelName
-		valid bool
+	scenarios := []struct {
+		ln          LabelName
+		legacyValid bool
+		utf8Valid   bool
 	}{
 		{
-			ln:    "Avalid_23name",
-			valid: true,
+			ln:          "Avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "_Avalid_23name",
-			valid: true,
+			ln:          "_Avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "1valid_23name",
-			valid: false,
+			ln:          "1valid_23name",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "avalid_23name",
-			valid: true,
+			ln:          "avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "Ava:lid_23name",
-			valid: false,
+			ln:          "Ava:lid_23name",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "a lid_23name",
-			valid: false,
+			ln:          "a lid_23name",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			ln:    ":leading_colon",
-			valid: false,
+			ln:          ":leading_colon",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			ln:    "colon:in:the:middle",
-			valid: false,
+			ln:          "colon:in:the:middle",
+			legacyValid: false,
+			utf8Valid:   true,
+		},
+		{
+			ln:          "a\xc5z",
+			legacyValid: false,
+			utf8Valid:   false,
 		},
 	}
 
 	for _, s := range scenarios {
-		if s.ln.IsValid() != s.valid {
-			t.Errorf("Expected %v for %q using IsValid method", s.valid, s.ln)
+		NameValidationScheme = LegacyValidation
+		if s.ln.IsValid() != s.legacyValid {
+			t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln)
+		}
+		if LabelNameRE.MatchString(string(s.ln)) != s.legacyValid {
+			t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln)
 		}
-		if LabelNameRE.MatchString(string(s.ln)) != s.valid {
-			t.Errorf("Expected %v for %q using regexp match", s.valid, s.ln)
+		NameValidationScheme = UTF8Validation
+		if s.ln.IsValid() != s.utf8Valid {
+			t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.legacyValid, s.ln)
 		}
 	}
 }
diff --git a/model/labelset_test.go b/model/labelset_test.go
index dfdfc59..c008816 100644
--- a/model/labelset_test.go
+++ b/model/labelset_test.go
@@ -32,7 +32,6 @@ func TestUnmarshalJSONLabelSet(t *testing.T) {
 }`
 	var c testConfig
 	err := json.Unmarshal([]byte(labelSetJSON), &c)
-
 	if err != nil {
 		t.Errorf("unexpected error while marshalling JSON : %s", err.Error())
 	}
@@ -53,6 +52,7 @@ func TestUnmarshalJSONLabelSet(t *testing.T) {
 	}
 }`
 
+	NameValidationScheme = LegacyValidation
 	err = json.Unmarshal([]byte(invalidlabelSetJSON), &c)
 	expectedErr := `"1nvalid_23name" is not a valid label name`
 	if err == nil || err.Error() != expectedErr {
@@ -116,5 +116,4 @@ func TestLabelSetMerge(t *testing.T) {
 			t.Errorf("expected to get LabelValue %s, but got %s for LabelName %s", expected, lv, ln)
 		}
 	}
-
 }
diff --git a/model/metric_test.go b/model/metric_test.go
index db447f6..5b0fcba 100644
--- a/model/metric_test.go
+++ b/model/metric_test.go
@@ -13,10 +13,17 @@
 
 package model
 
-import "testing"
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	dto "github.com/prometheus/client_model/go"
+	"google.golang.org/protobuf/proto"
+)
 
 func testMetric(t testing.TB) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		input           LabelSet
 		fingerprint     Fingerprint
 		fastFingerprint Fingerprint
@@ -82,55 +89,75 @@ func BenchmarkMetric(b *testing.B) {
 	}
 }
 
-func TestMetricNameIsValid(t *testing.T) {
-	var scenarios = []struct {
-		mn    LabelValue
-		valid bool
+func TestMetricNameIsLegacyValid(t *testing.T) {
+	scenarios := []struct {
+		mn          LabelValue
+		legacyValid bool
+		utf8Valid   bool
 	}{
 		{
-			mn:    "Avalid_23name",
-			valid: true,
+			mn:          "Avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
+		},
+		{
+			mn:          "_Avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "_Avalid_23name",
-			valid: true,
+			mn:          "1valid_23name",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "1valid_23name",
-			valid: false,
+			mn:          "avalid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "avalid_23name",
-			valid: true,
+			mn:          "Ava:lid_23name",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "Ava:lid_23name",
-			valid: true,
+			mn:          "a lid_23name",
+			legacyValid: false,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "a lid_23name",
-			valid: false,
+			mn:          ":leading_colon",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			mn:    ":leading_colon",
-			valid: true,
+			mn:          "colon:in:the:middle",
+			legacyValid: true,
+			utf8Valid:   true,
 		},
 		{
-			mn:    "colon:in:the:middle",
-			valid: true,
+			mn:          "",
+			legacyValid: false,
+			utf8Valid:   false,
 		},
 		{
-			mn:    "",
-			valid: false,
+			mn:          "a\xc5z",
+			legacyValid: false,
+			utf8Valid:   false,
 		},
 	}
 
 	for _, s := range scenarios {
-		if IsValidMetricName(s.mn) != s.valid {
-			t.Errorf("Expected %v for %q using IsValidMetricName function", s.valid, s.mn)
+		NameValidationScheme = LegacyValidation
+		if IsValidMetricName(s.mn) != s.legacyValid {
+			t.Errorf("Expected %v for %q using legacy IsValidMetricName method", s.legacyValid, s.mn)
+		}
+		if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid {
+			t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn)
 		}
-		if MetricNameRE.MatchString(string(s.mn)) != s.valid {
-			t.Errorf("Expected %v for %q using regexp matching", s.valid, s.mn)
+		NameValidationScheme = UTF8Validation
+		if IsValidMetricName(s.mn) != s.utf8Valid {
+			t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.legacyValid, s.mn)
 		}
 	}
 }
@@ -198,7 +225,423 @@ func TestMetricToString(t *testing.T) {
 		t.Run(scenario.name, func(t *testing.T) {
 			actual := scenario.input.String()
 			if actual != scenario.expected {
-				t.Errorf("expected string output %s but got %s", actual, scenario.expected)
+				t.Errorf("expected string output %s but got %s", scenario.expected, actual)
+			}
+		})
+	}
+}
+
+func TestEscapeName(t *testing.T) {
+	scenarios := []struct {
+		name                  string
+		input                 string
+		expectedUnderscores   string
+		expectedDots          string
+		expectedUnescapedDots string
+		expectedValue         string
+	}{
+		{
+			name: "empty string",
+		},
+		{
+			name:                "legacy valid name",
+			input:               "no:escaping_required",
+			expectedUnderscores: "no:escaping_required",
+			// Dots escaping will escape underscores even though it's not strictly
+			// necessary for compatibility.
+			expectedDots:          "no:escaping__required",
+			expectedUnescapedDots: "no:escaping_required",
+			expectedValue:         "no:escaping_required",
+		},
+		{
+			name:                  "name with dots",
+			input:                 "mysystem.prod.west.cpu.load",
+			expectedUnderscores:   "mysystem_prod_west_cpu_load",
+			expectedDots:          "mysystem_dot_prod_dot_west_dot_cpu_dot_load",
+			expectedUnescapedDots: "mysystem.prod.west.cpu.load",
+			expectedValue:         "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load",
+		},
+		{
+			name:                  "name with dots and colon",
+			input:                 "http.status:sum",
+			expectedUnderscores:   "http_status:sum",
+			expectedDots:          "http_dot_status:sum",
+			expectedUnescapedDots: "http.status:sum",
+			expectedValue:         "U__http_2e_status:sum",
+		},
+		{
+			name:                "name with unicode characters > 0x100",
+			input:               "花火",
+			expectedUnderscores: "__",
+			expectedDots:        "__",
+			// Dots-replacement does not know the difference between two replaced
+			// characters and a single underscore.
+			expectedUnescapedDots: "_",
+			expectedValue:         "U___82b1__706b_",
+		},
+	}
+
+	for _, scenario := range scenarios {
+		t.Run(scenario.name, func(t *testing.T) {
+			got := EscapeName(scenario.input, UnderscoreEscaping)
+			if got != scenario.expectedUnderscores {
+				t.Errorf("expected string output %s but got %s", scenario.expectedUnderscores, got)
+			}
+			// Unescaping with the underscore method is a noop.
+			got = UnescapeName(got, UnderscoreEscaping)
+			if got != scenario.expectedUnderscores {
+				t.Errorf("expected unescaped string output %s but got %s", scenario.expectedUnderscores, got)
+			}
+
+			got = EscapeName(scenario.input, DotsEscaping)
+			if got != scenario.expectedDots {
+				t.Errorf("expected string output %s but got %s", scenario.expectedDots, got)
+			}
+			got = UnescapeName(got, DotsEscaping)
+			if got != scenario.expectedUnescapedDots {
+				t.Errorf("expected unescaped string output %s but got %s", scenario.expectedUnescapedDots, got)
+			}
+
+			got = EscapeName(scenario.input, ValueEncodingEscaping)
+			if got != scenario.expectedValue {
+				t.Errorf("expected string output %s but got %s", scenario.expectedValue, got)
+			}
+			// Unescaped result should always be identical to the original input.
+			got = UnescapeName(got, ValueEncodingEscaping)
+			if got != scenario.input {
+				t.Errorf("expected unescaped string output %s but got %s", scenario.input, got)
+			}
+		})
+	}
+}
+
+func TestValueUnescapeErrors(t *testing.T) {
+	scenarios := []struct {
+		name     string
+		input    string
+		expected string
+	}{
+		{
+			name: "empty string",
+		},
+		{
+			name:     "basic case, no error",
+			input:    "U__no:unescapingrequired",
+			expected: "no:unescapingrequired",
+		},
+		{
+			name:     "capitals ok, no error",
+			input:    "U__capitals_2E_ok",
+			expected: "capitals.ok",
+		},
+		{
+			name:     "underscores, no error",
+			input:    "U__underscores__doubled__",
+			expected: "underscores_doubled_",
+		},
+		{
+			name:     "invalid single underscore",
+			input:    "U__underscores_doubled_",
+			expected: "U__underscores_doubled_",
+		},
+		{
+			name:     "invalid single underscore, 2",
+			input:    "U__underscores__doubled_",
+			expected: "U__underscores__doubled_",
+		},
+		{
+			name:     "giant fake utf-8 code",
+			input:    "U__my__hack_2e_attempt_872348732fabdabbab_",
+			expected: "U__my__hack_2e_attempt_872348732fabdabbab_",
+		},
+		{
+			name:     "trailing utf-8",
+			input:    "U__my__hack_2e",
+			expected: "U__my__hack_2e",
+		},
+		{
+			name:     "invalid utf-8 value",
+			input:    "U__bad__utf_2eg_",
+			expected: "U__bad__utf_2eg_",
+		},
+		{
+			name:     "surrogate utf-8 value",
+			input:    "U__bad__utf_D900_",
+			expected: "U__bad__utf_D900_",
+		},
+	}
+
+	for _, scenario := range scenarios {
+		t.Run(scenario.name, func(t *testing.T) {
+			got := UnescapeName(scenario.input, ValueEncodingEscaping)
+			if got != scenario.expected {
+				t.Errorf("expected unescaped string output %s but got %s", scenario.expected, got)
+			}
+		})
+	}
+}
+
+func TestEscapeMetricFamily(t *testing.T) {
+	scenarios := []struct {
+		name     string
+		input    *dto.MetricFamily
+		scheme   EscapingScheme
+		expected *dto.MetricFamily
+	}{
+		{
+			name:     "empty",
+			input:    &dto.MetricFamily{},
+			scheme:   ValueEncodingEscaping,
+			expected: &dto.MetricFamily{},
+		},
+		{
+			name:   "simple, no escaping needed",
+			scheme: ValueEncodingEscaping,
+			input: &dto.MetricFamily{
+				Name: proto.String("my_metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("my_metric"),
+							},
+							{
+								Name:  proto.String("some_label"),
+								Value: proto.String("labelvalue"),
+							},
+						},
+					},
+				},
+			},
+			expected: &dto.MetricFamily{
+				Name: proto.String("my_metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("my_metric"),
+							},
+							{
+								Name:  proto.String("some_label"),
+								Value: proto.String("labelvalue"),
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:   "label name escaping needed",
+			scheme: ValueEncodingEscaping,
+			input: &dto.MetricFamily{
+				Name: proto.String("my_metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("my_metric"),
+							},
+							{
+								Name:  proto.String("some.label"),
+								Value: proto.String("labelvalue"),
+							},
+						},
+					},
+				},
+			},
+			expected: &dto.MetricFamily{
+				Name: proto.String("my_metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("my_metric"),
+							},
+							{
+								Name:  proto.String("U__some_2e_label"),
+								Value: proto.String("labelvalue"),
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:   "counter, escaping needed",
+			scheme: ValueEncodingEscaping,
+			input: &dto.MetricFamily{
+				Name: proto.String("my.metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("my.metric"),
+							},
+							{
+								Name:  proto.String("some?label"),
+								Value: proto.String("label??value"),
+							},
+						},
+					},
+				},
+			},
+			expected: &dto.MetricFamily{
+				Name: proto.String("U__my_2e_metric"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_COUNTER.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Counter: &dto.Counter{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("U__my_2e_metric"),
+							},
+							{
+								Name:  proto.String("U__some_3f_label"),
+								Value: proto.String("label??value"),
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:   "gauge, escaping needed",
+			scheme: DotsEscaping,
+			input: &dto.MetricFamily{
+				Name: proto.String("unicode.and.dots.花火"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_GAUGE.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("unicode.and.dots.花火"),
+							},
+							{
+								Name:  proto.String("some_label"),
+								Value: proto.String("label??value"),
+							},
+						},
+					},
+				},
+			},
+			expected: &dto.MetricFamily{
+				Name: proto.String("unicode_dot_and_dot_dots_dot___"),
+				Help: proto.String("some help text"),
+				Type: dto.MetricType_GAUGE.Enum(),
+				Metric: []*dto.Metric{
+					{
+						Gauge: &dto.Gauge{
+							Value: proto.Float64(34.2),
+						},
+						Label: []*dto.LabelPair{
+							{
+								Name:  proto.String("__name__"),
+								Value: proto.String("unicode_dot_and_dot_dots_dot___"),
+							},
+							{
+								Name:  proto.String("some_label"),
+								Value: proto.String("label??value"),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	unexportList := []interface{}{dto.MetricFamily{}, dto.Metric{}, dto.LabelPair{}, dto.Counter{}, dto.Gauge{}}
+
+	for _, scenario := range scenarios {
+		t.Run(scenario.name, func(t *testing.T) {
+			original := proto.Clone(scenario.input)
+			got := EscapeMetricFamily(scenario.input, scenario.scheme)
+			if !cmp.Equal(scenario.expected, got, cmpopts.IgnoreUnexported(unexportList...)) {
+				t.Errorf("unexpected difference in escaped output:" + cmp.Diff(scenario.expected, got, cmpopts.IgnoreUnexported(unexportList...)))
+			}
+			if !cmp.Equal(scenario.input, original, cmpopts.IgnoreUnexported(unexportList...)) {
+				t.Errorf("input was mutated during escaping" + cmp.Diff(scenario.expected, got, cmpopts.IgnoreUnexported(unexportList...)))
+			}
+		})
+	}
+}
+
+// TestProtoFormatUnchanged checks to see if the proto format changed, in which
+// case EscapeMetricFamily will need to be updated.
+func TestProtoFormatUnchanged(t *testing.T) {
+	scenarios := []struct {
+		name         string
+		input        proto.Message
+		expectFields []string
+	}{
+		{
+			name:         "MetricFamily",
+			input:        &dto.MetricFamily{},
+			expectFields: []string{"name", "help", "type", "metric", "unit"},
+		},
+		{
+			name:         "Metric",
+			input:        &dto.Metric{},
+			expectFields: []string{"label", "gauge", "counter", "summary", "untyped", "histogram", "timestamp_ms"},
+		},
+		{
+			name:         "LabelPair",
+			input:        &dto.LabelPair{},
+			expectFields: []string{"name", "value"},
+		},
+	}
+
+	for _, scenario := range scenarios {
+		t.Run(scenario.name, func(t *testing.T) {
+			desc := scenario.input.ProtoReflect().Descriptor()
+			fields := desc.Fields()
+			if fields.Len() != len(scenario.expectFields) {
+				t.Errorf("dto.MetricFamily changed length, expected %d, got %d", len(scenario.expectFields), fields.Len())
+			}
+
+			for i := 0; i < fields.Len(); i++ {
+				got := fields.Get(i).TextName()
+				if got != scenario.expectFields[i] {
+					t.Errorf("dto.MetricFamily field mismatch, expected %s got %s", scenario.expectFields[i], got)
+				}
 			}
 		})
 	}
diff --git a/model/signature_test.go b/model/signature_test.go
index 0c79705..a075bdc 100644
--- a/model/signature_test.go
+++ b/model/signature_test.go
@@ -21,7 +21,7 @@ import (
 )
 
 func TestLabelsToSignature(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  map[string]string
 		out uint64
 	}{
@@ -45,7 +45,7 @@ func TestLabelsToSignature(t *testing.T) {
 }
 
 func TestMetricToFingerprint(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  LabelSet
 		out Fingerprint
 	}{
@@ -69,7 +69,7 @@ func TestMetricToFingerprint(t *testing.T) {
 }
 
 func TestMetricToFastFingerprint(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in  LabelSet
 		out Fingerprint
 	}{
@@ -93,7 +93,7 @@ func TestMetricToFastFingerprint(t *testing.T) {
 }
 
 func TestSignatureForLabels(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in     Metric
 		labels LabelNames
 		out    uint64
@@ -145,7 +145,7 @@ func TestSignatureForLabels(t *testing.T) {
 }
 
 func TestSignatureWithoutLabels(t *testing.T) {
-	var scenarios = []struct {
+	scenarios := []struct {
 		in     Metric
 		labels map[LabelName]struct{}
 		out    uint64
@@ -157,12 +157,12 @@ func TestSignatureWithoutLabels(t *testing.T) {
 		},
 		{
 			in:     Metric{"name": "garland, briggs", "fear": "love is not enough"},
-			labels: map[LabelName]struct{}{"fear": struct{}{}, "name": struct{}{}},
+			labels: map[LabelName]struct{}{"fear": {}, "name": {}},
 			out:    14695981039346656037,
 		},
 		{
 			in:     Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"},
-			labels: map[LabelName]struct{}{"foo": struct{}{}},
+			labels: map[LabelName]struct{}{"foo": {}},
 			out:    5799056148416392346,
 		},
 		{
diff --git a/model/silence_test.go b/model/silence_test.go
index be50298..4e4508d 100644
--- a/model/silence_test.go
+++ b/model/silence_test.go
@@ -20,9 +20,10 @@ import (
 )
 
 func TestMatcherValidate(t *testing.T) {
-	var cases = []struct {
-		matcher *Matcher
-		err     string
+	cases := []struct {
+		matcher   *Matcher
+		legacyErr string
+		utf8Err   string
 	}{
 		{
 			matcher: &Matcher{
@@ -42,46 +43,74 @@ func TestMatcherValidate(t *testing.T) {
 				Name:  "name!",
 				Value: "value",
 			},
-			err: "invalid name",
+			legacyErr: "invalid name",
 		},
 		{
 			matcher: &Matcher{
 				Name:  "",
 				Value: "value",
 			},
-			err: "invalid name",
+			legacyErr: "invalid name",
+			utf8Err:   "invalid name",
 		},
 		{
 			matcher: &Matcher{
 				Name:  "name",
 				Value: "value\xff",
 			},
-			err: "invalid value",
+			legacyErr: "invalid value",
+			utf8Err:   "invalid value",
 		},
 		{
 			matcher: &Matcher{
 				Name:  "name",
 				Value: "",
 			},
-			err: "invalid value",
+			legacyErr: "invalid value",
+			utf8Err:   "invalid value",
+		},
+		{
+			matcher: &Matcher{
+				Name:  "a\xc5z",
+				Value: "",
+			},
+			legacyErr: "invalid name",
+			utf8Err:   "invalid name",
 		},
 	}
 
 	for i, c := range cases {
-		err := c.matcher.Validate()
-		if err == nil {
-			if c.err == "" {
+		NameValidationScheme = LegacyValidation
+		legacyErr := c.matcher.Validate()
+		NameValidationScheme = UTF8Validation
+		utf8Err := c.matcher.Validate()
+		if legacyErr == nil && utf8Err == nil {
+			if c.legacyErr == "" && c.utf8Err == "" {
 				continue
 			}
-			t.Errorf("%d. Expected error %q but got none", i, c.err)
+			if c.legacyErr != "" {
+				t.Errorf("%d. Expected error for legacy validation %q but got none", i, c.legacyErr)
+			}
+			if c.utf8Err != "" {
+				t.Errorf("%d. Expected error for utf-8 validation %q but got none", i, c.utf8Err)
+			}
 			continue
 		}
-		if c.err == "" {
-			t.Errorf("%d. Expected no error but got %q", i, err)
-			continue
+		if legacyErr != nil {
+			if c.legacyErr == "" {
+				t.Errorf("%d. Expected no legacy validation error but got %q", i, legacyErr)
+			} else if !strings.Contains(legacyErr.Error(), c.legacyErr) {
+				t.Errorf("%d. Expected error to contain %q but got %q", i, c.legacyErr, legacyErr)
+			}
 		}
-		if !strings.Contains(err.Error(), c.err) {
-			t.Errorf("%d. Expected error to contain %q but got %q", i, c.err, err)
+		if utf8Err != nil {
+			if c.utf8Err == "" {
+				t.Errorf("%d. Expected no utf-8 validation error but got %q", i, utf8Err)
+				continue
+			}
+			if !strings.Contains(utf8Err.Error(), c.utf8Err) {
+				t.Errorf("%d. Expected error to contain %q but got %q", i, c.utf8Err, utf8Err)
+			}
 		}
 	}
 }
@@ -89,7 +118,7 @@ func TestMatcherValidate(t *testing.T) {
 func TestSilenceValidate(t *testing.T) {
 	ts := time.Now()
 
-	var cases = []struct {
+	cases := []struct {
 		sil *Silence
 		err string
 	}{
@@ -219,6 +248,7 @@ func TestSilenceValidate(t *testing.T) {
 	}
 
 	for i, c := range cases {
+		NameValidationScheme = LegacyValidation
 		err := c.sil.Validate()
 		if err == nil {
 			if c.err == "" {
diff --git a/model/time_test.go b/model/time_test.go
index b0703fd..f572412 100644
--- a/model/time_test.go
+++ b/model/time_test.go
@@ -88,7 +88,7 @@ func TestDuration(t *testing.T) {
 }
 
 func TestParseDuration(t *testing.T) {
-	var cases = []struct {
+	cases := []struct {
 		in  string
 		out time.Duration
 
@@ -159,7 +159,7 @@ func TestParseDuration(t *testing.T) {
 }
 
 func TestDuration_UnmarshalText(t *testing.T) {
-	var cases = []struct {
+	cases := []struct {
 		in  string
 		out time.Duration
 
@@ -232,7 +232,7 @@ func TestDuration_UnmarshalText(t *testing.T) {
 }
 
 func TestDuration_UnmarshalJSON(t *testing.T) {
-	var cases = []struct {
+	cases := []struct {
 		in  string
 		out time.Duration
 
@@ -242,43 +242,55 @@ func TestDuration_UnmarshalJSON(t *testing.T) {
 			in:             `"0"`,
 			out:            0,
 			expectedString: `"0s"`,
-		}, {
+		},
+		{
 			in:             `"0w"`,
 			out:            0,
 			expectedString: `"0s"`,
-		}, {
+		},
+		{
 			in:  `"0s"`,
 			out: 0,
-		}, {
+		},
+		{
 			in:  `"324ms"`,
 			out: 324 * time.Millisecond,
-		}, {
+		},
+		{
 			in:  `"3s"`,
 			out: 3 * time.Second,
-		}, {
+		},
+		{
 			in:  `"5m"`,
 			out: 5 * time.Minute,
-		}, {
+		},
+		{
 			in:  `"1h"`,
 			out: time.Hour,
-		}, {
+		},
+		{
 			in:  `"4d"`,
 			out: 4 * 24 * time.Hour,
-		}, {
+		},
+		{
 			in:  `"4d1h"`,
 			out: 4*24*time.Hour + time.Hour,
-		}, {
+		},
+		{
 			in:             `"14d"`,
 			out:            14 * 24 * time.Hour,
 			expectedString: `"2w"`,
-		}, {
+		},
+		{
 			in:  `"3w"`,
 			out: 3 * 7 * 24 * time.Hour,
-		}, {
+		},
+		{
 			in:             `"3w2d1h"`,
 			out:            3*7*24*time.Hour + 2*24*time.Hour + time.Hour,
 			expectedString: `"23d1h"`,
-		}, {
+		},
+		{
 			in:  `"10y"`,
 			out: 10 * 365 * 24 * time.Hour,
 		},
@@ -312,7 +324,7 @@ func TestDuration_UnmarshalJSON(t *testing.T) {
 }
 
 func TestParseBadDuration(t *testing.T) {
-	var cases = []string{
+	cases := []string{
 		"1",
 		"1y1m1d",
 		"-1w",
@@ -362,10 +374,8 @@ func TestTimeJSON(t *testing.T) {
 			if !test.in.Equal(tm) {
 				t.Fatalf("Mismatch after Unmarshal expected=%v actual=%v", test.in, tm)
 			}
-
 		})
 	}
-
 }
 
 func BenchmarkParseDuration(b *testing.B) {
diff --git a/model/value_test.go b/model/value_test.go
index 1eeae33..c57efb1 100644
--- a/model/value_test.go
+++ b/model/value_test.go
@@ -153,7 +153,6 @@ func TestEqualSamples(t *testing.T) {
 			t.Errorf("Comparing %s, %v and %v: got %t, want %t", name, test.in1, test.in2, got, test.want)
 		}
 	}
-
 }
 
 func TestScalarJSON(t *testing.T) {
diff --git a/server/static_file_server_test.go b/server/static_file_server_test.go
index a75936b..78fe56d 100644
--- a/server/static_file_server_test.go
+++ b/server/static_file_server_test.go
@@ -67,7 +67,6 @@ func TestServeHttp(t *testing.T) {
 		t.Run(c.name, func(t *testing.T) {
 			rr := httptest.NewRecorder()
 			req, err := http.NewRequest("GET", "http://localhost/"+c.path, nil)
-
 			if err != nil {
 				t.Fatal(err)
 			}