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  }