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