github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/loghttp/push/push.go (about) 1 package push 2 3 import ( 4 "compress/flate" 5 "compress/gzip" 6 "fmt" 7 "io" 8 "math" 9 "mime" 10 "net/http" 11 "time" 12 13 "github.com/dustin/go-humanize" 14 "github.com/go-kit/log" 15 "github.com/go-kit/log/level" 16 "github.com/prometheus/client_golang/prometheus" 17 "github.com/prometheus/client_golang/prometheus/promauto" 18 "github.com/prometheus/prometheus/model/labels" 19 20 "github.com/grafana/loki/pkg/loghttp" 21 "github.com/grafana/loki/pkg/logproto" 22 "github.com/grafana/loki/pkg/logql/syntax" 23 "github.com/grafana/loki/pkg/usagestats" 24 "github.com/grafana/loki/pkg/util" 25 loki_util "github.com/grafana/loki/pkg/util" 26 "github.com/grafana/loki/pkg/util/unmarshal" 27 unmarshal2 "github.com/grafana/loki/pkg/util/unmarshal/legacy" 28 ) 29 30 var ( 31 contentType = http.CanonicalHeaderKey("Content-Type") 32 contentEnc = http.CanonicalHeaderKey("Content-Encoding") 33 bytesIngested = promauto.NewCounterVec(prometheus.CounterOpts{ 34 Namespace: "loki", 35 Name: "distributor_bytes_received_total", 36 Help: "The total number of uncompressed bytes received per tenant", 37 }, []string{"tenant", "retention_hours"}) 38 linesIngested = promauto.NewCounterVec(prometheus.CounterOpts{ 39 Namespace: "loki", 40 Name: "distributor_lines_received_total", 41 Help: "The total number of lines received per tenant", 42 }, []string{"tenant"}) 43 44 bytesReceivedStats = usagestats.NewCounter("distributor_bytes_received") 45 linesReceivedStats = usagestats.NewCounter("distributor_lines_received") 46 ) 47 48 const applicationJSON = "application/json" 49 50 type TenantsRetention interface { 51 RetentionPeriodFor(userID string, lbs labels.Labels) time.Duration 52 } 53 54 func ParseRequest(logger log.Logger, userID string, r *http.Request, tenantsRetention TenantsRetention) (*logproto.PushRequest, error) { 55 // Body 56 var body io.Reader 57 // bodySize should always reflect the compressed size of the request body 58 bodySize := loki_util.NewSizeReader(r.Body) 59 contentEncoding := r.Header.Get(contentEnc) 60 switch contentEncoding { 61 case "": 62 body = bodySize 63 case "snappy": 64 // Snappy-decoding is done by `util.ParseProtoReader(..., util.RawSnappy)` below. 65 // Pass on body bytes. Note: HTTP clients do not need to set this header, 66 // but they sometimes do. See #3407. 67 body = bodySize 68 case "gzip": 69 gzipReader, err := gzip.NewReader(bodySize) 70 if err != nil { 71 return nil, err 72 } 73 defer gzipReader.Close() 74 body = gzipReader 75 case "deflate": 76 flateReader := flate.NewReader(bodySize) 77 defer flateReader.Close() 78 body = flateReader 79 default: 80 return nil, fmt.Errorf("Content-Encoding %q not supported", contentEncoding) 81 } 82 83 contentType := r.Header.Get(contentType) 84 var ( 85 entriesSize int64 86 streamLabelsSize int64 87 totalEntries int64 88 req logproto.PushRequest 89 ) 90 91 contentType, _ /* params */, err := mime.ParseMediaType(contentType) 92 if err != nil { 93 return nil, err 94 } 95 96 switch contentType { 97 case applicationJSON: 98 99 var err error 100 101 // todo once https://github.com/weaveworks/common/commit/73225442af7da93ec8f6a6e2f7c8aafaee3f8840 is in Loki. 102 // We can try to pass the body as bytes.buffer instead to avoid reading into another buffer. 103 if loghttp.GetVersion(r.RequestURI) == loghttp.VersionV1 { 104 err = unmarshal.DecodePushRequest(body, &req) 105 } else { 106 err = unmarshal2.DecodePushRequest(body, &req) 107 } 108 109 if err != nil { 110 return nil, err 111 } 112 113 default: 114 // When no content-type header is set or when it is set to 115 // `application/x-protobuf`: expect snappy compression. 116 if err := util.ParseProtoReader(r.Context(), body, int(r.ContentLength), math.MaxInt32, &req, util.RawSnappy); err != nil { 117 return nil, err 118 } 119 } 120 121 mostRecentEntry := time.Unix(0, 0) 122 123 for _, s := range req.Streams { 124 streamLabelsSize += int64(len(s.Labels)) 125 var retentionHours string 126 if tenantsRetention != nil { 127 lbs, err := syntax.ParseLabels(s.Labels) 128 if err != nil { 129 return nil, err 130 } 131 retentionHours = fmt.Sprintf("%d", int64(math.Floor(tenantsRetention.RetentionPeriodFor(userID, lbs).Hours()))) 132 } 133 for _, e := range s.Entries { 134 totalEntries++ 135 entriesSize += int64(len(e.Line)) 136 bytesIngested.WithLabelValues(userID, retentionHours).Add(float64(int64(len(e.Line)))) 137 bytesReceivedStats.Inc(int64(len(e.Line))) 138 if e.Timestamp.After(mostRecentEntry) { 139 mostRecentEntry = e.Timestamp 140 } 141 } 142 } 143 144 // incrementing tenant metrics if we have a tenant. 145 if totalEntries != 0 && userID != "" { 146 linesIngested.WithLabelValues(userID).Add(float64(totalEntries)) 147 } 148 linesReceivedStats.Inc(totalEntries) 149 150 level.Debug(logger).Log( 151 "msg", "push request parsed", 152 "path", r.URL.Path, 153 "contentType", contentType, 154 "contentEncoding", contentEncoding, 155 "bodySize", humanize.Bytes(uint64(bodySize.Size())), 156 "streams", len(req.Streams), 157 "entries", totalEntries, 158 "streamLabelsSize", humanize.Bytes(uint64(streamLabelsSize)), 159 "entriesSize", humanize.Bytes(uint64(entriesSize)), 160 "totalSize", humanize.Bytes(uint64(entriesSize+streamLabelsSize)), 161 "mostRecentLagMs", time.Since(mostRecentEntry).Milliseconds(), 162 ) 163 return &req, nil 164 }