Skip empty lines from the scanner that result from double newlines (application slog trailing newline + container runtime newline). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.0 KiB
Go
80 lines
2.0 KiB
Go
package agent
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
|
|
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
|
|
"git.wntrmute.dev/mc/mcp/internal/registry"
|
|
"git.wntrmute.dev/mc/mcp/internal/runtime"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// Logs streams container logs for a service component.
|
|
func (a *Agent) Logs(req *mcpv1.LogsRequest, stream mcpv1.McpAgentService_LogsServer) error {
|
|
if req.GetService() == "" {
|
|
return status.Error(codes.InvalidArgument, "service name is required")
|
|
}
|
|
|
|
// Resolve component name.
|
|
component := req.GetComponent()
|
|
if component == "" {
|
|
components, err := registry.ListComponents(a.DB, req.GetService())
|
|
if err != nil {
|
|
return status.Errorf(codes.Internal, "list components: %v", err)
|
|
}
|
|
if len(components) == 0 {
|
|
return status.Error(codes.NotFound, "no components found for service")
|
|
}
|
|
component = components[0].Name
|
|
}
|
|
|
|
containerName := ContainerNameFor(req.GetService(), component)
|
|
|
|
podman, ok := a.Runtime.(*runtime.Podman)
|
|
if !ok {
|
|
return status.Error(codes.Internal, "logs requires podman runtime")
|
|
}
|
|
|
|
cmd := podman.Logs(stream.Context(), containerName, int(req.GetTail()), req.GetFollow(), req.GetTimestamps(), req.GetSince())
|
|
|
|
a.Logger.Info("running podman logs", "container", containerName, "args", cmd.Args)
|
|
|
|
// Podman writes container stdout to its stdout and container stderr
|
|
// to its stderr. Merge both into a single pipe.
|
|
pr, pw := io.Pipe()
|
|
cmd.Stdout = pw
|
|
cmd.Stderr = pw
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
pw.Close()
|
|
return status.Errorf(codes.Internal, "start podman logs: %v", err)
|
|
}
|
|
|
|
// Close the write end when the command exits so the scanner finishes.
|
|
go func() {
|
|
err := cmd.Wait()
|
|
if err != nil {
|
|
a.Logger.Warn("podman logs exited", "container", containerName, "error", err)
|
|
}
|
|
pw.Close()
|
|
}()
|
|
|
|
scanner := bufio.NewScanner(pr)
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
if err := stream.Send(&mcpv1.LogsResponse{
|
|
Data: append(line, '\n'),
|
|
}); err != nil {
|
|
_ = cmd.Process.Kill()
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|