File: logfmt_printer.go

package info (click to toggle)
golang-github-mightyguava-jl 0.1.0%2Bgit20220705%2B8771236337c6-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 1,112 kB
  • sloc: makefile: 5
file content (140 lines) | stat: -rw-r--r-- 3,094 bytes parent folder | download
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package jl

import (
	"encoding/json"
	"fmt"
	"io"
	"sort"
	"strings"
)

// DefaultLogfmtPreferredFields is the set of fields that NewLogfmtPrinter orders ahead of other fields.
var DefaultLogfmtPreferredFields = []string{
	"timestamp",
	"time",
	"level",
	"thread",
	"logger",
	"message",
	"msg",
	"exceptions",
}

// LogfmtPrinter prints log entries in the logfmt format.
type LogfmtPrinter struct {
	// Out is the writer where formatted logs are written to.
	Out             io.Writer
	// PreferredFields is an order list of top-level keys that the logfmt formatter will display ahead of other
	// fields in the JSON log entry.
	PreferredFields []string
	// DisableColor disables ANSI color escape sequences.
	DisableColor    bool
}

// NewLogfmtPrinter allocates and returns a new LogFmtPrinter.
func NewLogfmtPrinter(w io.Writer) *LogfmtPrinter {
	return &LogfmtPrinter{
		Out:             w,
		PreferredFields: DefaultLogfmtPreferredFields,
	}
}

func (p *LogfmtPrinter) Print(input *Entry) {
	if input.Partials == nil {
		fmt.Fprintln(p.Out, string(input.Raw))
		return
	}
	entry := newLogfmtEntry(input, p.PreferredFields)
	color := entry.Color()

	sortedFields := append(entry.preferredFields, entry.sortedFields...)
	for i, field := range sortedFields {
		if i != 0 {
			fmt.Fprint(p.Out, " ")
		}
		key := field.Key
		if !p.DisableColor {
			key = ColorText(color, field.Key)
		}
		fmt.Fprintf(p.Out, "%s=%s", key, toString(field.Value))
	}
	fmt.Fprintln(p.Out)
}

func toString(v json.RawMessage) string {
	var str string
	if err := json.Unmarshal(v, &str); err != nil {
		return string(v)
	}
	return str
}

type logfmtEntry struct {
	rawMessage      []byte
	partials        map[string]json.RawMessage
	sortedFields    []*field
	preferredFields []*field
}

func newLogfmtEntry(m *Entry, preferredFields []string) *logfmtEntry {
	var preferredKeys = stringSet(preferredFields)
	var preferred, sorted []*field
	for _, k := range DefaultLogfmtPreferredFields {
		if v, ok := m.Partials[k]; ok {
			preferred = append(preferred, newField(k, v))
		}
	}
	var sortedKeys = sortKeys(m.Partials)
	for _, k := range sortedKeys {
		if _, ok := preferredKeys[k]; ok {
			continue
		}
		v := m.Partials[k]
		sorted = append(sorted, newField(k, v))
	}
	return &logfmtEntry{
		rawMessage:      m.Raw,
		partials:        m.Partials,
		sortedFields:    sorted,
		preferredFields: preferred,
	}
}

func (e *logfmtEntry) Color() Color {
	level := "info"
	if levelField, ok := e.partials["level"]; ok {
		level = toString(levelField)
	}
	if color, ok := LevelColors[strings.ToLower(level)]; ok {
		return color
	}
	return Green
}

type field struct {
	Key   string
	Value json.RawMessage
}

func newField(k string, v json.RawMessage) *field {
	return &field{Key: k, Value: v}
}

func stringSet(l []string) map[string]interface{} {
	m := make(map[string]interface{})
	for _, v := range l {
		m[v] = nil
	}
	return m
}

func sortKeys(m map[string]json.RawMessage) []string {
	keys := make([]string, len(m))
	i := 0
	for k := range m {
		keys[i] = k
		i++
	}
	sort.StringSlice(keys).Sort()
	return keys
}