---
name: docker_container_logs
kind: function
lang: go
domain: infra
version: "2.0.0"
purity: impure
signature: "func DockerContainerLogs(opts DockerLogsOpts) ([]DockerLogLine, error)"
description: "Tail/grep logs de container Docker via engine API. Snapshot (N lineas) o streaming (callback por linea con context cancel). Demux frame stdout/stderr. Capability docker.container.logs del device_agent."
tags: [docker, docker-agent, logs, streaming, infra]
uses_functions: []
uses_types:
  - docker_logs_opts_go_infra
  - docker_log_line_go_infra
  - error_go_core
returns:
  - docker_log_line_go_infra
returns_optional: false
error_type: "error_go_core"
imports:
  - context
  - encoding/binary
  - fmt
  - io
  - net
  - net/http
  - net/url
  - strings
  - time
params:
  - name: opts
    desc: "Parametros de la peticion: container ID, tail N, since, stdout/stderr, timestamps, docker host. Ver DockerLogsOpts."
output: "Slice de DockerLogLine con stream (stdout/stderr), timestamp RFC3339 opcional y texto de la linea."
tested: true
tests:
  - "snapshot stdout y stderr demuxeados"
  - "container no encontrado retorna error"
  - "timestamps parseados del prefijo Docker"
  - "tail y since se envian como query params"
  - "streaming recibe lineas via callback"
  - "ctx cancel detiene el stream"
  - "callback error cancela el stream"
  - "frame stdout decodificado correctamente"
  - "frame stderr decodificado correctamente"
test_file_path: "functions/infra/docker_container_logs_test.go"
file_path: "functions/infra/docker_container_logs.go"
---

## Ejemplo

```go
// Snapshot: ultimas 50 lineas de stdout+stderr
lines, err := DockerContainerLogs(infra.DockerLogsOpts{
    ContainerID: "registry_api",
    Tail:        50,
    Since:       "10m",
    Stdout:      true,
    Stderr:      true,
    Timestamps:  true,
})
if err != nil {
    log.Fatal(err)
}
for _, l := range lines {
    fmt.Printf("[%s] %s %s\n", l.Stream, l.Timestamp, l.Line)
}

// Streaming: follow hasta cancelacion
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

err = infra.DockerContainerLogsStream(ctx, infra.DockerLogsOpts{
    ContainerID: "registry_api",
    Stdout:      true,
    Stderr:      true,
}, func(line infra.DockerLogLine) error {
    fmt.Printf("[%s] %s\n", line.Stream, line.Line)
    if strings.Contains(line.Line, "FATAL") {
        return fmt.Errorf("fatal error detectado")
    }
    return nil
})
```

## Cuando usarla

Cuando el device_agent necesite leer o monitorizar logs de un container Docker en tiempo real. Usar modo snapshot para health checks puntuales (N ultimas lineas). Usar streaming para tail -f reactivo con procesamiento por linea.

## Gotchas

- Containers sin `--tty` usan el protocolo de multiplexion de 8 bytes — esta funcion lo demuxea correctamente. Containers con `--tty` mezclan stdout/stderr en un stream plano sin headers, lo que puede dar `Stream = "stdout"` para todo o parsear mal el header (byte 0 podria ser el primer caracter de texto).
- Streaming consume una goroutine/conexion hasta que `ctx` se cancele o `cb` retorne error. El caller es responsable del ciclo de vida del contexto.
- `Since` acepta unix timestamp en string, RFC3339 o duracion Go ("10m", "1h30m"). El daemon Docker acepta los 3 formatos directamente.
- Sin reconexion automatica en streaming. Si el daemon reinicia o la conexion se corta, el caller recibe error y decide si reintentar.
- `DockerHost` vacio conecta a `/var/run/docker.sock`. En sistemas donde el socket esta en otra ruta (Docker Desktop macOS, Podman), pasar la URL explicitamente.

## Capability growth log

v2.0.0 (2026-05-23) — reemplaza implementacion CLI (exec docker logs) por engine API HTTP con demux de frames. Anade DockerLogsOpts, DockerLogLine, modo streaming con callback y ctx cancel. Consumidor nordvpn_container_start actualizado.
