// This is called every time we receive log lines from an app func processLogs(w http.ResponseWriter, r *http.Request) { c := redisPool.Get() defer c.Close() lp := lpx.NewReader(bufio.NewReader(r.Body)) // a single request may contain multiple log lines. Loop over each of them for lp.Next() { // we only care about logs from the heroku router if string(lp.Header().Procid) == "router" { rl := new(routerLog) if err := logfmt.Unmarshal(lp.Bytes(), rl); err != nil { fmt.Printf("Error parsing log line: %v\n", err) } else { timeBucket, err := timestamp2Bucket(lp.Header().Time) if err != nil { fmt.Printf("Error parsing time: %v", err) continue } _, err = c.Do("INCR", fmt.Sprintf("%v:%v", rl.host, timeBucket)) if err != nil { fmt.Printf("Error running INCR on Redis: %v\n", err) } _, err = c.Do("INCR", "host:"+rl.host) if err != nil { fmt.Printf("Error running INCR on Redis: %v\n", err) } fmt.Printf("%v @ %v: +1\n", rl.host, timeBucket) } } } }
func BuildBuckets(body *bufio.Reader, opts options) <-chan *bucket.Bucket { p := new(parser) p.opts = opts p.out = make(chan *bucket.Bucket) p.lr = lpx.NewReader(body) p.ld = NewLogData() go p.parse() return p.out }
func handlerLogs(w http.ResponseWriter, r *http.Request) { switch { case r.Method == "POST": // Handle logplex frames lp := lpx.NewReader(bufio.NewReader(r.Body)) for lp.Next() { logsCh <- NewLogLineFromLpx(lp) } case r.Method == "GET": // Stream received logs back to the client } }
func ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() if drainId := r.Header.Get("Logplex-Drain-Token"); drainId != "" { lp := lpx.NewReader(bufio.NewReader(r.Body)) for lp.Next() { log.Printf("action=publish drainId=%s message=%s", drainId, string(lp.Bytes())) } w.WriteHeader(http.StatusAccepted) } else { w.WriteHeader(http.StatusUnauthorized) } }
// Fix function to convert post data to length prefixed syslog frames func fix(r io.Reader, remoteAddr string, logplexDrainToken string) ([]byte, error) { var messageWriter bytes.Buffer var messageLenWriter bytes.Buffer lp := lpx.NewReader(bufio.NewReader(r)) for lp.Next() { header := lp.Header() // LEN SP PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP STRUCTURED-DATA MSG messageWriter.Write(header.PrivalVersion) messageWriter.WriteString(" ") messageWriter.Write(header.Time) messageWriter.WriteString(" ") if string(header.Hostname) == logplexDefaultHost && logplexDrainToken != "" { messageWriter.WriteString(logplexDrainToken) } else { messageWriter.Write(header.Hostname) } messageWriter.WriteString(" ") messageWriter.Write(header.Name) messageWriter.WriteString(" ") messageWriter.Write(header.Procid) messageWriter.WriteString(" ") messageWriter.Write(header.Msgid) messageWriter.WriteString(" [origin ip=\"") messageWriter.WriteString(remoteAddr) messageWriter.WriteString("\"]") b := lp.Bytes() if len(b) >= 2 && bytes.Equal(b[0:2], nilVal) { messageWriter.Write(b[1:]) } else if len(b) > 0 { if b[0] != '[' { messageWriter.WriteString(" ") } messageWriter.Write(b) } messageLenWriter.WriteString(strconv.Itoa(messageWriter.Len())) messageLenWriter.WriteString(" ") messageWriter.WriteTo(&messageLenWriter) } return messageLenWriter.Bytes(), lp.Err() }
// "Parse tree" from hell func serveDrain(w http.ResponseWriter, r *http.Request) { ctx := slog.Context{} defer func() { LogWithContext(ctx) }() w.Header().Set("Content-Length", "0") if r.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) ctx.Count("errors.drain.wrong.method", 1) return } id := r.Header.Get("Logplex-Drain-Token") if id == "" { if err := checkAuth(r); err != nil { w.WriteHeader(http.StatusForbidden) ctx.Count("errors.auth.failure", 1) return } } ctx.Count("batch", 1) parseStart := time.Now() lp := lpx.NewReader(bufio.NewReader(r.Body)) for lp.Next() { ctx.Count("lines.total", 1) header := lp.Header() // If the syslog App Name Header field containts what looks like a log token, // let's assume it's an override of the id and we're getting the data from the magic // channel if bytes.HasPrefix(header.Name, TokenPrefix) { id = string(header.Name) } // If we still don't have an id, throw an error and try the next line if id == "" { ctx.Count("errors.token.missing", 1) continue } chanGroup := hashRing.Get(id) msg := lp.Bytes() switch { case bytes.Equal(header.Name, Heroku), bytes.HasPrefix(header.Name, TokenPrefix): t, e := time.Parse("2006-01-02T15:04:05.000000+00:00", string(lp.Header().Time)) if e != nil { log.Printf("Error Parsing Time(%s): %q\n", string(lp.Header().Time), e) continue } timestamp := t.UnixNano() / int64(time.Microsecond) pid := string(header.Procid) switch pid { case "router": switch { // router logs with a H error code in them case bytes.Contains(msg, keyCodeH): ctx.Count("lines.router.error", 1) re := routerError{} err := logfmt.Unmarshal(msg, &re) if err != nil { log.Printf("logfmt unmarshal error: %s\n", err) continue } chanGroup.points[EventsRouter] <- []interface{}{timestamp, id, re.Code} // likely a standard router log default: ctx.Count("lines.router", 1) rm := routerMsg{} err := logfmt.Unmarshal(msg, &rm) if err != nil { log.Printf("logfmt unmarshal error: %s\n", err) continue } chanGroup.points[Router] <- []interface{}{timestamp, id, rm.Status, rm.Service} } // Non router logs, so either dynos, runtime, etc default: switch { // Dyno error messages case bytes.HasPrefix(msg, dynoErrorSentinel): ctx.Count("lines.dyno.error", 1) de, err := parseBytesToDynoError(msg) if err != nil { log.Printf("Unable to parse dyno error message: %q\n", err) } what := string(lp.Header().Procid) chanGroup.points[EventsDyno] <- []interface{}{ timestamp, id, what, "R", de.Code, string(msg), dynoType(what), } // Dyno log-runtime-metrics memory messages case bytes.Contains(msg, dynoMemMsgSentinel): ctx.Count("lines.dyno.mem", 1) dm := dynoMemMsg{} err := logfmt.Unmarshal(msg, &dm) if err != nil { log.Printf("logfmt unmarshal error: %s\n", err) continue } if dm.Source != "" { chanGroup.points[DynoMem] <- []interface{}{ timestamp, id, dm.Source, dm.MemoryCache, dm.MemoryPgpgin, dm.MemoryPgpgout, dm.MemoryRSS, dm.MemorySwap, dm.MemoryTotal, dynoType(dm.Source), } } // Dyno log-runtime-metrics load messages case bytes.Contains(msg, dynoLoadMsgSentinel): ctx.Count("lines.dyno.load", 1) dm := dynoLoadMsg{} err := logfmt.Unmarshal(msg, &dm) if err != nil { log.Printf("logfmt unmarshal error: %s\n", err) continue } if dm.Source != "" { chanGroup.points[DynoLoad] <- []interface{}{ timestamp, id, dm.Source, dm.LoadAvg1Min, dm.LoadAvg5Min, dm.LoadAvg15Min, dynoType(dm.Source), } } // unknown default: ctx.Count("lines.unknown.heroku", 1) if Debug { log.Printf("Unknown Heroku Line - Header: PRI: %s, Time: %s, Hostname: %s, Name: %s, ProcId: %s, MsgId: %s - Body: %s", header.PrivalVersion, header.Time, header.Hostname, header.Name, header.Procid, header.Msgid, string(msg), ) } } } // non heroku lines default: ctx.Count("lines.unknown.user", 1) if Debug { log.Printf("Unknown User Line - Header: PRI: %s, Time: %s, Hostname: %s, Name: %s, ProcId: %s, MsgId: %s - Body: %s", header.PrivalVersion, header.Time, header.Hostname, header.Name, header.Procid, header.Msgid, string(msg), ) } } } ctx.MeasureSince("lines.parse.time", parseStart) // If we are told to close the connection after the reply, do so. select { case <-connectionCloser: w.Header().Set("Connection", "close") default: //Nothing } w.WriteHeader(http.StatusNoContent) }
func (d *Drain) LogsHandler(w http.ResponseWriter, r *http.Request) { lp := lpx.NewReader(bufio.NewReader(r.Body)) for lp.Next() { d.logsCh <- NewLogLineFromLpx(lp) } }