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 }