Files
HellreigN/agent/internal/logsource/docker/impl.go
T
2026-04-05 01:43:38 +03:00

90 lines
2.5 KiB
Go

package docker
import (
"bufio"
"fmt"
"io"
"os/exec"
"syscall"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/config"
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/agent/internal/logsource"
)
var _ logsource.LogSource = new(DockerLogSource)
// DockerLogSource reads logs from a Docker container via `docker logs -f`.
type DockerLogSource struct {
cmd *exec.Cmd
stdout io.ReadCloser
stdoutscanner *bufio.Scanner
}
// ReadLine implements logsource.LogSource.
func (d *DockerLogSource) ReadLine() (string, error) {
if d.stdoutscanner.Scan() {
return d.stdoutscanner.Text(), nil
} else {
if d.stdoutscanner.Err() == nil {
return "", fmt.Errorf("%w: %s", logsource.ErrDead, io.EOF)
}
return "", d.stdoutscanner.Err()
}
}
// Close implements logsource.LogSource.
func (d *DockerLogSource) Close() error {
_ = d.cmd.Process.Signal(syscall.SIGTERM)
return d.cmd.Wait()
}
// New creates a Docker log source for the given container.
// The container name is taken from cfg.Path (if set) or cfg.Name.
func New(cfg config.ServiceConfig) (*DockerLogSource, error) {
containerName := cfg.Name
if cfg.Path != nil && *cfg.Path != "" {
containerName = *cfg.Path
}
// docker logs -f --tail=0 --no-color <container_name>
// -f : follow new logs
// --tail=0 : skip existing logs
// --no-color: strip color codes for clean output
cmd := exec.Command("docker", "logs", "-f", "--tail=0", "--no-color", containerName) //nolint:gosec
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("failed to create stdout pipe for docker logs: %w", err)
}
// Also capture stderr since docker logs merges stdout and stderr from the container
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("failed to create stderr pipe for docker logs: %w", err)
}
err = cmd.Start()
if err != nil {
return nil, fmt.Errorf("failed to start docker logs for container %q: %w", containerName, err)
}
// Use MultiReader to merge stdout and stderr
// Docker logs outputs container stdout+stderr to its own stdout, but we also
// capture the docker CLI's stderr separately in case of errors (e.g. container not found)
stdoutscanner := bufio.NewScanner(stdout)
// Start a goroutine to consume stderr (we don't send docker CLI stderr as logs,
// but we need to prevent the pipe from filling up)
go func() {
buf := make([]byte, 4096)
for {
_, err := stderr.Read(buf)
if err != nil {
return
}
}
}()
return &DockerLogSource{cmd, stdout, stdoutscanner}, nil
}