restructure project
This commit is contained in:
100
pkg/lines/lines.go
Normal file
100
pkg/lines/lines.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package lines
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
m "git.k-space.ee/k-space/logmower-shipper/pkg/mongo"
|
||||
)
|
||||
|
||||
type (
|
||||
RawC <-chan Raw
|
||||
Raw struct {
|
||||
*File
|
||||
Offset int64
|
||||
B []byte
|
||||
}
|
||||
|
||||
// file.File, but avoiding import cycle
|
||||
File struct {
|
||||
*m.File
|
||||
MetricsName string // filepath.Base()
|
||||
}
|
||||
)
|
||||
|
||||
// assumes all lines are from same file
|
||||
func (unparsed RawC) Process(bufferLimitBytes int, parsed chan<- m.Record) {
|
||||
lines := make(chan singleLine)
|
||||
go unparsed.parse(lines)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
stdOut, stdErr := make(chan singleLine), make(chan singleLine)
|
||||
go func() {
|
||||
singleLines(stdOut).process(bufferLimitBytes, parsed)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
singleLines(stdErr).process(bufferLimitBytes, parsed)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// split stdout and stderr
|
||||
for {
|
||||
line, ok := <-lines
|
||||
if !ok {
|
||||
close(stdOut)
|
||||
close(stdErr)
|
||||
wg.Wait()
|
||||
close(parsed)
|
||||
return
|
||||
}
|
||||
|
||||
if line.StdErr {
|
||||
stdErr <- line
|
||||
} else {
|
||||
stdOut <- line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lines singleLines) process(bufferLimitBytes int, parsed chan<- m.Record) {
|
||||
var firstMetadata *m.ParsedMetadata
|
||||
var buffer []byte
|
||||
|
||||
for {
|
||||
line, ok := <-lines
|
||||
if !ok {
|
||||
// partial line should always be finished with full line
|
||||
// discard any partial lines without end (full line)
|
||||
return
|
||||
}
|
||||
|
||||
if len(buffer) == 0 {
|
||||
firstMetadata = &line.ParsedMetadata
|
||||
}
|
||||
|
||||
buffer = append(buffer, line.B...)
|
||||
|
||||
if len(buffer) > bufferLimitBytes {
|
||||
promRecordDroppedTooLarge.WithLabelValues(line.MetricsName).Add(1)
|
||||
log.Printf("dropped record: size in bytes exceeds limit of %d", bufferLimitBytes)
|
||||
|
||||
buffer = nil
|
||||
continue
|
||||
}
|
||||
|
||||
if !line.partial {
|
||||
parsed <- m.Record{
|
||||
File: line.File.File,
|
||||
Offset: line.Offset,
|
||||
|
||||
String: string(buffer),
|
||||
ParsedMetadata: *firstMetadata,
|
||||
}
|
||||
|
||||
buffer = nil
|
||||
}
|
||||
}
|
||||
}
|
66
pkg/lines/lines_single.go
Normal file
66
pkg/lines/lines_single.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package lines
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
m "git.k-space.ee/k-space/logmower-shipper/pkg/mongo"
|
||||
)
|
||||
|
||||
func (unparsed RawC) parse(parsed chan<- singleLine) {
|
||||
for {
|
||||
raw, ok := <-unparsed
|
||||
if !ok {
|
||||
close(parsed)
|
||||
return
|
||||
}
|
||||
|
||||
line := singleLine{Raw: raw}
|
||||
|
||||
if err := line.parse(); err != nil {
|
||||
promRecordPrefixParsingErr.WithLabelValues(raw.MetricsName).Add(1)
|
||||
log.Printf("parsing kubernetes log line in %q: %e", raw.File.Path, err)
|
||||
}
|
||||
|
||||
// TODO: should this only be on success?
|
||||
parsed <- line
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
singleLines <-chan singleLine
|
||||
singleLine struct {
|
||||
Raw
|
||||
|
||||
// populated by parse()
|
||||
m.ParsedMetadata
|
||||
partial bool // P or F
|
||||
}
|
||||
)
|
||||
|
||||
func (line *singleLine) parse() (err error) {
|
||||
split := bytes.SplitN(line.B, []byte(" "), 4)
|
||||
if len(split) != 4 {
|
||||
return fmt.Errorf("expected at least 3 spaces in , got %d", len(split)-1)
|
||||
}
|
||||
|
||||
line.TimeKubernetes, err = time.Parse(time.RFC3339Nano, string(split[0]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid time: %w", err)
|
||||
}
|
||||
|
||||
line.StdErr = string(split[1]) == "stderr" // or stdout
|
||||
|
||||
switch string(split[2]) {
|
||||
case "P":
|
||||
line.partial = true
|
||||
case "F":
|
||||
default:
|
||||
return fmt.Errorf("partial indicator must be 'P' or 'F', not %q", split[2])
|
||||
}
|
||||
|
||||
line.B = split[3]
|
||||
return nil
|
||||
}
|
23
pkg/lines/metrics.go
Normal file
23
pkg/lines/metrics.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package lines
|
||||
|
||||
import (
|
||||
"git.k-space.ee/k-space/logmower-shipper/pkg/globals"
|
||||
prom "github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
promRecordDroppedTooLarge = promauto.NewCounterVec(prom.CounterOpts{
|
||||
Namespace: globals.PrometheusPrefix,
|
||||
// Subsystem: "record",
|
||||
Name: "dropped_lines", // "dropped",
|
||||
Help: "Records dropped due to being too large",
|
||||
}, []string{"filename"})
|
||||
|
||||
promRecordPrefixParsingErr = promauto.NewCounterVec(prom.CounterOpts{
|
||||
Namespace: globals.PrometheusPrefix,
|
||||
Subsystem: "record",
|
||||
Name: "parsing_errors",
|
||||
Help: "Errors while parsing log line prefixes",
|
||||
}, []string{"filename"})
|
||||
)
|
Reference in New Issue
Block a user