blob: c34c7de432b1d70e180a30952a50003d6f5a8401 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2015 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// Package expfmt contains tools for reading and writing Prometheus metrics.
15package expfmt
16
Abhay Kumara2ae5992025-11-10 14:02:24 +000017import (
18 "errors"
19 "strings"
20
21 "github.com/prometheus/common/model"
22)
23
khenaidooab1f7bd2019-11-14 14:00:27 -050024// Format specifies the HTTP content type of the different wire protocols.
25type Format string
26
Abhay Kumara2ae5992025-11-10 14:02:24 +000027// Constants to assemble the Content-Type values for the different wire
28// protocols. The Content-Type strings here are all for the legacy exposition
29// formats, where valid characters for metric names and label names are limited.
30// Support for arbitrary UTF-8 characters in those names is already partially
31// implemented in this module (see model.ValidationScheme), but to actually use
32// it on the wire, new content-type strings will have to be agreed upon and
33// added here.
khenaidooab1f7bd2019-11-14 14:00:27 -050034const (
Abhay Kumara2ae5992025-11-10 14:02:24 +000035 TextVersion = "0.0.4"
36 ProtoType = `application/vnd.google.protobuf`
37 ProtoProtocol = `io.prometheus.client.MetricFamily`
38 // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
39 ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
40 OpenMetricsType = `application/openmetrics-text`
41 //nolint:revive // Allow for underscores.
42 OpenMetricsVersion_0_0_1 = "0.0.1"
43 //nolint:revive // Allow for underscores.
44 OpenMetricsVersion_1_0_0 = "1.0.0"
khenaidooab1f7bd2019-11-14 14:00:27 -050045
Abhay Kumara2ae5992025-11-10 14:02:24 +000046 // The Content-Type values for the different wire protocols. Do not do direct
47 // comparisons to these constants, instead use the comparison functions.
48 // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead.
49 FmtUnknown Format = `<unknown>`
50 // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead.
51 FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
52 // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead.
53 FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
54 // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead.
55 FmtProtoText Format = ProtoFmt + ` encoding=text`
56 // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead.
khenaidooab1f7bd2019-11-14 14:00:27 -050057 FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
Abhay Kumara2ae5992025-11-10 14:02:24 +000058 // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
59 //nolint:revive // Allow for underscores.
60 FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
61 // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead.
62 //nolint:revive // Allow for underscores.
63 FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
khenaidooab1f7bd2019-11-14 14:00:27 -050064)
65
66const (
67 hdrContentType = "Content-Type"
68 hdrAccept = "Accept"
69)
Abhay Kumara2ae5992025-11-10 14:02:24 +000070
71// FormatType is a Go enum representing the overall category for the given
72// Format. As the number of Format permutations increases, doing basic string
73// comparisons are not feasible, so this enum captures the most useful
74// high-level attribute of the Format string.
75type FormatType int
76
77const (
78 TypeUnknown FormatType = iota
79 TypeProtoCompact
80 TypeProtoDelim
81 TypeProtoText
82 TypeTextPlain
83 TypeOpenMetrics
84)
85
86// NewFormat generates a new Format from the type provided. Mostly used for
87// tests, most Formats should be generated as part of content negotiation in
88// encode.go. If a type has more than one version, the latest version will be
89// returned.
90func NewFormat(t FormatType) Format {
91 switch t {
92 case TypeProtoCompact:
93 return FmtProtoCompact
94 case TypeProtoDelim:
95 return FmtProtoDelim
96 case TypeProtoText:
97 return FmtProtoText
98 case TypeTextPlain:
99 return FmtText
100 case TypeOpenMetrics:
101 return FmtOpenMetrics_1_0_0
102 default:
103 return FmtUnknown
104 }
105}
106
107// NewOpenMetricsFormat generates a new OpenMetrics format matching the
108// specified version number.
109func NewOpenMetricsFormat(version string) (Format, error) {
110 if version == OpenMetricsVersion_0_0_1 {
111 return FmtOpenMetrics_0_0_1, nil
112 }
113 if version == OpenMetricsVersion_1_0_0 {
114 return FmtOpenMetrics_1_0_0, nil
115 }
116 return FmtUnknown, errors.New("unknown open metrics version string")
117}
118
119// WithEscapingScheme returns a copy of Format with the specified escaping
120// scheme appended to the end. If an escaping scheme already exists it is
121// removed.
122func (f Format) WithEscapingScheme(s model.EscapingScheme) Format {
123 var terms []string
124 for _, p := range strings.Split(string(f), ";") {
125 toks := strings.Split(p, "=")
126 if len(toks) != 2 {
127 trimmed := strings.TrimSpace(p)
128 if len(trimmed) > 0 {
129 terms = append(terms, trimmed)
130 }
131 continue
132 }
133 key := strings.TrimSpace(toks[0])
134 if key != model.EscapingKey {
135 terms = append(terms, strings.TrimSpace(p))
136 }
137 }
138 terms = append(terms, model.EscapingKey+"="+s.String())
139 return Format(strings.Join(terms, "; "))
140}
141
142// FormatType deduces an overall FormatType for the given format.
143func (f Format) FormatType() FormatType {
144 toks := strings.Split(string(f), ";")
145 params := make(map[string]string)
146 for i, t := range toks {
147 if i == 0 {
148 continue
149 }
150 args := strings.Split(t, "=")
151 if len(args) != 2 {
152 continue
153 }
154 params[strings.TrimSpace(args[0])] = strings.TrimSpace(args[1])
155 }
156
157 switch strings.TrimSpace(toks[0]) {
158 case ProtoType:
159 if params["proto"] != ProtoProtocol {
160 return TypeUnknown
161 }
162 switch params["encoding"] {
163 case "delimited":
164 return TypeProtoDelim
165 case "text":
166 return TypeProtoText
167 case "compact-text":
168 return TypeProtoCompact
169 default:
170 return TypeUnknown
171 }
172 case OpenMetricsType:
173 if params["charset"] != "utf-8" {
174 return TypeUnknown
175 }
176 return TypeOpenMetrics
177 case "text/plain":
178 v, ok := params["version"]
179 if !ok {
180 return TypeTextPlain
181 }
182 if v == TextVersion {
183 return TypeTextPlain
184 }
185 return TypeUnknown
186 default:
187 return TypeUnknown
188 }
189}
190
191// ToEscapingScheme returns an EscapingScheme depending on the Format. Iff the
192// Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid
193// "escaping" term exists, that will be used. Otherwise, the global default will
194// be returned.
195func (f Format) ToEscapingScheme() model.EscapingScheme {
196 for _, p := range strings.Split(string(f), ";") {
197 toks := strings.Split(p, "=")
198 if len(toks) != 2 {
199 continue
200 }
201 key, value := strings.TrimSpace(toks[0]), strings.TrimSpace(toks[1])
202 if key == model.EscapingKey {
203 scheme, err := model.ToEscapingScheme(value)
204 if err != nil {
205 return model.NameEscapingScheme
206 }
207 return scheme
208 }
209 }
210 return model.NameEscapingScheme
211}