blob: 03bb6ff05883d31bcbf50797899f13ba795a51e9 [file] [log] [blame]
Lorenz Brunc7108352023-08-09 17:09:40 +02001//go:build linux
2// +build linux
3
4package logtree
5
6import (
7 "bytes"
8 "context"
9 "errors"
10 "fmt"
11 "os"
12 "strconv"
13 "strings"
14 "time"
15
16 "golang.org/x/sys/unix"
17)
18
19const (
20 loglevelEmergency = 0
21 loglevelAlert = 1
22 loglevelCritical = 2
23 loglevelError = 3
24 loglevelWarning = 4
25 loglevelNotice = 5
26 loglevelInfo = 6
27 loglevelDebug = 7
28)
29
30// KmsgPipe pipes logs from the kernel kmsg interface at /dev/kmsg into the
31// given logger.
32func KmsgPipe(ctx context.Context, lt LeveledLogger) error {
33 publisher, ok := lt.(*leveledPublisher)
34 if !ok {
35 // Fail fast, as this is a programming error.
36 panic("Expected *leveledPublisher in LeveledLogger from supervisor")
37 }
38 kmsgFile, err := os.Open("/dev/kmsg")
39 if err != nil {
40 return err
41 }
42 defer kmsgFile.Close()
43 var lastOverflow time.Time
44 // PRINTK_MESSAGE_MAX in @linux//kernel/printk:internal.h
45 linebuf := make([]byte, 2048)
46 for {
47 n, err := kmsgFile.Read(linebuf)
48 // Best-effort, in Go it is not possible to cancel a Read on-demand.
49 select {
50 case <-ctx.Done():
51 return ctx.Err()
52 default:
53 }
54 if errors.Is(err, unix.EPIPE) {
55 now := time.Now()
56 // Rate-limit to 1 per second
57 if lastOverflow.Add(1 * time.Second).Before(now) {
58 lt.Warning("Lost messages due to kernel ring buffer overflow")
59 lastOverflow = now
60 }
61 continue
62 }
63 if err != nil {
64 return fmt.Errorf("while reading from kmsg: %w", err)
65 }
66 var monotonicRaw unix.Timespec
67 if err := unix.ClockGettime(unix.CLOCK_MONOTONIC_RAW, &monotonicRaw); err != nil {
68 return fmt.Errorf("while getting monotonic timestamp: %w", err)
69 }
70 p := parseKmsg(time.Now(), time.Duration(monotonicRaw.Nano())*time.Nanosecond, linebuf[:n])
71 if p == nil {
72 continue
73 }
74 e := &entry{
75 origin: publisher.node.dn,
76 leveled: p,
77 }
78 publisher.node.tree.journal.append(e)
79 publisher.node.tree.journal.notify(e)
80 }
81}
82
83// See https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg for format.
84func parseKmsg(now time.Time, monotonicSinceBoot time.Duration, data []byte) *LeveledPayload {
85 meta, message, ok := bytes.Cut(data, []byte(";"))
86 if !ok {
87 // Unknown message format
88 return nil
89 }
90 endOfMsgIdx := bytes.IndexByte(message, '\n')
91 if endOfMsgIdx == -1 {
92 return nil
93 }
94 message = message[:endOfMsgIdx]
95 metaFields := strings.FieldsFunc(string(meta), func(r rune) bool { return r == ',' })
96 if len(metaFields) < 4 {
97 return nil
98 }
99 loglevel, err := strconv.ParseUint(metaFields[0], 10, 64)
100 if err != nil {
101 return nil
102 }
103
104 monotonicMicro, err := strconv.ParseUint(metaFields[2], 10, 64)
105 if err != nil {
106 return nil
107 }
108
109 // Kmsg entries are timestamped with CLOCK_MONOTONIC_RAW, a clock which does
110 // not have a direct correspondence with civil time (UTC). To assign best-
111 // effort timestamps, use the current monotonic clock reading to determine
112 // the elapsed time between the kmsg entry and now on the monotonic clock.
113 // This does not correspond well to elapsed UTC time on longer timescales as
114 // CLOCK_MONOTONIC_RAW is not trimmed to run true to UTC, but up to in the
115 // order of hours this is close. As the pipe generally processes messages
116 // very close to their creation date, the elapsed time and thus the accrued
117 // error is extremely small.
118 monotonic := time.Duration(monotonicMicro) * time.Microsecond
119
120 monotonicFromNow := monotonic - monotonicSinceBoot
121
122 var severity Severity
123 switch loglevel {
124 case loglevelEmergency, loglevelAlert:
125 severity = FATAL
126 case loglevelCritical, loglevelError:
127 severity = ERROR
128 case loglevelWarning:
129 severity = WARNING
130 case loglevelNotice, loglevelInfo, loglevelDebug:
131 severity = INFO
132 default:
133 severity = INFO
134 }
135
136 return &LeveledPayload{
137 timestamp: now.Add(monotonicFromNow),
138 severity: severity,
139 messages: []string{string(message)},
140 }
141}