github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/targets/lokipush/pushtarget.go (about)

     1  package lokipush
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"sort"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/go-kit/log"
    13  	"github.com/go-kit/log/level"
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/prometheus/common/model"
    16  	"github.com/prometheus/prometheus/model/labels"
    17  	"github.com/prometheus/prometheus/model/relabel"
    18  	promql_parser "github.com/prometheus/prometheus/promql/parser"
    19  	"github.com/weaveworks/common/server"
    20  
    21  	"github.com/grafana/dskit/tenant"
    22  
    23  	"github.com/grafana/loki/clients/pkg/promtail/api"
    24  	"github.com/grafana/loki/clients/pkg/promtail/scrapeconfig"
    25  	"github.com/grafana/loki/clients/pkg/promtail/targets/serverutils"
    26  	"github.com/grafana/loki/clients/pkg/promtail/targets/target"
    27  
    28  	"github.com/grafana/loki/pkg/loghttp/push"
    29  	"github.com/grafana/loki/pkg/logproto"
    30  	util_log "github.com/grafana/loki/pkg/util/log"
    31  )
    32  
    33  type PushTarget struct {
    34  	logger        log.Logger
    35  	handler       api.EntryHandler
    36  	config        *scrapeconfig.PushTargetConfig
    37  	relabelConfig []*relabel.Config
    38  	jobName       string
    39  	server        *server.Server
    40  }
    41  
    42  func NewPushTarget(logger log.Logger,
    43  	handler api.EntryHandler,
    44  	relabel []*relabel.Config,
    45  	jobName string,
    46  	config *scrapeconfig.PushTargetConfig,
    47  ) (*PushTarget, error) {
    48  
    49  	pt := &PushTarget{
    50  		logger:        logger,
    51  		handler:       handler,
    52  		relabelConfig: relabel,
    53  		jobName:       jobName,
    54  		config:        config,
    55  	}
    56  
    57  	mergedServerConfigs, err := serverutils.MergeWithDefaults(config.Server)
    58  	if err != nil {
    59  		return nil, fmt.Errorf("failed to parse configs and override defaults when configuring loki push target: %w", err)
    60  	}
    61  	// Set the config to the new combined config.
    62  	config.Server = mergedServerConfigs
    63  
    64  	err = pt.run()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return pt, nil
    70  }
    71  
    72  func (t *PushTarget) run() error {
    73  	level.Info(t.logger).Log("msg", "starting push server", "job", t.jobName)
    74  	// To prevent metric collisions because all metrics are going to be registered in the global Prometheus registry.
    75  	t.config.Server.MetricsNamespace = "promtail_" + t.jobName
    76  
    77  	// We don't want the /debug and /metrics endpoints running
    78  	t.config.Server.RegisterInstrumentation = false
    79  
    80  	// The logger registers a metric which will cause a duplicate registry panic unless we provide an empty registry
    81  	// The metric created is for counting log lines and isn't likely to be missed.
    82  	util_log.InitLogger(&t.config.Server, prometheus.NewRegistry())
    83  
    84  	srv, err := server.New(t.config.Server)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	t.server = srv
    90  	t.server.HTTP.Path("/loki/api/v1/push").Methods("POST").Handler(http.HandlerFunc(t.handleLoki))
    91  	t.server.HTTP.Path("/promtail/api/v1/raw").Methods("POST").Handler(http.HandlerFunc(t.handlePlaintext))
    92  
    93  	go func() {
    94  		err := srv.Run()
    95  		if err != nil {
    96  			level.Error(t.logger).Log("msg", "Loki push server shutdown with error", "err", err)
    97  		}
    98  	}()
    99  
   100  	return nil
   101  }
   102  
   103  func (t *PushTarget) handleLoki(w http.ResponseWriter, r *http.Request) {
   104  	logger := util_log.WithContext(r.Context(), util_log.Logger)
   105  	userID, _ := tenant.TenantID(r.Context())
   106  	req, err := push.ParseRequest(logger, userID, r, nil)
   107  	if err != nil {
   108  		level.Warn(t.logger).Log("msg", "failed to parse incoming push request", "err", err.Error())
   109  		http.Error(w, err.Error(), http.StatusBadRequest)
   110  		return
   111  	}
   112  	var lastErr error
   113  	for _, stream := range req.Streams {
   114  		ls, err := promql_parser.ParseMetric(stream.Labels)
   115  		if err != nil {
   116  			lastErr = err
   117  			continue
   118  		}
   119  		sort.Sort(ls)
   120  
   121  		lb := labels.NewBuilder(ls)
   122  
   123  		// Add configured labels
   124  		for k, v := range t.config.Labels {
   125  			lb.Set(string(k), string(v))
   126  		}
   127  
   128  		// Apply relabeling
   129  		processed := relabel.Process(lb.Labels(), t.relabelConfig...)
   130  		if processed == nil || len(processed) == 0 {
   131  			w.WriteHeader(http.StatusNoContent)
   132  			return
   133  		}
   134  
   135  		// Convert to model.LabelSet
   136  		filtered := model.LabelSet{}
   137  		for i := range processed {
   138  			if strings.HasPrefix(processed[i].Name, "__") {
   139  				continue
   140  			}
   141  			filtered[model.LabelName(processed[i].Name)] = model.LabelValue(processed[i].Value)
   142  		}
   143  
   144  		for _, entry := range stream.Entries {
   145  			e := api.Entry{
   146  				Labels: filtered.Clone(),
   147  				Entry: logproto.Entry{
   148  					Line: entry.Line,
   149  				},
   150  			}
   151  			if t.config.KeepTimestamp {
   152  				e.Timestamp = entry.Timestamp
   153  			} else {
   154  				e.Timestamp = time.Now()
   155  			}
   156  			t.handler.Chan() <- e
   157  		}
   158  	}
   159  
   160  	if lastErr != nil {
   161  		level.Warn(t.logger).Log("msg", "at least one entry in the push request failed to process", "err", lastErr.Error())
   162  		http.Error(w, lastErr.Error(), http.StatusBadRequest)
   163  		return
   164  	}
   165  
   166  	w.WriteHeader(http.StatusNoContent)
   167  }
   168  
   169  // handlePlaintext handles newline delimited input such as plaintext or NDJSON.
   170  func (t *PushTarget) handlePlaintext(w http.ResponseWriter, r *http.Request) {
   171  	entries := t.handler.Chan()
   172  	defer r.Body.Close()
   173  	body := bufio.NewReader(r.Body)
   174  	for {
   175  		line, err := body.ReadString('\n')
   176  		if err != nil && err != io.EOF {
   177  			level.Warn(t.logger).Log("msg", "failed to read incoming push request", "err", err.Error())
   178  			http.Error(w, err.Error(), http.StatusBadRequest)
   179  			return
   180  		}
   181  		line = strings.TrimRight(line, "\r\n")
   182  		if line == "" {
   183  			if err == io.EOF {
   184  				break
   185  			}
   186  			continue
   187  		}
   188  		entries <- api.Entry{
   189  			Labels: t.Labels().Clone(),
   190  			Entry: logproto.Entry{
   191  				Timestamp: time.Now(),
   192  				Line:      line,
   193  			},
   194  		}
   195  		if err == io.EOF {
   196  			break
   197  		}
   198  	}
   199  
   200  	w.WriteHeader(http.StatusNoContent)
   201  }
   202  
   203  // Type returns PushTargetType.
   204  func (t *PushTarget) Type() target.TargetType {
   205  	return target.PushTargetType
   206  }
   207  
   208  // Ready indicates whether or not the PushTarget target is ready to be read from.
   209  func (t *PushTarget) Ready() bool {
   210  	return true
   211  }
   212  
   213  // DiscoveredLabels returns the set of labels discovered by the PushTarget, which
   214  // is always nil. Implements Target.
   215  func (t *PushTarget) DiscoveredLabels() model.LabelSet {
   216  	return nil
   217  }
   218  
   219  // Labels returns the set of labels that statically apply to all log entries
   220  // produced by the PushTarget.
   221  func (t *PushTarget) Labels() model.LabelSet {
   222  	return t.config.Labels
   223  }
   224  
   225  // Details returns target-specific details.
   226  func (t *PushTarget) Details() interface{} {
   227  	return map[string]string{}
   228  }
   229  
   230  // Stop shuts down the PushTarget.
   231  func (t *PushTarget) Stop() error {
   232  	level.Info(t.logger).Log("msg", "stopping push server", "job", t.jobName)
   233  	t.server.Shutdown()
   234  	t.handler.Stop()
   235  	return nil
   236  }