| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 1 | // 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 |
| 15 | |
| 16 | import ( |
| 17 | "fmt" |
| 18 | "io" |
| 19 | "net/http" |
| 20 | |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 21 | "github.com/munnerz/goautoneg" |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 22 | dto "github.com/prometheus/client_model/go" |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 23 | "google.golang.org/protobuf/encoding/protodelim" |
| 24 | "google.golang.org/protobuf/encoding/prototext" |
| 25 | |
| 26 | "github.com/prometheus/common/model" |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 27 | ) |
| 28 | |
| 29 | // Encoder types encode metric families into an underlying wire protocol. |
| 30 | type Encoder interface { |
| 31 | Encode(*dto.MetricFamily) error |
| 32 | } |
| 33 | |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 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. |
| 41 | type Closer interface { |
| 42 | Close() error |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 43 | } |
| 44 | |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 45 | type encoderCloser struct { |
| 46 | encode func(*dto.MetricFamily) error |
| 47 | close func() error |
| 48 | } |
| 49 | |
| 50 | func (ec encoderCloser) Encode(v *dto.MetricFamily) error { |
| 51 | return ec.encode(v) |
| 52 | } |
| 53 | |
| 54 | func (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 Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 62 | // FmtOpenMetrics, use NegotiateIncludingOpenMetrics. |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 63 | func Negotiate(h http.Header) Format { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 64 | escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String()))) |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 65 | for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 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 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 74 | ver := ac.Params["version"] |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 75 | if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { |
| 76 | switch ac.Params["encoding"] { |
| 77 | case "delimited": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 78 | return FmtProtoDelim + escapingScheme |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 79 | case "text": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 80 | return FmtProtoText + escapingScheme |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 81 | case "compact-text": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 82 | return FmtProtoCompact + escapingScheme |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 83 | } |
| 84 | } |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 85 | if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 86 | return FmtText + escapingScheme |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 87 | } |
| 88 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 89 | return FmtText + escapingScheme |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 90 | } |
| 91 | |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 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. |
| 96 | func NegotiateIncludingOpenMetrics(h http.Header) Format { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 97 | escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String()))) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 98 | for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 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 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 107 | ver := ac.Params["version"] |
| 108 | if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { |
| 109 | switch ac.Params["encoding"] { |
| 110 | case "delimited": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 111 | return FmtProtoDelim + escapingScheme |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 112 | case "text": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 113 | return FmtProtoText + escapingScheme |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 114 | case "compact-text": |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 115 | return FmtProtoCompact + escapingScheme |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 116 | } |
| 117 | } |
| 118 | if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 119 | return FmtText + escapingScheme |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 120 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 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 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 128 | } |
| 129 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 130 | return FmtText + escapingScheme |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 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. |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 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. |
| 147 | func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder { |
| 148 | escapingScheme := format.ToEscapingScheme() |
| 149 | |
| 150 | switch format.FormatType() { |
| 151 | case TypeProtoDelim: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 152 | return encoderCloser{ |
| 153 | encode: func(v *dto.MetricFamily) error { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 154 | _, err := protodelim.MarshalTo(w, model.EscapeMetricFamily(v, escapingScheme)) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 155 | return err |
| 156 | }, |
| 157 | close: func() error { return nil }, |
| 158 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 159 | case TypeProtoCompact: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 160 | return encoderCloser{ |
| 161 | encode: func(v *dto.MetricFamily) error { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 162 | _, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String()) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 163 | return err |
| 164 | }, |
| 165 | close: func() error { return nil }, |
| 166 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 167 | case TypeProtoText: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 168 | return encoderCloser{ |
| 169 | encode: func(v *dto.MetricFamily) error { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 170 | _, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme))) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 171 | return err |
| 172 | }, |
| 173 | close: func() error { return nil }, |
| 174 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 175 | case TypeTextPlain: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 176 | return encoderCloser{ |
| 177 | encode: func(v *dto.MetricFamily) error { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 178 | _, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme)) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 179 | return err |
| 180 | }, |
| 181 | close: func() error { return nil }, |
| 182 | } |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 183 | case TypeOpenMetrics: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 184 | return encoderCloser{ |
| 185 | encode: func(v *dto.MetricFamily) error { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 186 | _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...) |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 187 | return err |
| 188 | }, |
| 189 | close: func() error { |
| 190 | _, err := FinalizeOpenMetrics(w) |
| 191 | return err |
| 192 | }, |
| 193 | } |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 194 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 195 | panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format)) |
| khenaidoo | ab1f7bd | 2019-11-14 14:00:27 -0500 | [diff] [blame] | 196 | } |