// Copyright 2013 The Prometheus Authors// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.package modelimport (dto)var (// NameValidationScheme determines the global default method of the name // validation to be used by all calls to IsValidMetricName() and LabelName // IsValid(). // // Deprecated: This variable should not be used and might be removed in the // far future. If you wish to stick to the legacy name validation use // `IsValidLegacyMetricName()` and `LabelName.IsValidLegacy()` methods // instead. This variable is here as an escape hatch for emergency cases, // given the recent change from `LegacyValidation` to `UTF8Validation`, e.g., // to delay UTF-8 migrations in time or aid in debugging unforeseen results of // the change. In such a case, a temporary assignment to `LegacyValidation` // value in the `init()` function in your main.go or so, could be considered. // // Historically we opted for a global variable for feature gating different // validation schemes in operations that were not otherwise easily adjustable // (e.g. Labels yaml unmarshaling). That could have been a mistake, a separate // Labels structure or package might have been a better choice. Given the // change was made and many upgraded the common already, we live this as-is // with this warning and learning for the future.NameValidationScheme = UTF8Validation// 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 should not be set to the NoEscaping value. That string // is used in content negotiation to indicate that a system supports UTF-8 and // has that feature enabled.NameEscapingScheme = UnderscoreEscaping)// ValidationScheme is a Go enum for determining how metric and label names will// be validated by this library.typeValidationSchemeintconst (// LegacyValidation is a setting that requires that all metric and label names // conform to the original Prometheus character requirements described by // MetricNameRE and LabelNameRE.LegacyValidationValidationScheme = iota// UTF8Validation only requires that metric and label names be valid UTF-8 // strings.UTF8Validation)typeEscapingSchemeintconst (// 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.NoEscapingEscapingScheme = 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.varMetricNameRE = 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.typeMetricLabelSet// Equal compares the metrics.func ( Metric) ( Metric) bool {returnLabelSet().Equal(LabelSet())}// Before compares the metrics' underlying label sets.func ( Metric) ( Metric) bool {returnLabelSet().Before(LabelSet())}// Clone returns a copy of the Metric.func ( Metric) () Metric { := make(Metric, len())for , := range { [] = }return}func ( Metric) () string { , := [MetricNameLabel] := len() - 1if ! { = len() } := make([]string, 0, )for , := range {if != MetricNameLabel { = append(, fmt.Sprintf("%s=%q", , )) } }switch {case0:if {returnstring() }return"{}"default:sort.Strings()returnfmt.Sprintf("%s{%s}", , strings.Join(, ", ")) }}// Fingerprint returns a Metric's Fingerprint.func ( Metric) () Fingerprint {returnLabelSet().Fingerprint()}// FastFingerprint returns a Metric's Fingerprint calculated by a faster hashing// algorithm, which is, however, more susceptible to hash collisions.func ( Metric) () Fingerprint {returnLabelSet().FastFingerprint()}// 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 ( LabelValue) bool {switchNameValidationScheme {caseLegacyValidation:returnIsValidLegacyMetricName(string())caseUTF8Validation:iflen() == 0 {returnfalse }returnutf8.ValidString(string())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 ( string) bool {iflen() == 0 {returnfalse }for , := range {if !isValidLegacyRune(, ) {returnfalse } }returntrue}// 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 ( *dto.MetricFamily, EscapingScheme) *dto.MetricFamily {if == nil {returnnil }if == NoEscaping {return } := &dto.MetricFamily{Help: .Help,Type: .Type,Unit: .Unit, }// If the name is nil, copy as-is, don't try to escape.if .Name == nil || IsValidLegacyMetricName(.GetName()) { .Name = .Name } else { .Name = proto.String(EscapeName(.GetName(), )) }for , := range .Metric {if !metricNeedsEscaping() { .Metric = append(.Metric, )continue } := &dto.Metric{Gauge: .Gauge,Counter: .Counter,Summary: .Summary,Untyped: .Untyped,Histogram: .Histogram,TimestampMs: .TimestampMs, }for , := range .Label {if .GetName() == MetricNameLabel {if .Value == nil || IsValidLegacyMetricName(.GetValue()) { .Label = append(.Label, )continue } .Label = append(.Label, &dto.LabelPair{Name: proto.String(MetricNameLabel),Value: proto.String(EscapeName(.GetValue(), )), })continue }if .Name == nil || IsValidLegacyMetricName(.GetName()) { .Label = append(.Label, )continue } .Label = append(.Label, &dto.LabelPair{Name: proto.String(EscapeName(.GetName(), )),Value: .Value, }) } .Metric = append(.Metric, ) }return}func metricNeedsEscaping( *dto.Metric) bool {for , := range .Label {if .GetName() == MetricNameLabel && !IsValidLegacyMetricName(.GetValue()) {returntrue }if !IsValidLegacyMetricName(.GetName()) {returntrue } }returnfalse}// 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 ( string, EscapingScheme) string {iflen() == 0 {return }varstrings.Builderswitch {caseNoEscaping:returncaseUnderscoreEscaping:ifIsValidLegacyMetricName() {return }for , := range {ifisValidLegacyRune(, ) { .WriteRune() } else { .WriteRune('_') } }return .String()caseDotsEscaping:// Do not early return for legacy valid names, we still escape underscores.for , := range {if == '_' { .WriteString("__") } elseif == '.' { .WriteString("_dot_") } elseifisValidLegacyRune(, ) { .WriteRune() } else { .WriteString("__") } }return .String()caseValueEncodingEscaping:ifIsValidLegacyMetricName() {return } .WriteString("U__")for , := range {if == '_' { .WriteString("__") } elseifisValidLegacyRune(, ) { .WriteRune() } elseif !utf8.ValidRune() { .WriteString("_FFFD_") } else { .WriteRune('_') .WriteString(strconv.FormatInt(int64(), 16)) .WriteRune('_') } }return .String()default:panic(fmt.Sprintf("invalid escaping scheme %d", )) }}// lower function taken from strconv.atoifunc lower( byte) byte {return | ('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 ( string, EscapingScheme) string {iflen() == 0 {return }switch {caseNoEscaping:returncaseUnderscoreEscaping:// It is not possible to unescape from underscore replacement.returncaseDotsEscaping: = strings.ReplaceAll(, "_dot_", ".") = strings.ReplaceAll(, "__", "_")returncaseValueEncodingEscaping: , := strings.CutPrefix(, "U__")if ! {return }varstrings.Builder :for := 0; < len(); ++ {// All non-underscores are treated normally.if [] != '_' { .WriteByte([])continue } ++if >= len() {return }// A double underscore is a single underscore.if [] == '_' { .WriteByte('_')continue }// We think we are in a UTF-8 code, process it.varuintfor := 0; < len(); ++ {// This is too many characters for a utf8 value based on the MaxRune // value of '\U0010FFFF'.if >= 6 {return }// Found a closing underscore, convert to a rune, check validity, and append.if [] == '_' { := rune()if !utf8.ValidRune() {return } .WriteRune()continue } := lower([]) *= 16if >= '0' && <= '9' { += uint() - '0' } elseif >= 'a' && <= 'f' { += uint() - 'a' + 10 } else {return } ++ }// Didn't find closing underscore, invalid.return }return .String()default:panic(fmt.Sprintf("invalid escaping scheme %d", )) }}func isValidLegacyRune( rune, int) bool {return ( >= 'a' && <= 'z') || ( >= 'A' && <= 'Z') || == '_' || == ':' || ( >= '0' && <= '9' && > 0)}func ( EscapingScheme) () string {switch {caseNoEscaping:returnAllowUTF8caseUnderscoreEscaping:returnEscapeUnderscorescaseDotsEscaping:returnEscapeDotscaseValueEncodingEscaping:returnEscapeValuesdefault:panic(fmt.Sprintf("unknown format scheme %d", )) }}func ( string) (EscapingScheme, error) {if == "" {returnNoEscaping, errors.New("got empty string instead of escaping scheme") }switch {caseAllowUTF8:returnNoEscaping, nilcaseEscapeUnderscores:returnUnderscoreEscaping, nilcaseEscapeDots:returnDotsEscaping, nilcaseEscapeValues:returnValueEncodingEscaping, nildefault:returnNoEscaping, fmt.Errorf("unknown format scheme %s", ) }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.