encode.go 6.8 KB

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