Dockerでコンテナを立ち上げて、いざ動作確認をば・・・あれ、何か動かない。そんなとき、とりあえず「ログを見る」ためにdocker logsを叩くのは、エンジニアの条件反射みたいなもの。
でも、出力されるログが多すぎる!「grepでエラーログだけ絞りゃええやろ」と思ってコマンドを打ち込みます。
docker logs my_container | grep errorところがどっこい。
grepをつけたはずなのに、全量出力されてしまう。あるいは、エラーが出ているはずなのに何も表示されない。パイプ(|)が仕事をしていない?
これ、実はDockerあるあるの一つなんです。今日はこの現象の原因と、解決する方法について書きたいと思います。
結論から言うと、多くの場合、その原因は「標準エラー出力(STDERR)」にあります。
docker logsの仕組み:docker logsコマンドは、コンテナが「標準出力(STDOUT)」に出力した内容と、「標準エラー出力(STDERR)」に出力した内容の両方を表示してくれます。|)の落とし穴:普段何気なく使っているパイプ(|)は、デフォルトではSTDOUT(標準出力)だけを次のコマンドに渡す、という性質を持っています。つまり。
|)はSTDOUT(標準出力)しかgrepに渡してくれない。grep君は「STDOUTからはerrorなんて来なかったよ?」と判断し、何も表示しない(あるいは、STDERRの内容はそのままスルーされてターミナルに表示され続ける)・・・という事態が発生するわけです。grepがサボっていたわけではなく、そもそもフィルタリングすべきデータが渡っていなかったんですね。
原因がわかれば対処は簡単です。要するに、STDERRの内容もgrepに渡してあげれば良いのです。
これが一番メジャーな解決策です。シェルの機能を使って、「標準エラー出力(STDERR)も標準出力(STDOUT)に合流させてから」パイプに流します。
bashやzshを使っている場合 (簡単な記法):
docker logs my_container |& grep error|& という記法が、「STDOUTとSTDERRの両方を次のコマンドに渡す」という意味になります。なので、この記法が使えるシェルであれば、この方法が一番早いです。
sh(標準的なシェル)でも動く記法:
docker logs my_container 2>&1 | grep error2>&1 というのは、前述の簡単な書き方ができないシェルでも有効な記法です。これは「2番(STDERR)を 1番(STDOUT)と同じ場所(&)に向けてね」という意味になります。これをパイプの手前に書くことで、STDOUTとSTDERRが合流した状態でgrepに渡されます。
どちらを使っても、今度こそgrepが意図した通りに「error」を含む行だけをフィルタリングしてくれるはずです。
docker logsのオプションを活用するそもそもログが多すぎるという問題に対して、grepを使うのではなくdocker logs自体のオプションで絞り込むという方法です。
直近のログだけ見る:
docker logs --tail 100 my_container(直近100行を表示。grepと併用ももちろんOK)
時間で絞り込む:
docker logs --since '10m' my_container(過去10分間のログを表示)
docker logs -f my_container 2>&1 | grep error(-fオプションでログをリアルタイム表示しつつ、2>&1とgrepでフィルタリング)
docker logs | grepが効かない!と思ったら、まずは「STDERR(標準エラー出力)」を疑いましょう。
# これで解決!
docker logs my_container 2>&1 | grep error(または |& を使う)
シェルのリダイレクトは少し取っつきにくいかもしれませんが、一度覚えてしまえばDockerに限らず、さまざまな場面で役立つ知識です。