2026-01-05 07:46:21 -05:00
// Copyright The Prometheus Authors
2016-12-28 13:04:59 -05:00
// 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 relabel
import (
"crypto/md5"
2023-02-28 12:36:58 -05:00
"encoding/binary"
2024-11-24 13:46:24 -05:00
"encoding/json"
2024-11-03 07:15:51 -05:00
"errors"
2016-12-28 13:04:59 -05:00
"fmt"
2024-02-28 20:33:17 -05:00
"strconv"
2016-12-28 13:04:59 -05:00
"strings"
2022-02-12 18:58:27 -05:00
"github.com/grafana/regexp"
2016-12-28 13:04:59 -05:00
"github.com/prometheus/common/model"
2021-11-08 09:23:17 -05:00
"github.com/prometheus/prometheus/model/labels"
2016-12-28 13:04:59 -05:00
)
2018-12-18 06:26:36 -05:00
var (
2025-01-27 04:49:50 -05:00
// relabelTargetLegacy allows targeting labels with legacy Prometheus character set, plus ${<var>} variables for dynamic characters from source the metrics.
2025-01-23 03:53:59 -05:00
relabelTargetLegacy = regexp . MustCompile ( ` ^(?:(?:[a-zA-Z_]|\$(?:\ { \w+\}|\w+))+\w*)+$ ` )
2018-12-18 06:26:36 -05:00
DefaultRelabelConfig = Config {
Action : Replace ,
Separator : ";" ,
Regex : MustNewRegexp ( "(.*)" ) ,
Replacement : "$1" ,
}
)
// Action is the action to be performed on relabeling.
type Action string
const (
// Replace performs a regex replacement.
Replace Action = "replace"
// Keep drops targets for which the input does not match the regex.
Keep Action = "keep"
// Drop drops targets for which the input does match the regex.
Drop Action = "drop"
2022-11-10 08:17:47 -05:00
// KeepEqual drops targets for which the input does not match the target.
KeepEqual Action = "keepequal"
2024-05-08 11:57:09 -04:00
// DropEqual drops targets for which the input does match the target.
2022-11-10 08:17:47 -05:00
DropEqual Action = "dropequal"
2018-12-18 06:26:36 -05:00
// HashMod sets a label to the modulus of a hash of labels.
HashMod Action = "hashmod"
// LabelMap copies labels to other labelnames based on a regex.
LabelMap Action = "labelmap"
// LabelDrop drops any label matching the regex.
LabelDrop Action = "labeldrop"
// LabelKeep drops any label not matching the regex.
LabelKeep Action = "labelkeep"
2022-05-03 04:09:53 -04:00
// Lowercase maps input letters to their lower case.
Lowercase Action = "lowercase"
// Uppercase maps input letters to their upper case.
Uppercase Action = "uppercase"
2018-12-18 06:26:36 -05:00
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
2025-08-27 08:38:54 -04:00
func ( a * Action ) UnmarshalYAML ( unmarshal func ( any ) error ) error {
2018-12-18 06:26:36 -05:00
var s string
if err := unmarshal ( & s ) ; err != nil {
return err
}
switch act := Action ( strings . ToLower ( s ) ) ; act {
2022-11-10 08:17:47 -05:00
case Replace , Keep , Drop , HashMod , LabelMap , LabelDrop , LabelKeep , Lowercase , Uppercase , KeepEqual , DropEqual :
2018-12-18 06:26:36 -05:00
* a = act
return nil
}
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "unknown relabel action %q" , s )
2018-12-18 06:26:36 -05:00
}
// Config is the configuration for relabeling of target label sets.
type Config struct {
// A list of labels from which values are taken and concatenated
// with the configured separator in order.
2025-10-15 09:08:07 -04:00
SourceLabels model . LabelNames ` yaml:"source_labels,flow,omitempty" json:"source_labels,omitempty" `
2018-12-18 06:26:36 -05:00
// Separator is the string between concatenated values from the source labels.
2024-11-24 13:46:24 -05:00
Separator string ` yaml:"separator,omitempty" json:"separator,omitempty" `
2018-12-18 06:26:36 -05:00
// Regex against which the concatenation is matched.
2024-11-24 13:46:24 -05:00
Regex Regexp ` yaml:"regex,omitempty" json:"regex,omitempty" `
2018-12-18 06:26:36 -05:00
// Modulus to take of the hash of concatenated values from the source labels.
2024-11-24 13:46:24 -05:00
Modulus uint64 ` yaml:"modulus,omitempty" json:"modulus,omitempty" `
2018-12-18 06:26:36 -05:00
// TargetLabel is the label to which the resulting string is written in a replacement.
// Regexp interpolation is allowed for the replace action.
2025-10-15 09:08:07 -04:00
TargetLabel string ` yaml:"target_label,omitempty" json:"target_label,omitempty" `
2018-12-18 06:26:36 -05:00
// Replacement is the regex replacement pattern to be used.
2024-11-24 13:46:24 -05:00
Replacement string ` yaml:"replacement,omitempty" json:"replacement,omitempty" `
2018-12-18 06:26:36 -05:00
// Action is the action to be performed for the relabeling.
2024-11-24 13:46:24 -05:00
Action Action ` yaml:"action,omitempty" json:"action,omitempty" `
2025-08-18 04:09:00 -04:00
// NameValidationScheme to use when validating labels.
NameValidationScheme model . ValidationScheme ` yaml:"-" json:"-" `
2018-12-18 06:26:36 -05:00
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
2025-08-27 08:38:54 -04:00
func ( c * Config ) UnmarshalYAML ( unmarshal func ( any ) error ) error {
2018-12-18 06:26:36 -05:00
* c = DefaultRelabelConfig
type plain Config
if err := unmarshal ( ( * plain ) ( c ) ) ; err != nil {
return err
}
if c . Regex . Regexp == nil {
c . Regex = MustNewRegexp ( "" )
}
2025-08-18 04:09:00 -04:00
return nil
2023-12-18 09:52:42 -05:00
}
2025-08-18 04:09:00 -04:00
func ( c * Config ) Validate ( nameValidationScheme model . ValidationScheme ) error {
2021-08-31 11:52:57 -04:00
if c . Action == "" {
2024-11-03 07:15:51 -05:00
return errors . New ( "relabel action cannot be empty" )
2021-08-31 11:52:57 -04:00
}
2018-12-18 06:26:36 -05:00
if c . Modulus == 0 && c . Action == HashMod {
2024-11-03 07:15:51 -05:00
return errors . New ( "relabel configuration for hashmod requires non-zero modulus" )
2018-12-18 06:26:36 -05:00
}
2022-11-10 08:17:47 -05:00
if ( c . Action == Replace || c . Action == HashMod || c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && c . TargetLabel == "" {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "relabel configuration for %s action requires 'target_label' value" , c . Action )
2018-12-18 06:26:36 -05:00
}
2025-08-18 04:09:00 -04:00
// Relabel config validation scheme matches global if left blank.
switch c . NameValidationScheme {
case model . LegacyValidation , model . UTF8Validation :
case model . UnsetValidation :
c . NameValidationScheme = nameValidationScheme
default :
return fmt . Errorf ( "unknown relabel config name validation method specified, must be either '', 'legacy' or 'utf8', got %s" , c . NameValidationScheme )
}
if c . Action == Replace && ! varInRegexTemplate ( c . TargetLabel ) && ! c . NameValidationScheme . IsValidLabelName ( c . TargetLabel ) {
2023-12-18 09:58:56 -05:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
}
2025-01-23 03:53:59 -05:00
2025-01-27 04:49:50 -05:00
isValidLabelNameWithRegexVarFn := func ( value string ) bool {
// UTF-8 allows ${} characters, so standard validation allow $variables by default.
// TODO(bwplotka): Relabelling users cannot put $ and ${<...>} characters in metric names or values.
// Design escaping mechanism to allow that, once valid use case appears.
2025-10-20 07:54:31 -04:00
switch c . NameValidationScheme {
case model . UTF8Validation :
return c . NameValidationScheme . IsValidLabelName ( value )
default :
// For legacy validation, use the legacy regex that allows $variables.
return relabelTargetLegacy . MatchString ( value )
}
2025-01-27 04:49:50 -05:00
}
if c . Action == Replace && varInRegexTemplate ( c . TargetLabel ) && ! isValidLabelNameWithRegexVarFn ( c . TargetLabel ) {
2023-12-18 09:58:56 -05:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
}
2025-08-18 04:09:00 -04:00
if ( c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && ! c . NameValidationScheme . IsValidLabelName ( c . TargetLabel ) {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
2018-12-18 06:26:36 -05:00
}
2022-11-10 08:17:47 -05:00
if ( c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && c . Replacement != DefaultRelabelConfig . Replacement {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "'replacement' can not be set for %s action" , c . Action )
2022-05-03 04:09:53 -04:00
}
2025-01-27 04:49:50 -05:00
if c . Action == LabelMap && ! isValidLabelNameWithRegexVarFn ( c . Replacement ) {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "%q is invalid 'replacement' for %s action" , c . Replacement , c . Action )
2018-12-18 06:26:36 -05:00
}
2025-08-18 04:09:00 -04:00
if c . Action == HashMod && ! c . NameValidationScheme . IsValidLabelName ( c . TargetLabel ) {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
2018-12-18 06:26:36 -05:00
}
2022-11-10 08:17:47 -05:00
if c . Action == DropEqual || c . Action == KeepEqual {
if c . Regex != DefaultRelabelConfig . Regex ||
c . Modulus != DefaultRelabelConfig . Modulus ||
c . Separator != DefaultRelabelConfig . Separator ||
c . Replacement != DefaultRelabelConfig . Replacement {
return fmt . Errorf ( "%s action requires only 'source_labels' and `target_label`, and no other fields" , c . Action )
}
}
2018-12-18 06:26:36 -05:00
if c . Action == LabelDrop || c . Action == LabelKeep {
if c . SourceLabels != nil ||
c . TargetLabel != DefaultRelabelConfig . TargetLabel ||
c . Modulus != DefaultRelabelConfig . Modulus ||
c . Separator != DefaultRelabelConfig . Separator ||
c . Replacement != DefaultRelabelConfig . Replacement {
2022-06-27 15:29:19 -04:00
return fmt . Errorf ( "%s action requires only 'regex', and no other fields" , c . Action )
2018-12-18 06:26:36 -05:00
}
}
return nil
}
// Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
type Regexp struct {
* regexp . Regexp
}
// NewRegexp creates a new anchored Regexp and returns an error if the
// passed-in regular expression does not compile.
func NewRegexp ( s string ) ( Regexp , error ) {
2024-07-24 05:49:10 -04:00
regex , err := regexp . Compile ( "^(?s:" + s + ")$" )
2022-06-13 09:00:48 -04:00
return Regexp { Regexp : regex } , err
2018-12-18 06:26:36 -05:00
}
// MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile.
func MustNewRegexp ( s string ) Regexp {
re , err := NewRegexp ( s )
if err != nil {
panic ( err )
}
return re
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
2025-08-27 08:38:54 -04:00
func ( re * Regexp ) UnmarshalYAML ( unmarshal func ( any ) error ) error {
2018-12-18 06:26:36 -05:00
var s string
if err := unmarshal ( & s ) ; err != nil {
return err
}
r , err := NewRegexp ( s )
if err != nil {
return err
}
* re = r
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
2025-08-27 08:38:54 -04:00
func ( re Regexp ) MarshalYAML ( ) ( any , error ) {
2022-06-13 09:00:48 -04:00
if re . String ( ) != "" {
return re . String ( ) , nil
2018-12-18 06:26:36 -05:00
}
return nil , nil
}
2024-11-24 13:46:24 -05:00
// UnmarshalJSON implements the json.Unmarshaler interface.
func ( re * Regexp ) UnmarshalJSON ( b [ ] byte ) error {
var s string
if err := json . Unmarshal ( b , & s ) ; err != nil {
return err
}
r , err := NewRegexp ( s )
if err != nil {
return err
}
* re = r
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func ( re Regexp ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( re . String ( ) )
}
2024-04-30 10:45:56 -04:00
// IsZero implements the yaml.IsZeroer interface.
func ( re Regexp ) IsZero ( ) bool {
return re . Regexp == DefaultRelabelConfig . Regex . Regexp
}
2022-06-13 09:00:48 -04:00
// String returns the original string used to compile the regular expression.
func ( re Regexp ) String ( ) string {
2024-07-26 08:55:39 -04:00
if re . Regexp == nil {
return ""
}
2022-06-13 09:00:48 -04:00
str := re . Regexp . String ( )
2024-07-24 05:49:10 -04:00
// Trim the anchor `^(?s:` prefix and `)$` suffix.
return str [ 5 : len ( str ) - 2 ]
2022-06-13 09:00:48 -04:00
}
2025-11-13 13:31:54 -05:00
// ProcessBuilder applies relabeling configurations (rules) to the labels in lb.
// The rules are applied in order of input. Returns false if the rule says to drop.
2023-02-28 14:09:58 -05:00
func ProcessBuilder ( lb * labels . Builder , cfgs ... * Config ) ( keep bool ) {
2016-12-28 13:04:59 -05:00
for _ , cfg := range cfgs {
2023-02-28 14:09:58 -05:00
keep = relabel ( cfg , lb )
2022-03-09 17:13:03 -05:00
if ! keep {
2023-02-28 14:09:58 -05:00
return false
2016-12-28 13:04:59 -05:00
}
}
2023-02-28 14:09:58 -05:00
return true
2016-12-28 13:04:59 -05:00
}
2023-02-28 14:09:58 -05:00
func relabel ( cfg * Config , lb * labels . Builder ) ( keep bool ) {
2022-08-19 05:57:52 -04:00
var va [ 16 ] string
values := va [ : 0 ]
if len ( cfg . SourceLabels ) > cap ( values ) {
values = make ( [ ] string , 0 , len ( cfg . SourceLabels ) )
}
2016-12-28 13:04:59 -05:00
for _ , ln := range cfg . SourceLabels {
2023-02-28 14:09:58 -05:00
values = append ( values , lb . Get ( string ( ln ) ) )
2016-12-28 13:04:59 -05:00
}
val := strings . Join ( values , cfg . Separator )
switch cfg . Action {
2018-12-18 06:26:36 -05:00
case Drop :
2016-12-28 13:04:59 -05:00
if cfg . Regex . MatchString ( val ) {
2023-02-28 14:09:58 -05:00
return false
2016-12-28 13:04:59 -05:00
}
2018-12-18 06:26:36 -05:00
case Keep :
2016-12-28 13:04:59 -05:00
if ! cfg . Regex . MatchString ( val ) {
2023-02-28 14:09:58 -05:00
return false
2016-12-28 13:04:59 -05:00
}
2022-11-10 08:17:47 -05:00
case DropEqual :
2023-02-28 14:09:58 -05:00
if lb . Get ( cfg . TargetLabel ) == val {
return false
2022-11-10 08:17:47 -05:00
}
case KeepEqual :
2023-02-28 14:09:58 -05:00
if lb . Get ( cfg . TargetLabel ) != val {
return false
2016-12-28 13:04:59 -05:00
}
2018-12-18 06:26:36 -05:00
case Replace :
2023-03-24 05:18:24 -04:00
// Fast path to add or delete label pair.
if val == "" && cfg . Regex == DefaultRelabelConfig . Regex &&
! varInRegexTemplate ( cfg . TargetLabel ) && ! varInRegexTemplate ( cfg . Replacement ) {
2023-12-25 02:14:25 -05:00
lb . Set ( cfg . TargetLabel , cfg . Replacement )
2023-03-24 05:18:24 -04:00
break
}
2016-12-28 13:04:59 -05:00
indexes := cfg . Regex . FindStringSubmatchIndex ( val )
// If there is no match no replacement must take place.
if indexes == nil {
break
}
2025-08-18 04:09:00 -04:00
target := string ( cfg . Regex . ExpandString ( [ ] byte { } , cfg . TargetLabel , val , indexes ) )
if ! cfg . NameValidationScheme . IsValidLabelName ( target ) {
2016-12-28 13:04:59 -05:00
break
}
res := cfg . Regex . ExpandString ( [ ] byte { } , cfg . Replacement , val , indexes )
if len ( res ) == 0 {
2025-08-18 04:09:00 -04:00
lb . Del ( target )
2016-12-28 13:04:59 -05:00
break
}
2025-08-18 04:09:00 -04:00
lb . Set ( target , string ( res ) )
2022-05-03 04:09:53 -04:00
case Lowercase :
lb . Set ( cfg . TargetLabel , strings . ToLower ( val ) )
case Uppercase :
lb . Set ( cfg . TargetLabel , strings . ToUpper ( val ) )
2018-12-18 06:26:36 -05:00
case HashMod :
2023-02-28 12:36:58 -05:00
hash := md5 . Sum ( [ ] byte ( val ) )
// Use only the last 8 bytes of the hash to give the same result as earlier versions of this code.
mod := binary . BigEndian . Uint64 ( hash [ 8 : ] ) % cfg . Modulus
2024-02-28 20:33:17 -05:00
lb . Set ( cfg . TargetLabel , strconv . FormatUint ( mod , 10 ) )
2018-12-18 06:26:36 -05:00
case LabelMap :
2023-02-28 14:09:58 -05:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 13:04:59 -05:00
if cfg . Regex . MatchString ( l . Name ) {
res := cfg . Regex . ReplaceAllString ( l . Name , cfg . Replacement )
lb . Set ( res , l . Value )
}
2022-03-09 17:13:03 -05:00
} )
2018-12-18 06:26:36 -05:00
case LabelDrop :
2023-02-28 14:09:58 -05:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 13:04:59 -05:00
if cfg . Regex . MatchString ( l . Name ) {
lb . Del ( l . Name )
}
2022-03-09 17:13:03 -05:00
} )
2018-12-18 06:26:36 -05:00
case LabelKeep :
2023-02-28 14:09:58 -05:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 13:04:59 -05:00
if ! cfg . Regex . MatchString ( l . Name ) {
lb . Del ( l . Name )
}
2022-03-09 17:13:03 -05:00
} )
2016-12-28 13:04:59 -05:00
default :
2022-06-27 15:29:19 -04:00
panic ( fmt . Errorf ( "relabel: unknown relabel action type %q" , cfg . Action ) )
2016-12-28 13:04:59 -05:00
}
2023-02-28 14:09:58 -05:00
return true
2016-12-28 13:04:59 -05:00
}
2023-03-24 05:18:24 -04:00
func varInRegexTemplate ( template string ) bool {
return strings . Contains ( template , "$" )
}