encode.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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/internal/bitbucket.org/ww/goautoneg"
  21. "github.com/prometheus/common/model"
  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(fmt.Sprintf("; escaping=%s", 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(fmt.Sprintf("; escaping=%s", 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. func NewEncoder(w io.Writer, format Format) Encoder {
  130. escapingScheme := format.ToEscapingScheme()
  131. switch format.FormatType() {
  132. case TypeProtoDelim:
  133. return encoderCloser{
  134. encode: func(v *dto.MetricFamily) error {
  135. _, err := protodelim.MarshalTo(w, v)
  136. return err
  137. },
  138. close: func() error { return nil },
  139. }
  140. case TypeProtoCompact:
  141. return encoderCloser{
  142. encode: func(v *dto.MetricFamily) error {
  143. _, err := fmt.Fprintln(w, model.EscapeMetricFamily(v, escapingScheme).String())
  144. return err
  145. },
  146. close: func() error { return nil },
  147. }
  148. case TypeProtoText:
  149. return encoderCloser{
  150. encode: func(v *dto.MetricFamily) error {
  151. _, err := fmt.Fprintln(w, prototext.Format(model.EscapeMetricFamily(v, escapingScheme)))
  152. return err
  153. },
  154. close: func() error { return nil },
  155. }
  156. case TypeTextPlain:
  157. return encoderCloser{
  158. encode: func(v *dto.MetricFamily) error {
  159. _, err := MetricFamilyToText(w, model.EscapeMetricFamily(v, escapingScheme))
  160. return err
  161. },
  162. close: func() error { return nil },
  163. }
  164. case TypeOpenMetrics:
  165. return encoderCloser{
  166. encode: func(v *dto.MetricFamily) error {
  167. _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme))
  168. return err
  169. },
  170. close: func() error {
  171. _, err := FinalizeOpenMetrics(w)
  172. return err
  173. },
  174. }
  175. }
  176. panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format))
  177. }