22
33
44from ..utils import floatToGoString
5+ from ..validation import _is_valid_legacy_labelname , _is_valid_legacy_metric_name
56
67CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=1.0.0; charset=utf-8'
78"""Content type of the latest OpenMetrics text format"""
@@ -24,18 +25,27 @@ def generate_latest(registry):
2425 try :
2526 mname = metric .name
2627 output .append ('# HELP {} {}\n ' .format (
27- mname , metric .documentation . replace ( ' \\ ' , r'\\' ). replace ( ' \n ' , r'\n' ). replace ( '"' , r'\"' )))
28- output .append (f'# TYPE { mname } { metric .type } \n ' )
28+ escape_metric_name ( mname ), _escape ( metric .documentation )))
29+ output .append (f'# TYPE { escape_metric_name ( mname ) } { metric .type } \n ' )
2930 if metric .unit :
30- output .append (f'# UNIT { mname } { metric .unit } \n ' )
31+ output .append (f'# UNIT { escape_metric_name ( mname ) } { metric .unit } \n ' )
3132 for s in metric .samples :
32- if s .labels :
33- labelstr = '{{{0}}}' .format (',' .join (
34- ['{}="{}"' .format (
35- k , v .replace ('\\ ' , r'\\' ).replace ('\n ' , r'\n' ).replace ('"' , r'\"' ))
36- for k , v in sorted (s .labels .items ())]))
33+ if not _is_valid_legacy_metric_name (s .name ):
34+ labelstr = escape_metric_name (s .name )
35+ if s .labels :
36+ labelstr += ', '
3737 else :
3838 labelstr = ''
39+
40+ if s .labels :
41+ items = sorted (s .labels .items ())
42+ labelstr += ',' .join (
43+ ['{}="{}"' .format (
44+ escape_label_name (k ), _escape (v ))
45+ for k , v in items ])
46+ if labelstr :
47+ labelstr = "{" + labelstr + "}"
48+
3949 if s .exemplar :
4050 if not _is_valid_exemplar_metric (metric , s ):
4151 raise ValueError (f"Metric { metric .name } has exemplars, but is not a histogram bucket or counter" )
@@ -59,16 +69,47 @@ def generate_latest(registry):
5969 timestamp = ''
6070 if s .timestamp is not None :
6171 timestamp = f' { s .timestamp } '
62- output .append ('{}{} {}{}{}\n ' .format (
63- s .name ,
64- labelstr ,
65- floatToGoString (s .value ),
66- timestamp ,
67- exemplarstr ,
68- ))
72+ if _is_valid_legacy_metric_name (s .name ):
73+ output .append ('{}{} {}{}{}\n ' .format (
74+ s .name ,
75+ labelstr ,
76+ floatToGoString (s .value ),
77+ timestamp ,
78+ exemplarstr ,
79+ ))
80+ else :
81+ output .append ('{} {}{}{}\n ' .format (
82+ labelstr ,
83+ floatToGoString (s .value ),
84+ timestamp ,
85+ exemplarstr ,
86+ ))
6987 except Exception as exception :
7088 exception .args = (exception .args or ('' ,)) + (metric ,)
7189 raise
7290
7391 output .append ('# EOF\n ' )
7492 return '' .join (output ).encode ('utf-8' )
93+
94+
95+ def escape_metric_name (s : str ) -> str :
96+ """Escapes the metric name and puts it in quotes iff the name does not
97+ conform to the legacy Prometheus character set.
98+ """
99+ if _is_valid_legacy_metric_name (s ):
100+ return s
101+ return '"{}"' .format (_escape (s ))
102+
103+
104+ def escape_label_name (s : str ) -> str :
105+ """Escapes the label name and puts it in quotes iff the name does not
106+ conform to the legacy Prometheus character set.
107+ """
108+ if _is_valid_legacy_labelname (s ):
109+ return s
110+ return '"{}"' .format (_escape (s ))
111+
112+
113+ def _escape (s : str ) -> str :
114+ """Performs backslash escaping on \, \n , and " characters."""
115+ return s .replace ('\\ ' , r'\\' ).replace ('\n ' , r'\n' ).replace ('"' , r'\"' )
0 commit comments