summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/pkg/logs/ltsv.go
blob: b7fbceb14cc4a33bd46c8da57b7e7d2a1ce710ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// SPDX-License-Identifier: GPL-3.0-or-later

package logs

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"strconv"
	"unsafe"

	"github.com/Wing924/ltsv"
)

type (
	LTSVConfig struct {
		FieldDelimiter string            `yaml:"field_delimiter" json:"field_delimiter"`
		ValueDelimiter string            `yaml:"value_delimiter" json:"value_delimiter"`
		Mapping        map[string]string `yaml:"mapping" json:"mapping"`
	}

	LTSVParser struct {
		r       *bufio.Reader
		parser  ltsv.Parser
		mapping map[string]string
	}
)

func NewLTSVParser(config LTSVConfig, in io.Reader) (*LTSVParser, error) {
	p := ltsv.Parser{
		FieldDelimiter: ltsv.DefaultParser.FieldDelimiter,
		ValueDelimiter: ltsv.DefaultParser.ValueDelimiter,
		StrictMode:     false,
	}
	if config.FieldDelimiter != "" {
		if d, err := parseLTSVDelimiter(config.FieldDelimiter); err == nil {
			p.FieldDelimiter = d
		}
	}
	if config.ValueDelimiter != "" {
		if d, err := parseLTSVDelimiter(config.ValueDelimiter); err == nil {
			p.ValueDelimiter = d
		}
	}
	parser := &LTSVParser{
		r:       bufio.NewReader(in),
		parser:  p,
		mapping: config.Mapping,
	}
	return parser, nil
}

func (p *LTSVParser) ReadLine(line LogLine) error {
	row, err := p.r.ReadSlice('\n')
	if err != nil && len(row) == 0 {
		return err
	}
	if len(row) > 0 && row[len(row)-1] == '\n' {
		row = row[:len(row)-1]
	}
	return p.Parse(row, line)
}

func (p *LTSVParser) Parse(row []byte, line LogLine) error {
	err := p.parser.ParseLine(row, func(label []byte, value []byte) error {
		s := *(*string)(unsafe.Pointer(&label)) // no alloc, same as in fmt.Builder.String()
		if v, ok := p.mapping[s]; ok {
			s = v
		}
		return line.Assign(s, string(value))
	})
	if err != nil {
		return &ParseError{msg: fmt.Sprintf("ltsv parse: %v", err), err: err}
	}
	return nil
}

func (p LTSVParser) Info() string {
	return fmt.Sprintf("ltsv: %q", p.mapping)
}

func parseLTSVDelimiter(s string) (byte, error) {
	if isNumber(s) {
		d, err := strconv.ParseUint(s, 10, 8)
		if err != nil {
			return 0, fmt.Errorf("invalid LTSV delimiter: %v", err)
		}
		return byte(d), nil
	}
	if len(s) != 1 {
		return 0, errors.New("invalid LTSV delimiter: must be a single character")
	}
	return s[0], nil
}