logs: stream corrosion logs with 'uc machine logs corrosion' from container instead of journal (fixes #392)

This commit is contained in:
Pasha Sviderski
2026-06-17 18:28:25 +10:00
parent 247154eeb3
commit 7bb3cb9682
11 changed files with 91 additions and 83 deletions
+6 -2
View File
@@ -17,8 +17,12 @@ import (
"github.com/psviderski/uncloud/pkg/api"
)
// Image is the Corrosion image pinned to the uncloudd version.
const Image = "ghcr.io/unlabs-dev/corrosion:2026.6.15"
const (
// Image is the Corrosion image pinned to the uncloudd version.
Image = "ghcr.io/unlabs-dev/corrosion:2026.6.15"
// ContainerName is the name of the managed Corrosion container.
ContainerName = "uncloud-corrosion"
)
type DockerService struct {
Client *client.Client
+3 -1
View File
@@ -170,7 +170,9 @@ func (s *Service) ListImages(ctx context.Context, opts image.ListOptions) (Image
// ContainerLogs streams logs from a container and returns demultiplexed entries via a channel.
// The channel is closed when streaming completes or context is cancelled.
func (s *Service) ContainerLogs(ctx context.Context, containerID string, opts api.ServiceLogsOptions) (<-chan api.LogEntry, error) {
func (s *Service) ContainerLogs(
ctx context.Context, containerID string, opts api.ServiceLogsOptions,
) (<-chan api.LogEntry, error) {
dockerOpts := container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
+22 -6
View File
@@ -13,6 +13,7 @@ import (
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
"time"
@@ -139,7 +140,7 @@ func (c *Config) SetDefaults() (*Config, error) {
cfg.CorrosionService = &corroservice.DockerService{
Client: cfg.DockerClient,
Image: corroservice.Image,
Name: "uncloud-corrosion",
Name: corroservice.ContainerName,
DataDir: cfg.CorrosionDataDir,
RunDir: cfg.CorrosionRunDir,
User: fmt.Sprintf("%d:%d", uid, gid),
@@ -1217,7 +1218,7 @@ func (m *Machine) InspectService(
// logsHeartbeatInterval is the interval at which heartbeat entries are sent when there are no logs to stream.
const logsHeartbeatInterval = 200 * time.Millisecond
// MachineLogs streams logs from a systemd service.
// MachineLogs streams logs from a system service.
func (m *Machine) MachineLogs(
req *pb.LogsRequest, stream grpc.ServerStreamingServer[pb.LogEntry],
) error {
@@ -1231,16 +1232,31 @@ func (m *Machine) MachineLogs(
Until: req.Until,
}
logsCh, err := journal.Logs(ctx, req.Id, opts)
var logsCh <-chan api.LogEntry
var err error
log := slog.With("stream_id", fmt.Sprintf("%p", stream)[2:])
switch req.Id {
case api.SystemServiceUncloud, api.SystemServiceDocker:
// These run as systemd units whose names match the service name.
logsCh, err = journal.Logs(ctx, req.Id, opts)
log = log.With("unit", req.Id)
case api.SystemServiceCorrosion:
// Corrosion runs as a daemon-managed container, not a systemd unit, so read its logs
// from the container, the same way `uc logs` does for service containers.
logsCh, err = m.dockerService.ContainerLogs(ctx, corroservice.ContainerName, opts)
log = log.With("container", corroservice.ContainerName)
default:
return status.Errorf(codes.InvalidArgument, "unsupported system service %q; supported services: %s",
req.Id, strings.Join(api.SystemServices, ", "))
}
if err != nil {
if errdefs.IsNotFound(err) {
return status.Error(codes.NotFound, err.Error())
}
return status.Errorf(codes.Internal, "get journal logs: %v", err)
return status.Errorf(codes.Internal, "get logs: %v", err)
}
log := slog.With("unit", req.Id, "stream_id", fmt.Sprintf("%p", stream)[2:])
log.Debug("Starting systemd service logs streaming.",
log.Debug("Starting system service logs streaming.",
"follow", req.Follow, "tail", req.Tail, "since", req.Since, "until", req.Until)
// Heartbeats are needed only when following logs to let the client know when there are no new log entries