新くもなんともない話です。当たり前と思う人には昔から当たり前(昔度合いはおまけ3を参照)。でも知らないことは悪ではないし、そうなんだーってなればいいだけの話。
一連のログに同じ値を付与したい時、 MDC
(SLF4J) や ThreadContext
(Log4j2) を使うかと思います。
中身は java.lang.ThreadLocal
とかだったりするので、サーバーアプリケーションやマルチスレッドでは取り除いてあげないと事故の元です。
// 微妙なやり方 void method() { MDC.put("username", "hoge"); // ... なんか処理 MDC.remove("username"); }
こんなことしちゃうと例外が発生した場合に remove
されなくて事故るので、伝統的な方法では finally
でやるかと思います。
// 伝統的なやり方 void method() { try { MDC.put("username", "hoge"); // ... なんか処理 } finally { MDC.remove("username"); } }
ThreadContext
もここまではほぼ同じ。
あと細かいけれど、上記の "username"
が二箇所に出るとか、複数の項目を扱うときとか、あんま嬉しくないです。
本題: 後片付けはJavaに任せよう
try-with-resources
を使えます。
SLF4Jだとこう。
void methodMDC() { try (var ignore = MDC.putCloseable("username", "hoge")) { // ... なんか処理 } }
Log4j2だと ThreadContext
のかわりにCloseableThreadContext
です。
void methodThreadContext() { try (var ignore = CloseableThreadContext.put("username", "hoge")) { // ... なんか処理 } }
try-with-resources
で close
することで remove
漏れもないし、キーを複数書かずによくなり、取り違えも無くなります。 put
と remove
を1メソッドに書けない(事前処理と事後処理を別のところで書かなきゃいけないなど)とかでなければ、常にこれらを使うことをお勧めします。
SFL4J は補完で putCloseable
メソッドが出てくるので気づきやすいのですが、 Log4j2は org.apache.logging.log4j.CloseableThreadContext
と別クラスになるため認知率はガクッと下がる印象です。Log4j2やSFL4J以外を使っていても、同様のものがあると思います。
var
を使っているのは素直にやると MDC.MDCCloseable
や CloseableThreadContext.Instance
と長くなるにも関わらず、この変数の型に興味がないためです。こういうとこは var
が輝く。
あと変数名を ignore
とかにするのは、未使用変数でもこれは警告しないでくれってIDEAへの主張です。
try-with-resources
を使用する場合にはよく使うと思います。ignore
でもignored
でもいいし、複数あるとignore1
とか連番になったりでダサくなります。個人的には _
とかにしたいんだけども。
IDEの警告はゼロにしとくのがいいです。
脱線: 濫用?
MDC
や ThreadContext
はログライブラリのものなんですが、使えるからと言う理由でデータ受け渡しに便利に使用されていたりします。
そんな使い方をするんじゃねぇ、必要であればその情報受け渡しをきっちり設計しようよ……と思ったりもするんですが、
引数で引き回す以外の方法となると、DBなどの永続技術を使うか ThreadLocal
などを使用する必要が出てきます。
単に動くだけならどんな方法でも簡単なんですが、適切に設計するのは難しく、下手なことするくらいならこっち使っておく方がマシか……と思ったりも。
ThreadLocal
を使うと非同期処理を使う時とかに適切に受け渡す必要があったり(たとえば SpringBootでAsyncを使う時に知っておきたいExecutorのこと (2023-08-24) で書いているように)、複数このようなものが出てくると漏れたりしがちで、あと引きまわしたいスコープも似たり寄ったりになるので、まぁもういいかって思ったり、いややはりちゃんと、となったり。
Observation
の Context
もだし、今回の例で username
と書いたけど認証コンテキストもそんな感じ。
たぶん各々で適切に取り扱う何かを作って、必要なとこでアダプタを作るーとかすれば綺麗なんだろけど、コストかけるとこでもないかなぁ……とかとか。なやましい。 動けばいいねん動けば
おまけ
これらがないときは AutoCloseableでなくてもtry-with-resourcesがしたい (2013-01-05) の方法で足掻いたりしかけて、わかりづらくなるかーってなって、普通に finally
でやったりしてました。
// 今は不要 void method() throws Exception { MDC.put("username", "hoge"); try (AutoCloseable ignore = () -> MDC.remove("username")) { // ... なんか処理 } }
putCloseable
あるのにこれやってたら色々考え直した方がいいです。
おまけ2
はてなブログにいつのまにかAIでのタイトル生成機能がついてたので試してみた。
どれもコレジャナイ感。 だけど、AIがつけるタイトルと自分がつけるタイトルが近い文章のほうがいい文章なのかもしれないなぁ、とか思ったりもした。
おまけ3
冒頭で「昔から」って書いたので、どれくらいかなーと調べてみた。
へー。日付は CentralRepository のタイムスタンプね。