mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-21 14:41:12 +02:00
Fixes #1563 This implements dmesg reading via `/dev/kmsg`, with message parsing and formatting. Kernel log facility and severity are parsed, timestamp is calculated relative to boot time (it's accurate unless time jumps a lot during node lifetime). New flags to follow dmesg was added, tail flag allows to stream only new message (ignoring old messages). We could try to implement tailing last N messages, just a bit more work, open to suggestions (for symmetry with regular logs). Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
200 lines
3.4 KiB
Go
200 lines
3.4 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package kmsg
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// Packet combines Message and error.
|
|
//
|
|
// Only one of the fields is set in Reader.Scan.
|
|
type Packet struct {
|
|
Message Message
|
|
Err error
|
|
}
|
|
|
|
// Reader for /dev/kmsg messages.
|
|
type Reader interface {
|
|
// Scan and issue parsed messages.
|
|
//
|
|
// Scan stops when context is canceled or when EOF is reached
|
|
// in NoFollow mode.
|
|
Scan(ctx context.Context) <-chan Packet
|
|
|
|
// Close releases resources associated with the Reader.
|
|
Close() error
|
|
}
|
|
|
|
// Option configures Reader.
|
|
type Option func(*options)
|
|
|
|
type options struct {
|
|
follow bool
|
|
tail bool
|
|
}
|
|
|
|
// Follow the kmsg to stream live messages.
|
|
func Follow() Option {
|
|
return func(o *options) {
|
|
o.follow = true
|
|
}
|
|
}
|
|
|
|
// FromTail starts reading kmsg from the tail (after last message).
|
|
func FromTail() Option {
|
|
return func(o *options) {
|
|
o.tail = true
|
|
}
|
|
}
|
|
|
|
// NewReader initializes new /dev/kmsg reader.
|
|
func NewReader(options ...Option) (Reader, error) {
|
|
r := &reader{}
|
|
|
|
for _, o := range options {
|
|
o(&r.options)
|
|
}
|
|
|
|
var err error
|
|
|
|
r.bootTime, err = getBootTime()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r.f, err = os.OpenFile("/dev/kmsg", os.O_RDONLY, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if r.options.tail {
|
|
_, err = r.f.Seek(0, os.SEEK_END)
|
|
if err != nil {
|
|
r.f.Close() //nolint: errcheck
|
|
return nil, fmt.Errorf("error seeking to the tail of kmsg: %w", err)
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
type reader struct {
|
|
f *os.File
|
|
options options
|
|
bootTime time.Time
|
|
}
|
|
|
|
func (r *reader) Close() error {
|
|
return r.f.Close()
|
|
}
|
|
|
|
func (r *reader) Scan(ctx context.Context) <-chan Packet {
|
|
ch := make(chan Packet)
|
|
|
|
if r.options.follow {
|
|
go r.scanFollow(ctx, ch)
|
|
} else {
|
|
go r.scanNoFollow(ctx, ch)
|
|
}
|
|
|
|
return ch
|
|
}
|
|
|
|
//nolint: gocyclo
|
|
func (r *reader) scanNoFollow(ctx context.Context, ch chan<- Packet) {
|
|
defer close(ch)
|
|
|
|
fd := int(r.f.Fd())
|
|
|
|
if err := syscall.SetNonblock(fd, true); err != nil {
|
|
select {
|
|
case ch <- Packet{
|
|
Err: fmt.Errorf("error switching to nonblock mode: %w", err),
|
|
}:
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, 8192)
|
|
|
|
for {
|
|
n, err := syscall.Read(fd, buf)
|
|
if err != nil {
|
|
if err == io.EOF || err == syscall.EAGAIN {
|
|
// end of file, done
|
|
return
|
|
}
|
|
|
|
if err == syscall.EPIPE {
|
|
// buffer overrun, retry
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case ch <- Packet{
|
|
Err: fmt.Errorf("error reading from kmsg: %w", err),
|
|
}:
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var packet Packet
|
|
packet.Message, packet.Err = ParseMessage(string(buf[:n]), r.bootTime)
|
|
|
|
select {
|
|
case ch <- packet:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *reader) scanFollow(ctx context.Context, ch chan<- Packet) {
|
|
defer close(ch)
|
|
|
|
buf := make([]byte, 8192)
|
|
|
|
for {
|
|
n, err := r.f.Read(buf)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
// end of file, done
|
|
return
|
|
}
|
|
|
|
if err == syscall.EPIPE {
|
|
// buffer overrun, retry
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case ch <- Packet{
|
|
Err: fmt.Errorf("error reading from kmsg: %w", err),
|
|
}:
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var packet Packet
|
|
packet.Message, packet.Err = ParseMessage(string(buf[:n]), r.bootTime)
|
|
|
|
select {
|
|
case ch <- packet:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
}
|