Considere este programa simples ( abort.py
) que escreve algum texto em stdout
e stderr
e então trava ( abort()
).
import os
import sys
print("end stdout")
print("end stderr", file=sys.stderr)
os.abort()
Quando executo no terminal, ambas as saídas para stdout
e stderr
são produzidas corretamente.
$ python3 abort.py
end stdout
end stderr
Aborted (core dumped)
Entretanto, se eu redirecionasse stdout
e/ou stderr
usasse outro programa (talvez para fins de registro), ele não funcionaria mais.
$ python3 abort.py | cat
end stderr
$ python3 abort.py |& cat
$ python3 abort.py | tee -a logs
end stderr
$ python3 abort.py |& tee -a logs
# file `logs` is unchanged
Na verdade, se o programa ( abort.py
) produz muitos textos para stdout
e stderr
, apenas a última seção deles é perdida da perspectiva do programa receptor na outra ponta do pipe. Eu também tentei executar o programa em um script bash e executar esse script, mas o resultado é o mesmo.
Por que isso está acontecendo e como devo corrigir?
Fundo
O exemplo acima é obviamente artificial. O cenário do mundo real é que eu estava depurando um programa grande que ocasionalmente travava (segfault ou abortava) após rodar por dias. Ele produz muitos logs para ambos stdout
e stderr
, mas a informação mais importante sobre sua falha (como rastreamento de pilha) é impressa no final, logo antes da falha acontecer. Eu tenho um pipeline de log configurado em um script bash (envolve alguns tee
e gzip
por exemplo), mas descobri que a última seção do log salvo está sempre faltando, o que é frustrante.
Este é um "problema" de buffer. Dizer ao python para não fazer buffer resulta no resultado desejado.
Quanto ao que fazer a respeito, você deve liberar a saída toda vez que algo for escrito com o qual você se importa o suficiente para ter o desempenho afetado, por exemplo
Note que as mensagens
Aborted
ouAborted (core dumped)
vêm do shell. Se você estiver executando o bash ou qualquer outro shell POSIX, o status de saída de um pipeline é o do fim do pipeline. Como ocat
não está abortando ou despejando o núcleo, você não vê uma mensagem. Para o bash, useset -o pipefail
para ver a mensagem abortada: