blob: 73c24dfbc9cb5b187937b40de0af032a443797dc [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
14package expfmt
15
16import (
17 "fmt"
18 "io"
19 "net/http"
20
Abhay Kumara2ae5992025-11-10 14:02:24 +000021 "github.com/munnerz/goautoneg"
khenaidooab1f7bd2019-11-14 14:00:27 -050022 dto "github.com/prometheus/client_model/go"
Abhay Kumara2ae5992025-11-10 14:02:24 +000023 "google.golang.org/protobuf/encoding/protodelim"
24 "google.golang.org/protobuf/encoding/prototext"
25
26 "github.com/prometheus/common/model"
khenaidooab1f7bd2019-11-14 14:00:27 -050027)
28
29// Encoder types encode metric families into an underlying wire protocol.
30type Encoder interface {
31 Encode(*dto.MetricFamily) error
32}
33
khenaidood948f772021-08-11 17:49:24 -040034// 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
khenaidooab1f7bd2019-11-14 14:00:27 -050043}
44
khenaidood948f772021-08-11 17:49:24 -040045type 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
Abhay Kumara2ae5992025-11-10 14:02:24 +000062// FmtOpenMetrics, use NegotiateIncludingOpenMetrics.
khenaidooab1f7bd2019-11-14 14:00:27 -050063func Negotiate(h http.Header) Format {
Abhay Kumara2ae5992025-11-10 14:02:24 +000064 escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
khenaidooab1f7bd2019-11-14 14:00:27 -050065 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
Abhay Kumara2ae5992025-11-10 14:02:24 +000066 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 }
khenaidood948f772021-08-11 17:49:24 -040074 ver := ac.Params["version"]
khenaidooab1f7bd2019-11-14 14:00:27 -050075 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
76 switch ac.Params["encoding"] {
77 case "delimited":
Abhay Kumara2ae5992025-11-10 14:02:24 +000078 return FmtProtoDelim + escapingScheme
khenaidooab1f7bd2019-11-14 14:00:27 -050079 case "text":
Abhay Kumara2ae5992025-11-10 14:02:24 +000080 return FmtProtoText + escapingScheme
khenaidooab1f7bd2019-11-14 14:00:27 -050081 case "compact-text":
Abhay Kumara2ae5992025-11-10 14:02:24 +000082 return FmtProtoCompact + escapingScheme
khenaidooab1f7bd2019-11-14 14:00:27 -050083 }
84 }
khenaidooab1f7bd2019-11-14 14:00:27 -050085 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
Abhay Kumara2ae5992025-11-10 14:02:24 +000086 return FmtText + escapingScheme
khenaidooab1f7bd2019-11-14 14:00:27 -050087 }
88 }
Abhay Kumara2ae5992025-11-10 14:02:24 +000089 return FmtText + escapingScheme
khenaidooab1f7bd2019-11-14 14:00:27 -050090}
91
khenaidood948f772021-08-11 17:49:24 -040092// 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 {
Abhay Kumara2ae5992025-11-10 14:02:24 +000097 escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
khenaidood948f772021-08-11 17:49:24 -040098 for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
Abhay Kumara2ae5992025-11-10 14:02:24 +000099 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 }
khenaidood948f772021-08-11 17:49:24 -0400107 ver := ac.Params["version"]
108 if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
109 switch ac.Params["encoding"] {
110 case "delimited":
Abhay Kumara2ae5992025-11-10 14:02:24 +0000111 return FmtProtoDelim + escapingScheme
khenaidood948f772021-08-11 17:49:24 -0400112 case "text":
Abhay Kumara2ae5992025-11-10 14:02:24 +0000113 return FmtProtoText + escapingScheme
khenaidood948f772021-08-11 17:49:24 -0400114 case "compact-text":
Abhay Kumara2ae5992025-11-10 14:02:24 +0000115 return FmtProtoCompact + escapingScheme
khenaidood948f772021-08-11 17:49:24 -0400116 }
117 }
118 if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000119 return FmtText + escapingScheme
khenaidood948f772021-08-11 17:49:24 -0400120 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000121 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 }
khenaidood948f772021-08-11 17:49:24 -0400128 }
129 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000130 return FmtText + escapingScheme
khenaidood948f772021-08-11 17:49:24 -0400131}
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.
Abhay Kumara2ae5992025-11-10 14:02:24 +0000139// 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:
khenaidood948f772021-08-11 17:49:24 -0400152 return encoderCloser{
153 encode: func(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000154 _, err := protodelim.MarshalTo(w, model.EscapeMetricFamily(v, escapingScheme))
khenaidood948f772021-08-11 17:49:24 -0400155 return err
156 },
157 close: func() error { return nil },
158 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000159 case TypeProtoCompact:
khenaidood948f772021-08-11 17:49:24 -0400160 return encoderCloser{
161 encode: func(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000162 _, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())
khenaidood948f772021-08-11 17:49:24 -0400163 return err
164 },
165 close: func() error { return nil },
166 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000167 case TypeProtoText:
khenaidood948f772021-08-11 17:49:24 -0400168 return encoderCloser{
169 encode: func(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000170 _, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))
khenaidood948f772021-08-11 17:49:24 -0400171 return err
172 },
173 close: func() error { return nil },
174 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000175 case TypeTextPlain:
khenaidood948f772021-08-11 17:49:24 -0400176 return encoderCloser{
177 encode: func(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000178 _, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))
khenaidood948f772021-08-11 17:49:24 -0400179 return err
180 },
181 close: func() error { return nil },
182 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000183 case TypeOpenMetrics:
khenaidood948f772021-08-11 17:49:24 -0400184 return encoderCloser{
185 encode: func(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000186 _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...)
khenaidood948f772021-08-11 17:49:24 -0400187 return err
188 },
189 close: func() error {
190 _, err := FinalizeOpenMetrics(w)
191 return err
192 },
193 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500194 }
khenaidood948f772021-08-11 17:49:24 -0400195 panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format))
khenaidooab1f7bd2019-11-14 14:00:27 -0500196}