blob: 73c24dfbc9cb5b187937b40de0af032a443797dc [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// 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
14package expfmt
15
16import (
17 "fmt"
18 "io"
19 "net/http"
20
21 "github.com/munnerz/goautoneg"
22 dto "github.com/prometheus/client_model/go"
23 "google.golang.org/protobuf/encoding/protodelim"
24 "google.golang.org/protobuf/encoding/prototext"
25
26 "github.com/prometheus/common/model"
27)
28
29// Encoder types encode metric families into an underlying wire protocol.
30type Encoder interface {
31 Encode(*dto.MetricFamily) error
32}
33
34// Closer is implemented by Encoders that need to be closed to finalize
35// encoding. (For example, OpenMetrics needs a final `# EOF` line.)
36//
37// Note that all Encoder implementations returned from this package implement
38// Closer, too, even if the Close call is a no-op. This happens in preparation
39// for adding a Close method to the Encoder interface directly in a (mildly
40// breaking) release in the future.
41type Closer interface {
42 Close() error
43}
44
45type encoderCloser struct {
46 encode func(*dto.MetricFamily) error
47 close func() error
48}
49
50func (ec encoderCloser) Encode(v *dto.MetricFamily) error {
51 return ec.encode(v)
52}
53
54func (ec encoderCloser) Close() error {
55 return ec.close()
56}
57
58// Negotiate returns the Content-Type based on the given Accept header. If no
59// appropriate accepted type is found, FmtText is returned (which is the
60// Prometheus text format). This function will never negotiate FmtOpenMetrics,
61// as the support is still experimental. To include the option to negotiate
62// FmtOpenMetrics, use NegotiateIncludingOpenMetrics.
63func Negotiate(h http.Header) Format {
64 escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
65 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
66 if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
67 switch Format(escapeParam) {
68 case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
69 escapingScheme = Format("; escaping=" + escapeParam)
70 default:
71 // If the escaping parameter is unknown, ignore it.
72 }
73 }
74 ver := ac.Params["version"]
75 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
76 switch ac.Params["encoding"] {
77 case "delimited":
78 return FmtProtoDelim + escapingScheme
79 case "text":
80 return FmtProtoText + escapingScheme
81 case "compact-text":
82 return FmtProtoCompact + escapingScheme
83 }
84 }
85 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
86 return FmtText + escapingScheme
87 }
88 }
89 return FmtText + escapingScheme
90}
91
92// NegotiateIncludingOpenMetrics works like Negotiate but includes
93// FmtOpenMetrics as an option for the result. Note that this function is
94// temporary and will disappear once FmtOpenMetrics is fully supported and as
95// such may be negotiated by the normal Negotiate function.
96func NegotiateIncludingOpenMetrics(h http.Header) Format {
97 escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
98 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
99 if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
100 switch Format(escapeParam) {
101 case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues:
102 escapingScheme = Format("; escaping=" + escapeParam)
103 default:
104 // If the escaping parameter is unknown, ignore it.
105 }
106 }
107 ver := ac.Params["version"]
108 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
109 switch ac.Params["encoding"] {
110 case "delimited":
111 return FmtProtoDelim + escapingScheme
112 case "text":
113 return FmtProtoText + escapingScheme
114 case "compact-text":
115 return FmtProtoCompact + escapingScheme
116 }
117 }
118 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
119 return FmtText + escapingScheme
120 }
121 if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
122 switch ver {
123 case OpenMetricsVersion_1_0_0:
124 return FmtOpenMetrics_1_0_0 + escapingScheme
125 default:
126 return FmtOpenMetrics_0_0_1 + escapingScheme
127 }
128 }
129 }
130 return FmtText + escapingScheme
131}
132
133// NewEncoder returns a new encoder based on content type negotiation. All
134// Encoder implementations returned by NewEncoder also implement Closer, and
135// callers should always call the Close method. It is currently only required
136// for FmtOpenMetrics, but a future (breaking) release will add the Close method
137// to the Encoder interface directly. The current version of the Encoder
138// interface is kept for backwards compatibility.
139// In cases where the Format does not allow for UTF-8 names, the global
140// NameEscapingScheme will be applied.
141//
142// NewEncoder can be called with additional options to customize the OpenMetrics text output.
143// For example:
144// NewEncoder(w, FmtOpenMetrics_1_0_0, WithCreatedLines())
145//
146// Extra options are ignored for all other formats.
147func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder {
148 escapingScheme := format.ToEscapingScheme()
149
150 switch format.FormatType() {
151 case TypeProtoDelim:
152 return encoderCloser{
153 encode: func(v *dto.MetricFamily) error {
154 _, err := protodelim.MarshalTo(w, model.EscapeMetricFamily(v, escapingScheme))
155 return err
156 },
157 close: func() error { return nil },
158 }
159 case TypeProtoCompact:
160 return encoderCloser{
161 encode: func(v *dto.MetricFamily) error {
162 _, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())
163 return err
164 },
165 close: func() error { return nil },
166 }
167 case TypeProtoText:
168 return encoderCloser{
169 encode: func(v *dto.MetricFamily) error {
170 _, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))
171 return err
172 },
173 close: func() error { return nil },
174 }
175 case TypeTextPlain:
176 return encoderCloser{
177 encode: func(v *dto.MetricFamily) error {
178 _, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))
179 return err
180 },
181 close: func() error { return nil },
182 }
183 case TypeOpenMetrics:
184 return encoderCloser{
185 encode: func(v *dto.MetricFamily) error {
186 _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...)
187 return err
188 },
189 close: func() error {
190 _, err := FinalizeOpenMetrics(w)
191 return err
192 },
193 }
194 }
195 panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format))
196}