runner logs

Agrupa subcomandos para acesso ao historico de deploys, streaming de logs recentes e estatisticas de uma app.

Use quando: quer inspecionar o que aconteceu em deploys anteriores, acompanhar um deploy em andamento ou obter metricas de sucesso/falha de uma app.

Sintaxe

runner logs <subcomando> <app> [flags]

Subcomandos

Subcomando Descricao
tail Exibe as ultimas N linhas do log de deploy
history Lista o historico de deploys com status e versao
registros Tentativas de registro (add/adopt), inclusive as recusadas (v2.188.0+)
stats Exibe estatisticas agregadas de deploy (total, sucessos, falhas)
scrub Mascara tokens já gravados nos logs (v2.158.0+)
rotation Retenção dos logs do runner no logrotate (v2.176.0+)

Flags comuns

Flag Default Descricao
-o, --output <FORMAT> human Formato de saida: human, json, yaml
--json — Atalho para --output json
-h, --help — Exibe ajuda

runner logs tail

Exibe as ultimas linhas do log do deploy mais recente.

runner logs tail <app> [flags]
Flag Default Descricao
-l, --lines <N> 50 Numero de linhas a exibir
--json — Saida em JSON

Exemplo

$ runner logs tail meu-app --lines 20
[2026-05-27T14:30:01Z] [PREPARE]   Preparando deploy v1.3.0...
[2026-05-27T14:30:02Z] [ARTIFACT]  Copiando artefatos...
[2026-05-27T14:30:05Z] [ENV]       Gerando ambiente...
[2026-05-27T14:30:06Z] [CONTAINER] Subindo container de teste...
[2026-05-27T14:30:18Z] [HEALTH]    healthy (200 OK, 38ms)
[2026-05-27T14:30:18Z] [PROMOTE]   Promovendo para producao...
[2026-05-27T14:30:19Z] Deploy concluido: v1.3.0

runner logs history

Lista o historico de deploys com versao, resultado e timestamp.

runner logs history <app> [flags]
Flag Default Descricao
-l, --limit <N> 10 Numero de entradas a exibir
--json — Saida em JSON

v2.175.1+: um rollback que falhou entra no historico como falha, com o erro prefixado por rollback: . O evento deploy_rollback correspondente aparece em `runner audit`.

Exemplo

$ runner logs history meu-app -l 5
#   versao   resultado   data
1   v1.3.0   success     2026-05-27T14:30:19Z
2   v1.2.1   success     2026-05-26T09:11:04Z
3   v1.2.0   failure     2026-05-25T18:42:30Z
4   v1.1.0   success     2026-05-24T11:00:15Z
5   v1.0.0   success     2026-05-20T08:30:00Z

App que não existe: um registro recusado não tem histórico de deploy. O history de uma app inexistente aponta para `runner logs registros`.


runner logs registros

v2.188.0+. Lista as tentativas de registro (runner add, runner adopt, runner add --from-compose), registradas ou recusadas, a mais nova primeiro. Uma recusa no registro (no_git_tags, ckey_too_short, nome_base_de_outra_app, manifesto inválido…) não aparece no logs history, porque a app ainda não existe. Ela aparece aqui.

runner logs registros [--repo <repo>] [--app <nome>] [--resultado registrado|recusado] [-l N] [-o json]
Flag Default Descricao
--repo <repo> — Só deste repo. Aceita org/app ou a URL, normalizada como no add (sem diferença de caixa)
--app <nome> — Só desta app: o nome pretendido na recusa, o registrado no sucesso
--resultado — registrado ou recusado
-l, --limit <N> (--tail) 20 Quantas tentativas; 0 = todas
--json — Saida em JSON (prefira -o json)

Exemplo

$ runner logs registros --repo org/site
QUANDO                RESULTADO   REPO                      APP                             CÓDIGO / MENSAGEM
2026-11-04 14:02:11   registrado  org/site                  site
2026-11-04 13:58:40   recusado    org/site                  site                            no_git_tags: o repositório não tem nenhuma tag alcançável, mas o `.deploy.yml` declara version.source: git-tag.
2 tentativa(s), 1 recusada(s)

JSON

Envelope v2 com command: "logs.registros". Capability: logs.registros.v1 (runner --capabilities).

{"data": {
  "arquivo": "/opt/runner/logs/register-attempts.jsonl",
  "total": 1, "recusados": 1, "ilegiveis": 0,
  "items": [{
    "timestamp": "2026-11-04T13:58:40Z",
    "comando": "add",
    "repo": "org/site", "branch": "dist", "manifest_path": null,
    "instancia": "production", "imagem": null, "app": "site",
    "resultado": "recusado",
    "codigo": "no_git_tags",
    "mensagem": "o repositório não tem nenhuma tag alcançável, mas o `.deploy.yml` declara version.source: git-tag.",
    "env_chaves": ["BASE_DOMAIN"],
    "deploy": null,
    "versao_runner": "2.188.0",
    "origem": "api", "ator": "leo", "usuario": "root"
  }]
}}
Campo Significado
comando add, add_from_compose, add_sem_repo, add_pr ou adopt
app Nome pretendido (slug ou project: do manifesto) na recusa; o registrado no sucesso. null quando a recusa veio antes de ler o manifesto
codigo Código estável do erro, o mesmo do -o json do add. runner_erro quando o erro não tem código. null no sucesso
mensagem A primeira linha do erro, sem o código, com no máximo 300 caracteres
env_chaves Só as chaves das --env/--env-file. O valor nunca é gravado
deploy Com add --deploy: o desfecho do primeiro deploy, {"executado", "ok", "codigo", "mensagem"}. null sem --deploy ou quando o registro foi recusado antes dele
origem cli, ou o --trigger do add (o CCS passa api). ator vem do --actor
ilegiveis Linhas do arquivo que não parsearam e ficaram de fora
env_invalidas Entradas de --env sem CHAVE= válida. Só aparece quando há alguma

Registro que deu certo, deploy que falhou

Com add --deploy, a app fica registrada mesmo quando o primeiro deploy falha: o add sai com 0 e resultado continua registrado. O desfecho vai em deploy, com o mesmo código estável do erro e o mesmo filtro de segredo da recusa:

{"resultado": "registrado", "app": "site",
 "deploy": {"executado": true, "ok": false, "codigo": "no_git_tags",
            "mensagem": "o repositório não tem nenhuma tag alcançável, mas o `.deploy.yml` declara version.source: git-tag."}}

É o caso do version.source: git-tag num repo sem tag. O no_git_tags nasce na leitura da versão, antes de o deploy entrar no logs history. Por isso esta linha é o único lugar onde ele fica. Depois de corrigir (crie a tag ou troque para git-commit), rode runner deploy <app>: não precisa registrar de novo.

  • ok: true com mensagem: o deploy subiu com pendência pós-deploy.
  • executado: false: pediu --deploy, registrou, e o deploy não rodou.

add --from-compose grava uma linha por serviço. Uma recusa antes da descoberta dos serviços grava uma linha só.

O arquivo

/opt/runner/logs/register-attempts.jsonl, uma linha JSON por tentativa, em 0640, como o insecure-events.jsonl:

  • Nunca grava ckey, token nem valor de env/secret. Das --env, só as chaves válidas (CHAVE=…). A entrada sem = ou com chave inválida, que o add ignora, só é contada (env_invalidas).

  • Os campos repo, branch, manifest_path, instancia e imagem ficam como o operador passou, sem credencial colada (https://user:token@…).

  • Na mensagem, além da máscara de tokens, viram ***:

    • o token e a ckey literal (o seletor do --ckey, como v2 ou sym, não);
    • valores de --env com 8+ caracteres;
    • valores de chaves que o manifesto declara em secrets:.

    Valor curto e comum (loja, dist, production) fica.

  • Gravar é best-effort: se falhar, o add não muda de resultado nem de exit code.

  • Rotação: entra no bloco /opt/runner/logs/*.jsonl do `logs rotation`. A leitura inclui as rotações (.1, .N.gz). O `logs scrub` também o varre.


runner logs stats

Exibe estatisticas agregadas de deploy: total de execucoes, taxa de sucesso e falhas.

runner logs stats <app> [flags]
Flag Default Descricao
--json — Saida em JSON

Exemplo

$ runner logs stats meu-app
app:        meu-app
deploys:    45
success:    43  (95.6%)
failure:    2   (4.4%)
last:       2026-05-27T14:30:19Z

runner logs scrub

v2.158.0+. Aplica a máscara de tokens aos logs que já existem e deixa os arquivos em 0640:

  • deploy-events.jsonl, fetch-audit.json, insecure-events.jsonl e os demais .json, .jsonl e .log de /opt/runner/logs/;
  • logs/deploy-audit.json e logs/deploy.log de cada app;
  • /var/log/runner-cron.log.
runner logs scrub --dry-run     # só conta, não muda nada
runner logs scrub               # mascara e corrige permissões
runner logs scrub -o json       # envelope logs.scrub, com a contagem por arquivo

A máscara troca o corpo de github_pat_, ghp_, gho_, ghu_, ghs_, ghr_, glpat-, xox[bpare]- e chaves AWS (AKIA…/ASIA…) por ***. A saída mostra só quantos foram trocados, nunca o valor.

Desde a v2.158.0, o runner aplica essa máscara a tudo que grava nesses arquivos e os cria em 0640. O scrub serve para o que foi gravado antes de atualizar.

Mascarar o log não revoga o token. Revogue no GitHub os tokens que apareceram.


runner logs rotation

v2.176.0+. Mostra e configura a retenção dos logs do próprio runner no /etc/logrotate.d/runner. A tela do CCS usa este comando em Detalhes do server › Subsistemas.

runner logs rotation status [-o json]
runner logs rotation set [--audit-days N] [--cron-days N] [-o json]

O arquivo tem dois blocos, achados pelos caminhos, não pela posição:

Bloco Caminhos Flag
audit /opt/runner/logs/*.json e *.jsonl --audit-days
cron /var/log/runner-cron.log (ou runner-fetch.log) --cron-days

Os dois valores vão de 1 a 365 e são independentes. A frequência é daily, então N dias equivale a rotate N. Para o gráfico de 30 dias do CCS, use --audit-days 35.

status

Só lê: não grava nada, nem o backup. Roda sem root, desde que o arquivo seja legível.

{"rotacao": {
  "suportado": true,
  "gerido_pelo_runner": false,
  "arquivo": "/etc/logrotate.d/runner",
  "arquivo_existe": true,
  "backup_previsto": "/opt/runner/backups/logrotate/runner-2026-10-29",
  "audit": {"dias": 14, "caminhos": ["/opt/runner/logs/*.json", "/opt/runner/logs/*.jsonl"], "historico_dias": 15},
  "cron":  {"dias": 14, "caminhos": ["/var/log/runner-cron.log"]}
}}
Campo Significado
gerido_pelo_runner O arquivo tem o cabeçalho # gerido pelo runner (runner logs rotation)
arquivo_existe false quando não há o arquivo. Aí dias é null e caminhos é []
backup_previsto Só em arquivo manual: onde a primeira gravação vai guardar o original
audit.historico_dias Dias que existem de fato: o deploy-events.jsonl ativo mais cada .N/.N.gz
avisos Só quando há algo a avisar (ver abaixo)

Sem logrotate no host: {"rotacao": {"suportado": false}}.

set

Exige root. Só este comando escreve no arquivo. O fetch, o self-update e o serviço nunca tocam nele.

sudo runner logs rotation set --audit-days 35 -o json
  • Só a linha rotate do bloco pedido muda. su, maxsize, compress, copytruncate, os caminhos, os comentários e os blocos que você acrescentou ficam iguais.
  • Se faltar um dos blocos, o set acrescenta esse bloco com as diretivas do outro. O su dele é derivado do diretório do log: com escrita de grupo ou grupo diferente de root, su <dono> <grupo> (su root syslog em /var/log, su root devs em /opt/runner/logs). É o que o logrotate exige para não recusar o diretório por "insecure permissions".
  • Um bloco de uma linha (caminho { daily }) vira um bloco de várias linhas, com o rotate dentro.
  • O runner não adivinha. Recusa e mostra em avisos:
    • um bloco que junta os logs do audit e o do cron (separe em dois blocos);
    • um glob que cobre os logs do runner de outro jeito, como /opt/runner/logs/*.
  • Primeira escrita num arquivo manual: o original é copiado (como cp -p) para /opt/runner/backups/logrotate/runner-AAAA-MM-DD, num diretório 700. Se já houver um backup com a mesma data, o novo leva -1, -2, e assim por diante: nunca sobrescreve.
  • Validação: antes de trocar, o runner roda logrotate -d com o /etc/logrotate.conf do host (os globais valem) apontado para uma cópia de /etc/logrotate.d com o arquivo novo. Reprova o erro que cita o runner e o erro que o arquivo novo introduz. Erro antigo de outro arquivo do diretório não reprova. Reprovado, nada é trocado.
  • A troca é atômica (temporário + rename), com 644 e dono root.
  • Um set por vez: o segundo espera o primeiro (flock em /opt/runner/backups/logrotate/.lock) e lê o que ele gravou.

A saída é o mesmo bloco rotacao do status, relido depois da troca (o valor que valeu), mais backup_criado: o caminho do backup feito agora, ou null.

Arquivo que repete os caminhos do runner

O logrotate lê todo arquivo de /etc/logrotate.d, menos os que terminam em extensões como .bak, .old, .orig, .disabled e ~. Uma cópia como runner.bak-2026-10-29 (o nome termina em -2026-10-29, não em .bak) repete os caminhos do runner. O logrotate acusa duplicate log entry e falha em toda execução diária.

Nesse caso, o status traz:

"avisos": ["arquivo `/etc/logrotate.d/runner.bak-2026-10-29` repete os caminhos do runner e faz o logrotate falhar; mova-o para fora de /etc/logrotate.d"]

e o set falha com logrotate_invalido até o arquivo sair dali.

Códigos de erro

Com -o json, o erro sai como {"error": "<código>", "message": "…"} e exit ≠ 0.

Código Quando
rotacao_dias_invalido Valor fora de 1–365, não numérico, ou nenhuma flag
permissao_negada set sem root
logrotate_ausente set num host sem logrotate
logrotate_invalido O logrotate -d reprovou o arquivo novo; nada foi trocado
rotacao_arquivo_ilegivel Não deu para ler o /etc/logrotate.d/runner
rotacao_gravacao_falhou Falha ao gravar ou trocar o arquivo; o atual fica intacto
rotacao_backup_falhou Falha ao gravar o backup; nada foi trocado
rotacao_bloco_combinado Um bloco tem os logs do audit e o do cron juntos: separe em dois
rotacao_bloco_nao_reconhecido Um bloco cobre os logs do runner com outro glob (ex.: /opt/runner/logs/*)

Erros comuns

  • app not found → verifique o nome com runner list; o argumento aceita nome do projeto ou caminho absoluto.
  • no deploy history → a app ainda nao teve nenhum deploy; execute runner deploy <app> primeiro.
By Borlot.com.br on 27/05/2026