1import os
2import re
3
4METRIC_NAME_RE = re.compile(r'^[a-zA-Z_:][a-zA-Z0-9_:]*$')
5METRIC_LABEL_NAME_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
6RESERVED_METRIC_LABEL_NAME_RE = re.compile(r'^__.*$')
7
8
9def _init_legacy_validation() -> bool:
10 """Retrieve name validation setting from environment."""
11 return os.environ.get("PROMETHEUS_LEGACY_NAME_VALIDATION", 'False').lower() in ('true', '1', 't')
12
13
14_legacy_validation = _init_legacy_validation()
15
16
17def get_legacy_validation() -> bool:
18 """Return the current status of the legacy validation setting."""
19 global _legacy_validation
20 return _legacy_validation
21
22
23def disable_legacy_validation():
24 """Disable legacy name validation, instead allowing all UTF8 characters."""
25 global _legacy_validation
26 _legacy_validation = False
27
28
29def enable_legacy_validation():
30 """Enable legacy name validation instead of allowing all UTF8 characters."""
31 global _legacy_validation
32 _legacy_validation = True
33
34
35def _validate_metric_name(name: str) -> None:
36 """Raises ValueError if the provided name is not a valid metric name.
37
38 This check uses the global legacy validation setting to determine the validation scheme.
39 """
40 if not name:
41 raise ValueError("metric name cannot be empty")
42 global _legacy_validation
43 if _legacy_validation:
44 if not METRIC_NAME_RE.match(name):
45 raise ValueError("invalid metric name " + name)
46 try:
47 name.encode('utf-8')
48 except UnicodeDecodeError:
49 raise ValueError("invalid metric name " + name)
50
51
52def _is_valid_legacy_metric_name(name: str) -> bool:
53 """Returns true if the provided metric name conforms to the legacy validation scheme."""
54 return METRIC_NAME_RE.match(name) is not None
55
56
57def _validate_metric_label_name_token(tok: str) -> None:
58 """Raises ValueError if a parsed label name token is invalid.
59
60 UTF-8 names must be quoted.
61 """
62 if not tok:
63 raise ValueError("invalid label name token " + tok)
64 global _legacy_validation
65 quoted = tok[0] == '"' and tok[-1] == '"'
66 if not quoted or _legacy_validation:
67 if not METRIC_LABEL_NAME_RE.match(tok):
68 raise ValueError("invalid label name token " + tok)
69 return
70 try:
71 tok.encode('utf-8')
72 except UnicodeDecodeError:
73 raise ValueError("invalid label name token " + tok)
74
75
76def _validate_labelname(l):
77 """Raises ValueError if the provided name is not a valid label name.
78
79 This check uses the global legacy validation setting to determine the validation scheme.
80 """
81 if get_legacy_validation():
82 if not METRIC_LABEL_NAME_RE.match(l):
83 raise ValueError('Invalid label metric name: ' + l)
84 if RESERVED_METRIC_LABEL_NAME_RE.match(l):
85 raise ValueError('Reserved label metric name: ' + l)
86 else:
87 try:
88 l.encode('utf-8')
89 except UnicodeDecodeError:
90 raise ValueError('Invalid label metric name: ' + l)
91 if RESERVED_METRIC_LABEL_NAME_RE.match(l):
92 raise ValueError('Reserved label metric name: ' + l)
93
94
95def _is_valid_legacy_labelname(l: str) -> bool:
96 """Returns true if the provided label name conforms to the legacy validation scheme."""
97 if METRIC_LABEL_NAME_RE.match(l) is None:
98 return False
99 return RESERVED_METRIC_LABEL_NAME_RE.match(l) is None
100
101
102def _validate_labelnames(cls, labelnames):
103 """Raises ValueError if any of the provided names is not a valid label name.
104
105 This check uses the global legacy validation setting to determine the validation scheme.
106 """
107 labelnames = tuple(labelnames)
108 for l in labelnames:
109 _validate_labelname(l)
110 if l in cls._reserved_labelnames:
111 raise ValueError('Reserved label methe fric name: ' + l)
112 return labelnames
113
114
115def _validate_exemplar(exemplar):
116 """Raises ValueError if the exemplar is invalid."""
117 runes = 0
118 for k, v in exemplar.items():
119 _validate_labelname(k)
120 runes += len(k)
121 runes += len(v)
122 if runes > 128:
123 raise ValueError('Exemplar labels have %d UTF-8 characters, exceeding the limit of 128')