mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
logs: stream corrosion logs with 'uc machine logs corrosion' from container instead of journal (fixes #392)
This commit is contained in:
@@ -129,7 +129,7 @@ func (f *Formatter) PrintEntry(entry api.ServiceLogEntry) {
|
||||
output.WriteString(f.formatMachine(entry.Metadata.MachineName))
|
||||
output.WriteString(" ")
|
||||
|
||||
// Service/container_id or service name for a systemd service.
|
||||
// Service/container_id or service name for a system service.
|
||||
output.WriteString(f.formatService(entry.Metadata.ServiceName, entry.Metadata.ContainerID, entry.Metadata.Hook))
|
||||
output.WriteString(" ")
|
||||
|
||||
@@ -160,7 +160,7 @@ func (f *Formatter) printError(entry api.ServiceLogEntry) {
|
||||
stringid.TruncateID(entry.Metadata.ContainerID),
|
||||
entry.Metadata.MachineName)
|
||||
} else {
|
||||
msg = fmt.Sprintf("WARNING: log stream from systemd service '%s' on machine '%s'",
|
||||
msg = fmt.Sprintf("WARNING: log stream from system service '%s' on machine '%s'",
|
||||
entry.Metadata.ServiceName,
|
||||
entry.Metadata.MachineName)
|
||||
}
|
||||
|
||||
@@ -10,31 +10,11 @@ import (
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
UnitUncloud = "uncloud"
|
||||
UnitDocker = "docker"
|
||||
UnitCorrosion = "uncloud-corrosion"
|
||||
)
|
||||
|
||||
func ValidUnit(unit string) bool {
|
||||
switch unit {
|
||||
case UnitUncloud:
|
||||
case UnitDocker:
|
||||
case UnitCorrosion:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const journalctl = "journalctl"
|
||||
|
||||
var commandContext = exec.CommandContext // allow override for test
|
||||
|
||||
func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.ReadCloser, func() error, error) {
|
||||
if !ValidUnit(unit) {
|
||||
return nil, nil, fmt.Errorf("journal logs: invalid unit: %s", unit)
|
||||
}
|
||||
args := []string{"-u", unit, "--no-hostname"}
|
||||
args = append(args, "-n")
|
||||
if opts.Tail > -1 {
|
||||
|
||||
@@ -3,7 +3,6 @@ package journal
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -13,10 +12,6 @@ import (
|
||||
|
||||
// Logs streams logs from a service and returns entries via a channel.
|
||||
func Logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (<-chan api.LogEntry, error) {
|
||||
if !ValidUnit(unit) {
|
||||
return nil, fmt.Errorf("journal logs: invalid unit: %s", unit)
|
||||
}
|
||||
|
||||
reader, wait, err := logs(ctx, unit, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user