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