From 92abb1be097e10305a2db61af9285654826cee4a Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 8 Apr 2026 15:21:32 -0700 Subject: [PATCH] Unify logging with slog Signed-off-by: Derek McGowan --- cmd/containerd-shim-nerdbox-v1/main.go | 11 +- cmd/vminitd/main.go | 26 ++- go.mod | 2 +- go.sum | 4 +- internal/logging/logging.go | 236 +++++++++++++++++++++++ internal/logging/logging_test.go | 250 +++++++++++++++++++++++++ internal/logging/shim_unix.go | 38 ++++ internal/logging/shim_windows.go | 68 +++++++ internal/systools/dump.go | 39 ++-- internal/vm/libkrun/instance.go | 7 +- 10 files changed, 635 insertions(+), 46 deletions(-) create mode 100644 internal/logging/logging.go create mode 100644 internal/logging/logging_test.go create mode 100644 internal/logging/shim_unix.go create mode 100644 internal/logging/shim_windows.go diff --git a/cmd/containerd-shim-nerdbox-v1/main.go b/cmd/containerd-shim-nerdbox-v1/main.go index 50c469a..6b73751 100644 --- a/cmd/containerd-shim-nerdbox-v1/main.go +++ b/cmd/containerd-shim-nerdbox-v1/main.go @@ -21,6 +21,7 @@ import ( "github.com/containerd/containerd/v2/pkg/shim" + "github.com/containerd/nerdbox/internal/logging" "github.com/containerd/nerdbox/internal/shim/manager" _ "github.com/containerd/nerdbox/plugins/shim/sandbox" @@ -30,6 +31,14 @@ import ( _ "github.com/containerd/nerdbox/plugins/vm/libkrun" ) +func init() { + logging.SetupShimLog() +} + func main() { - shim.RunShim(context.Background(), manager.NewShimManager("io.containerd.nerdbox.v1")) + shim.RunShim(context.Background(), manager.NewShimManager("io.containerd.nerdbox.v1"), + func(c *shim.Config) { + c.NoSetupLogger = true + }, + ) } diff --git a/cmd/vminitd/main.go b/cmd/vminitd/main.go index 3e76bc9..04449b0 100644 --- a/cmd/vminitd/main.go +++ b/cmd/vminitd/main.go @@ -23,6 +23,7 @@ import ( "errors" "flag" "fmt" + "log/slog" "net" "os" "os/signal" @@ -55,6 +56,21 @@ import ( _ "github.com/containerd/nerdbox/plugins/vminit/task" ) +// logLevel controls the slog handler level for vminitd. +var logLevel = &slog.LevelVar{} + +func init() { + log.UseSlog() + // Write structured logs to /dev/console rather than stderr so that + // output does not end up in the kernel message buffer (kmsg). + console, err := os.OpenFile("/dev/console", os.O_WRONLY, 0644) + if err != nil { + console = os.Stderr + } + handler := slog.NewJSONHandler(console, &slog.HandlerOptions{Level: logLevel}) + slog.SetDefault(slog.New(handler).With("component", "vminitd")) +} + func main() { t1 := time.Now() var ( @@ -74,18 +90,10 @@ func main() { } flag.CommandLine.Parse(args) - /* - c, err := os.OpenFile("/dev/console", os.O_WRONLY, 0644) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open /dev/console: %v\n", err) - os.Exit(1) - } - defer c.Close() - log.L.Logger.SetOutput(c) - */ var err error if *dev || config.Debug { + logLevel.Set(slog.LevelDebug) log.SetLevel("debug") } diff --git a/go.mod b/go.mod index 4552ebb..8cf9870 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 github.com/containerd/fifo v1.1.0 github.com/containerd/go-runc v1.1.0 - github.com/containerd/log v0.1.0 + github.com/containerd/log v0.1.1-0.20260403072107-cb1839ebf76b github.com/containerd/otelttrpc v0.1.0 github.com/containerd/plugin v1.0.0 github.com/containerd/ttrpc v1.2.8 diff --git a/go.sum b/go.sum index e934f3e..4d40713 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gGleA= github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/log v0.1.1-0.20260403072107-cb1839ebf76b h1:VT47r68OzwhsTu84qAaG6Dv7xQVRmMvt7yotn9auLtI= +github.com/containerd/log v0.1.1-0.20260403072107-cb1839ebf76b/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/otelttrpc v0.1.0 h1:UOX68eVTE8H/T45JveIg+I22Ev2aFj4qPITCmXsskjw= github.com/containerd/otelttrpc v0.1.0/go.mod h1:XhoA2VvaGPW1clB2ULwrBZfXVuEWuyOd2NUD1IM0yTg= github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4= diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..f511eb1 --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,236 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package logging provides unified structured logging utilities for the +// shim and vminitd components. +package logging + +import ( + "bufio" + "context" + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "time" + + "github.com/containerd/log" +) + +// SetupShimLog configures slog-based logging for the shim process. +// It opens the platform-specific log output (FIFO on Unix, named pipe +// on Windows), then creates a slog TextHandler and sets it as the +// default logger with a "component=shim" attribute. +// +// The base handler (without component) is stored for use by +// [ForwardConsoleLogs] so that forwarded records carry their own +// component rather than inheriting "shim". +// +// For the short-lived start and delete actions, only [log.UseSlog] is +// called to route logrus through slog; the log output is not opened. +func SetupShimLog() { + log.UseSlog() + + var ( + debug bool + ns string + id string + attrs []slog.Attr + ) + args := os.Args[1:] + for i := 0; i < len(args); i++ { + switch args[i] { + case "start", "delete": + return + case "-debug": + debug = true + case "-namespace": + if i+1 < len(args) { + i++ + ns = args[i] + attrs = append(attrs, slog.String("ns", ns)) + } + case "-id": + if i+1 < len(args) { + i++ + id = args[i] + attrs = append(attrs, slog.String("id", id)) + } + } + } + + w := openShimLog(ns, id) + + var level slog.LevelVar + if debug { + level.Set(slog.LevelDebug) + log.SetLevel("debug") //nolint:errcheck + } + + handler := slog.NewTextHandler(w, &slog.HandlerOptions{Level: &level}).WithAttrs(attrs) + SetBaseHandler(handler) + slog.SetDefault(slog.New(handler).With("component", "shim")) +} + +// baseHandler is the slog handler used by ForwardConsoleLogs to emit +// records without the caller's pre-applied attributes (e.g. component=shim). +var baseHandler slog.Handler + +// SetBaseHandler stores the base handler for use by ForwardConsoleLogs. +// This should be called before any console forwarding starts, typically +// during init with the handler before any .With() attributes are applied. +func SetBaseHandler(h slog.Handler) { + baseHandler = h +} + +// consoleHandler returns the handler that ForwardConsoleLogs should use. +// It prefers the base handler set via SetBaseHandler, falling back to +// the default slog handler. +func consoleHandler() slog.Handler { + if baseHandler != nil { + return baseHandler + } + return slog.Default().Handler() +} + +// ForwardConsoleLogs reads lines from r and re-emits them as structured +// log entries through the base [slog.Handler] set via [SetBaseHandler]. +// +// Lines that are valid JSON objects (emitted by vminitd's JSON slog handler) +// are parsed and re-emitted preserving the original level, message, +// and attributes. All other lines are treated as kernel messages and emitted +// at INFO level with component=kmsg. +// +// The base handler is used directly (rather than the default logger) so that +// pre-applied attributes such as component=shim are not added to forwarded +// records, which carry their own component. +// +// If raw is non-nil, every line is also written there verbatim (useful for +// tests that need the unprocessed console output). +func ForwardConsoleLogs(r io.Reader, raw io.Writer) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + + if raw != nil { + raw.Write([]byte(line)) + raw.Write([]byte("\n")) + } + + if line == "" { + continue + } + + if strings.HasPrefix(line, "{") { + if forwardJSONLog(line) { + continue + } + } + + // Kernel message — parse optional "[ 1.234567] " timestamp prefix. + msg := line + attrs := []slog.Attr{slog.String("component", "kmsg")} + if after, ktime, ok := parseKernelTimestamp(line); ok { + msg = after + attrs = append(attrs, slog.String("ktime", ktime)) + } + record := slog.NewRecord(time.Now(), slog.LevelInfo, msg, 0) + record.AddAttrs(attrs...) + handler := consoleHandler() + if handler.Enabled(context.Background(), slog.LevelInfo) { + handler.Handle(context.Background(), record) //nolint:errcheck + } + } + if err := scanner.Err(); err != nil { + record := slog.NewRecord(time.Now(), slog.LevelWarn, "console log reader stopped", 0) + record.AddAttrs(slog.String("component", "kmsg"), slog.Any("error", err)) + handler := consoleHandler() + if handler.Enabled(context.Background(), slog.LevelWarn) { + handler.Handle(context.Background(), record) //nolint:errcheck + } + } +} + +// forwardJSONLog attempts to parse line as a JSON slog record and emit it +// through the console handler. Returns true if the line was handled. +func forwardJSONLog(line string) bool { + var fields map[string]json.RawMessage + if err := json.Unmarshal([]byte(line), &fields); err != nil { + return false + } + + // A valid vminitd log must at least have "msg". + rawMsg, ok := fields["msg"] + if !ok { + return false + } + + var msg string + if err := json.Unmarshal(rawMsg, &msg); err != nil { + return false + } + delete(fields, "msg") + + var level slog.Level + if raw, ok := fields["level"]; ok { + var s string + if err := json.Unmarshal(raw, &s); err == nil { + level.UnmarshalText([]byte(s)) //nolint:errcheck + } + delete(fields, "level") + } + + // Discard the VM-side timestamp — the guest clock is not + // synchronised and typically reads as epoch. + delete(fields, "time") + t := time.Now() + + handler := consoleHandler() + if !handler.Enabled(context.Background(), level) { + return true + } + + record := slog.NewRecord(t, level, msg, 0) + for k, v := range fields { + var val any + if err := json.Unmarshal(v, &val); err == nil { + record.AddAttrs(slog.Any(k, val)) + } + } + + handler.Handle(context.Background(), record) //nolint:errcheck + return true +} + +// parseKernelTimestamp extracts the "[ seconds.usecs] " prefix from a +// kernel log line. Returns the message after the prefix, the timestamp +// string, and whether a timestamp was found. +func parseKernelTimestamp(line string) (msg, ktime string, ok bool) { + if len(line) < 3 || line[0] != '[' { + return "", "", false + } + end := strings.IndexByte(line, ']') + if end < 0 { + return "", "", false + } + ktime = strings.TrimSpace(line[1:end]) + msg = line[end+1:] + if len(msg) > 0 && msg[0] == ' ' { + msg = msg[1:] + } + return msg, ktime, true +} diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go new file mode 100644 index 0000000..45741de --- /dev/null +++ b/internal/logging/logging_test.go @@ -0,0 +1,250 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package logging + +import ( + "bytes" + "context" + "io" + "log/slog" + "strings" + "testing" +) + +// discardHandler is a handler that discards all output but still processes records. +type discardHandler struct{} + +func (discardHandler) Enabled(context.Context, slog.Level) bool { return true } +func (discardHandler) Handle(context.Context, slog.Record) error { return nil } +func (h discardHandler) WithAttrs([]slog.Attr) slog.Handler { return h } +func (h discardHandler) WithGroup(string) slog.Handler { return h } + +// --- forwardJSONLog benchmarks --- + +var sampleJSONLog = `{"time":"2024-01-01T00:00:00.000000000Z","level":"INFO","msg":"starting vminitd","component":"vminitd","args":["--debug"]}` +var sampleJSONLogDebug = `{"time":"2024-01-01T00:00:00.000000000Z","level":"DEBUG","msg":"loaded plugin","component":"vminitd","plugin_id":"io.containerd.task.v1"}` +var sampleJSONLogManyFields = `{"time":"2024-01-01T00:00:00.000000000Z","level":"INFO","msg":"network configured","component":"vminitd","iface":"eth0","ip":"10.0.0.2","gateway":"10.0.0.1","dns":"8.8.8.8","mtu":1500}` + +func BenchmarkForwardJSONLog_Typical(b *testing.B) { + SetBaseHandler(discardHandler{}) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + forwardJSONLog(sampleJSONLog) + } +} + +func BenchmarkForwardJSONLog_ManyFields(b *testing.B) { + SetBaseHandler(discardHandler{}) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + forwardJSONLog(sampleJSONLogManyFields) + } +} + +func BenchmarkForwardJSONLog_NotJSON(b *testing.B) { + SetBaseHandler(discardHandler{}) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + forwardJSONLog("{not valid json") + } +} + +// --- ForwardConsoleLogs benchmarks --- + +func buildConsoleInput(nKmsg, nJSON int) string { + var sb strings.Builder + kmsg := "[ 1.234567] virtio_net virtio0 enp0s3: renamed from eth0\n" + jsonLine := sampleJSONLog + "\n" + for i := 0; i < nKmsg; i++ { + sb.WriteString(kmsg) + } + for i := 0; i < nJSON; i++ { + sb.WriteString(jsonLine) + } + return sb.String() +} + +func BenchmarkForwardConsoleLogs_KmsgOnly(b *testing.B) { + SetBaseHandler(discardHandler{}) + input := buildConsoleInput(100, 0) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + ForwardConsoleLogs(strings.NewReader(input), nil) + } +} + +func BenchmarkForwardConsoleLogs_JSONOnly(b *testing.B) { + SetBaseHandler(discardHandler{}) + input := buildConsoleInput(0, 100) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + ForwardConsoleLogs(strings.NewReader(input), nil) + } +} + +func BenchmarkForwardConsoleLogs_Mixed(b *testing.B) { + SetBaseHandler(discardHandler{}) + input := buildConsoleInput(50, 50) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + ForwardConsoleLogs(strings.NewReader(input), nil) + } +} + +func BenchmarkForwardConsoleLogs_WithRawWriter(b *testing.B) { + SetBaseHandler(discardHandler{}) + input := buildConsoleInput(50, 50) + b.ResetTimer() + b.ReportAllocs() + for b.Loop() { + ForwardConsoleLogs(strings.NewReader(input), io.Discard) + } +} + +// --- Correctness tests --- + +// collectHandler collects records for inspection. +type collectHandler struct { + records []slog.Record +} + +func (h *collectHandler) Enabled(context.Context, slog.Level) bool { return true } +func (h *collectHandler) Handle(_ context.Context, r slog.Record) error { + h.records = append(h.records, r) + return nil +} +func (h *collectHandler) WithAttrs([]slog.Attr) slog.Handler { return h } +func (h *collectHandler) WithGroup(string) slog.Handler { return h } + +func TestForwardConsoleLogs_JSONLine(t *testing.T) { + ch := &collectHandler{} + SetBaseHandler(ch) + + input := sampleJSONLog + "\n" + ForwardConsoleLogs(strings.NewReader(input), nil) + + if len(ch.records) != 1 { + t.Fatalf("expected 1 record, got %d", len(ch.records)) + } + r := ch.records[0] + if r.Message != "starting vminitd" { + t.Errorf("unexpected message: %s", r.Message) + } + if r.Level != slog.LevelInfo { + t.Errorf("unexpected level: %s", r.Level) + } + var gotComponent string + r.Attrs(func(a slog.Attr) bool { + if a.Key == "component" { + gotComponent = a.Value.String() + } + return true + }) + if gotComponent != "vminitd" { + t.Errorf("expected component=vminitd, got %q", gotComponent) + } +} + +func TestForwardConsoleLogs_KernelLine(t *testing.T) { + ch := &collectHandler{} + SetBaseHandler(ch) + + input := "[ 1.234567] virtio_net virtio0 enp0s3: renamed from eth0\n" + ForwardConsoleLogs(strings.NewReader(input), nil) + + if len(ch.records) != 1 { + t.Fatalf("expected 1 record, got %d", len(ch.records)) + } + r := ch.records[0] + if r.Level != slog.LevelInfo { + t.Errorf("unexpected level: %s", r.Level) + } + var gotComponent string + r.Attrs(func(a slog.Attr) bool { + if a.Key == "component" { + gotComponent = a.Value.String() + } + return true + }) + if gotComponent != "kmsg" { + t.Errorf("expected component=kmsg, got %q", gotComponent) + } +} + +func TestForwardConsoleLogs_RawWriter(t *testing.T) { + ch := &collectHandler{} + SetBaseHandler(ch) + + input := "kernel line\n" + sampleJSONLog + "\n" + var raw bytes.Buffer + ForwardConsoleLogs(strings.NewReader(input), &raw) + + if len(ch.records) != 2 { + t.Fatalf("expected 2 records, got %d", len(ch.records)) + } + // Raw writer should get both lines verbatim. + expected := "kernel line\n" + sampleJSONLog + "\n" + if raw.String() != expected { + t.Errorf("raw output mismatch:\ngot: %q\nwant: %q", raw.String(), expected) + } +} + +func TestForwardConsoleLogs_EmptyLines(t *testing.T) { + ch := &collectHandler{} + SetBaseHandler(ch) + + input := "\n\nsome message\n\n" + ForwardConsoleLogs(strings.NewReader(input), nil) + + if len(ch.records) != 1 { + t.Fatalf("expected 1 record, got %d", len(ch.records)) + } +} + +func TestForwardConsoleLogs_DebugLevel(t *testing.T) { + ch := &collectHandler{} + SetBaseHandler(ch) + + ForwardConsoleLogs(strings.NewReader(sampleJSONLogDebug+"\n"), nil) + + if len(ch.records) != 1 { + t.Fatalf("expected 1 record, got %d", len(ch.records)) + } + if ch.records[0].Level != slog.LevelDebug { + t.Errorf("expected DEBUG, got %s", ch.records[0].Level) + } +} + +func TestForwardJSONLog_InvalidJSON(t *testing.T) { + SetBaseHandler(discardHandler{}) + if forwardJSONLog("{not json") { + t.Error("expected false for invalid JSON") + } +} + +func TestForwardJSONLog_NoMsg(t *testing.T) { + SetBaseHandler(discardHandler{}) + if forwardJSONLog(`{"level":"INFO","time":"2024-01-01T00:00:00Z"}`) { + t.Error("expected false for JSON without msg field") + } +} diff --git a/internal/logging/shim_unix.go b/internal/logging/shim_unix.go new file mode 100644 index 0000000..10224a2 --- /dev/null +++ b/internal/logging/shim_unix.go @@ -0,0 +1,38 @@ +//go:build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package logging + +import ( + "context" + "io" + "os" + + "github.com/containerd/fifo" + "golang.org/x/sys/unix" +) + +// openShimLog opens the "log" FIFO created by containerd and dup2's it +// onto stderr so that any raw stderr writes also reach containerd. +func openShimLog(string, string) io.Writer { + f, err := fifo.OpenFifoDup2(context.Background(), "log", unix.O_WRONLY, 0700, int(os.Stderr.Fd())) + if err != nil { + return os.Stderr + } + return f +} diff --git a/internal/logging/shim_windows.go b/internal/logging/shim_windows.go new file mode 100644 index 0000000..8fb9f4e --- /dev/null +++ b/internal/logging/shim_windows.go @@ -0,0 +1,68 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package logging + +import ( + "fmt" + "io" + "os" + + "github.com/Microsoft/go-winio" + "golang.org/x/sys/windows" +) + +// openShimLog creates a named pipe for containerd to read shim logs and +// redirects os.Stderr to it, mirroring the containerd shim framework's +// Windows openLog behavior. +// +// The namespace and id are read from os.Args by the caller +// ([SetupShimLog]) and passed through the package-level shimNamespace +// and shimID variables. +func openShimLog(ns, id string) io.Writer { + if ns == "" || id == "" { + return os.Stderr + } + pipeName := fmt.Sprintf(`\\.\pipe\containerd-shim-%s-%s-log`, ns, id) + l, err := winio.ListenPipe(pipeName, nil) + if err != nil { + return os.Stderr + } + + pr, pw, err := os.Pipe() + if err != nil { + l.Close() + return os.Stderr + } + + oldStderr := os.Stderr + os.Stderr = pw + windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(pw.Fd())) //nolint:errcheck + + go func() { + conn, err := l.Accept() + l.Close() + + var w io.Writer = oldStderr + if err == nil { + w = io.MultiWriter(oldStderr, conn) + defer conn.Close() + } + io.Copy(w, pr) //nolint:errcheck + }() + + return pw +} diff --git a/internal/systools/dump.go b/internal/systools/dump.go index 23e2c64..871e399 100644 --- a/internal/systools/dump.go +++ b/internal/systools/dump.go @@ -17,11 +17,8 @@ package systools import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "os" "os/exec" "path/filepath" @@ -67,30 +64,16 @@ func DumpInfo(ctx context.Context) { } func DumpFile(ctx context.Context, name string) { - e := log.G(ctx) - if e.Logger.IsLevelEnabled(log.DebugLevel) { - f, err := os.Open(name) - if err == nil { - defer f.Close() - log.G(ctx).WithField("f", name).Debug("dumping file to stderr") - if strings.HasSuffix(name, ".json") { - var b bytes.Buffer - v := map[string]any{} - io.Copy(&b, f) - if err := json.Unmarshal(b.Bytes(), &v); err != nil { - os.Stderr.Write(b.Bytes()) - fmt.Fprintln(os.Stderr) - return - } - enc := json.NewEncoder(os.Stderr) - enc.SetIndent("", " ") - enc.Encode(v) - } else { - io.Copy(os.Stderr, f) - fmt.Fprintln(os.Stderr) - } - } else { - log.G(ctx).WithError(err).WithField("f", name).Warn("failed to open file to dump") - } + if !log.G(ctx).Logger.IsLevelEnabled(log.DebugLevel) { + return + } + b, err := os.ReadFile(name) + if err != nil { + log.G(ctx).WithError(err).WithField("f", name).Warn("failed to read file to dump") + return } + log.G(ctx).WithFields(log.Fields{ + "f": name, + "content": string(b), + }).Debug("dump file") } diff --git a/internal/vm/libkrun/instance.go b/internal/vm/libkrun/instance.go index 98671b8..fc88f9a 100644 --- a/internal/vm/libkrun/instance.go +++ b/internal/vm/libkrun/instance.go @@ -34,6 +34,7 @@ import ( "github.com/containerd/log" "github.com/containerd/ttrpc" + "github.com/containerd/nerdbox/internal/logging" "github.com/containerd/nerdbox/internal/vm" ) @@ -251,11 +252,7 @@ func (v *vmInstance) Start(ctx context.Context, opts ...vm.StartOpt) (err error) return fmt.Errorf("failed to set up console: %w", err) } if lr != nil { - consoleW := io.Writer(os.Stderr) - if startOpts.ConsoleWriter != nil { - consoleW = io.MultiWriter(os.Stderr, startOpts.ConsoleWriter) - } - go io.Copy(consoleW, lr) + go logging.ForwardConsoleLogs(lr, startOpts.ConsoleWriter) } cwd, err := os.Getwd()