| //go:build linux | 
 | // +build linux | 
 |  | 
 | package logtree | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"context" | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"os" | 
 | 	"strconv" | 
 | 	"strings" | 
 | 	"time" | 
 |  | 
 | 	"golang.org/x/sys/unix" | 
 | ) | 
 |  | 
 | const ( | 
 | 	loglevelEmergency = 0 | 
 | 	loglevelAlert     = 1 | 
 | 	loglevelCritical  = 2 | 
 | 	loglevelError     = 3 | 
 | 	loglevelWarning   = 4 | 
 | 	loglevelNotice    = 5 | 
 | 	loglevelInfo      = 6 | 
 | 	loglevelDebug     = 7 | 
 | ) | 
 |  | 
 | // KmsgPipe pipes logs from the kernel kmsg interface at /dev/kmsg into the | 
 | // given logger. | 
 | func KmsgPipe(ctx context.Context, lt LeveledLogger) error { | 
 | 	publisher, ok := lt.(*leveledPublisher) | 
 | 	if !ok { | 
 | 		// Fail fast, as this is a programming error. | 
 | 		panic("Expected *leveledPublisher in LeveledLogger from supervisor") | 
 | 	} | 
 | 	kmsgFile, err := os.Open("/dev/kmsg") | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	defer kmsgFile.Close() | 
 | 	var lastOverflow time.Time | 
 | 	// PRINTK_MESSAGE_MAX in @linux//kernel/printk:internal.h | 
 | 	linebuf := make([]byte, 2048) | 
 | 	for { | 
 | 		n, err := kmsgFile.Read(linebuf) | 
 | 		// Best-effort, in Go it is not possible to cancel a Read on-demand. | 
 | 		select { | 
 | 		case <-ctx.Done(): | 
 | 			return ctx.Err() | 
 | 		default: | 
 | 		} | 
 | 		if errors.Is(err, unix.EPIPE) { | 
 | 			now := time.Now() | 
 | 			// Rate-limit to 1 per second | 
 | 			if lastOverflow.Add(1 * time.Second).Before(now) { | 
 | 				lt.Warning("Lost messages due to kernel ring buffer overflow") | 
 | 				lastOverflow = now | 
 | 			} | 
 | 			continue | 
 | 		} | 
 | 		if err != nil { | 
 | 			return fmt.Errorf("while reading from kmsg: %w", err) | 
 | 		} | 
 | 		var monotonicRaw unix.Timespec | 
 | 		if err := unix.ClockGettime(unix.CLOCK_MONOTONIC_RAW, &monotonicRaw); err != nil { | 
 | 			return fmt.Errorf("while getting monotonic timestamp: %w", err) | 
 | 		} | 
 | 		p := parseKmsg(time.Now(), time.Duration(monotonicRaw.Nano())*time.Nanosecond, linebuf[:n]) | 
 | 		if p == nil { | 
 | 			continue | 
 | 		} | 
 | 		e := &entry{ | 
 | 			origin:  publisher.node.dn, | 
 | 			leveled: p, | 
 | 		} | 
 | 		publisher.node.tree.journal.append(e) | 
 | 		publisher.node.tree.journal.notify(e) | 
 | 	} | 
 | } | 
 |  | 
 | // See https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg for format. | 
 | func parseKmsg(now time.Time, monotonicSinceBoot time.Duration, data []byte) *LeveledPayload { | 
 | 	meta, message, ok := bytes.Cut(data, []byte(";")) | 
 | 	if !ok { | 
 | 		// Unknown message format | 
 | 		return nil | 
 | 	} | 
 | 	endOfMsgIdx := bytes.IndexByte(message, '\n') | 
 | 	if endOfMsgIdx == -1 { | 
 | 		return nil | 
 | 	} | 
 | 	message = message[:endOfMsgIdx] | 
 | 	metaFields := strings.FieldsFunc(string(meta), func(r rune) bool { return r == ',' }) | 
 | 	if len(metaFields) < 4 { | 
 | 		return nil | 
 | 	} | 
 | 	loglevel, err := strconv.ParseUint(metaFields[0], 10, 64) | 
 | 	if err != nil { | 
 | 		return nil | 
 | 	} | 
 |  | 
 | 	monotonicMicro, err := strconv.ParseUint(metaFields[2], 10, 64) | 
 | 	if err != nil { | 
 | 		return nil | 
 | 	} | 
 |  | 
 | 	// Kmsg entries are timestamped with CLOCK_MONOTONIC_RAW, a clock which does | 
 | 	// not have a direct correspondence with civil time (UTC). To assign best- | 
 | 	// effort timestamps, use the current monotonic clock reading to determine | 
 | 	// the elapsed time between the kmsg entry and now on the monotonic clock. | 
 | 	// This does not correspond well to elapsed UTC time on longer timescales as | 
 | 	// CLOCK_MONOTONIC_RAW is not trimmed to run true to UTC, but up to in the | 
 | 	// order of hours this is close. As the pipe generally processes messages | 
 | 	// very close to their creation date, the elapsed time and thus the accrued | 
 | 	// error is extremely small. | 
 | 	monotonic := time.Duration(monotonicMicro) * time.Microsecond | 
 |  | 
 | 	monotonicFromNow := monotonic - monotonicSinceBoot | 
 |  | 
 | 	var severity Severity | 
 | 	switch loglevel { | 
 | 	case loglevelEmergency, loglevelAlert: | 
 | 		severity = FATAL | 
 | 	case loglevelCritical, loglevelError: | 
 | 		severity = ERROR | 
 | 	case loglevelWarning: | 
 | 		severity = WARNING | 
 | 	case loglevelNotice, loglevelInfo, loglevelDebug: | 
 | 		severity = INFO | 
 | 	default: | 
 | 		severity = INFO | 
 | 	} | 
 |  | 
 | 	return &LeveledPayload{ | 
 | 		timestamp: now.Add(monotonicFromNow), | 
 | 		severity:  severity, | 
 | 		messages:  []string{string(message)}, | 
 | 	} | 
 | } |