Posts for: #Java

JUnit5 のテスト拡張

1時に寝て5時に起きて2度寝して9時に起きた。前日呑んでたのであまり眠れなくて体調よくない。

JUnit5 的なロガーのテスト

お仕事でログ管理の機能開発をしている。カスタムロガーを使って出力するメッセージを加工している。設計が固まってきて機能も作り込むようになってきたので出力内容が意図した構造化ログになっているかをテストしたい。JUnit5 の機能と log4j の機能を組み合わせてカスタムロガーのテストの仕組みを作ってみた。

まずログ出力した内容を取得するオブジェクトを特定するためのアノテーションを定義する。

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggerTestWriter {
}

JUnit5 の Declarative Extension Registration の仕組みを使って、テストケース非依存な setup/teadown のメソッドを定義する。ExtensionContext から拡張するテストケースのインスタンスを取得できる。テストケースインスタンスに定義されている @LoggerTestWriter アノテーションがついたオブジェクトを lgo4j の Appender としてインジェクションするようなコードを setup/teardown (beforeEach/afterEach メソッド) で定義する。Appender のインジェクション周りは Log4j 2でログ出力をテストするサンプルソース の記事を参考にした。

public class SetupLogAppender implements BeforeEachCallback, AfterEachCallback {
    private static String APPENDER_NAME = "logger-test-appender";

    private Optional<Writer> getWriter(ExtensionContext context) throws IllegalAccessException {
        var testInstance = context.getRequiredTestInstance();
        for (var field : testInstance.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(LoggerTestWriter.class)) {
                return Optional.of((Writer) field.get(testInstance));
            }
        }
        return Optional.empty();
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        var writer = this.getWriter(context).orElseThrow(() ->
                new IllegalStateException("@LoggerTestWriter のアノテーションをもつ Writer を定義してください"));
        addAppender(writer, APPENDER_NAME);
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        var writer = this.getWriter(context).orElseThrow(IllegalStateException::new);
        removeAppender(APPENDER_NAME);
        if (writer instanceof StringWriter) {
            var stringWriter = (StringWriter) writer;
            stringWriter.getBuffer().delete(0, stringWriter.getBuffer().length());
        }
    }

実際にテストを書くテストクラスは次のようになる。

@ExtendWith(SetupLogAppender.class)
public class MyLoggerTest {
    private static final MyLogger logger = new MyLogger(MyLoggerTest.class.getName());

    @LoggerTestWriter
    StringWriter writer = new StringWriter();

    @Test
    void testDebugMap() {
        logger.debug("my-message")
        assertEquals("my-message" writer.toString());
    }

@ExtendWith で指定した SetupLogAppender クラスの beforeEach や afterEach がそれぞれのテストメソッドごとに呼ばれて、Appender のインジェクションが @LoggerTestWriter のアノテーションをもつ writer を使って行われる。この writer にはログ出力した文字列が記録されるようになる。これで、テストメソッドで logger に対して出力したメッセージを writer から取得できるので意図したメッセージが出力されていることをテストできる。カスタムロガーのテストケースごとに再利用可能な拡張をきれいに実装できた。

log4j2 の yml 設定

0時半に寝て6時半に起きた。だいぶ開発に集中してきて朝も起きれるようになってきた。

log4j2.yml を読み込む

log4j2 のログ設定を整理していて設定ファイルを読み込む順番は次のようにドキュメントに記載されている。java ライブラリのこういった手厚いルールはややうんざりするところもあるけど、是非はともかく、ファイルフォーマットの違い、ファイル名の違いで読み込む優先順位がある。歴史のあるライブラリだから要求を聞いているうちにこんな感じになったんだろうと推測する。

  1. Log4j will inspect the “log4j2.configurationFile” system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension. Note that this is not restricted to a location on the local file system and may contain a URL.
  2. If no system property is set the properties ConfigurationFactory will look for log4j2-test.properties in the classpath.
  3. If no such file is found the YAML ConfigurationFactory will look for log4j2-test.yaml or log4j2-test.yml in the classpath.
  4. If no such file is found the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
  5. If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
  6. If a test file cannot be located the properties ConfigurationFactory will look for log4j2.properties on the classpath.
  7. If a properties file cannot be located the YAML ConfigurationFactory will look for log4j2.yaml or log4j2.yml on the classpath.
  8. If a YAML file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
  9. If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
  10. If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.

log4j2 Automatic Configuration

普通 java は xml で設定を書くけど、この設定ファイルの読み込みルールをみたら yml にも対応しているならその方がよさそうとか思うやん。log4j2.xml から log4j2.yml に書き換えて試してみると、ログ設定が有効にならない。どうも log4j2.yml を読み込んでいないようにみえる。ググっていると jackson-dataformat-yaml を依存関係に追加しろといった内容をみつかるけど、どういう理屈でそういう仕様になっているのか、まったく理解できない。なによりも読み飛ばしたというログが出力されないから設定ファイルを読んでいるのかどうかすら気付けない。これは知ってないとはまるポイントの1つ。

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

maven のバージョンチェック処理の振る舞い

23時に寝て1時に起きてまた寝て6時半に起きた。変なライフサイクルになってきた。

maven のアップデートポリシー

maven が依存解決するとき、例えばバージョンの範囲を指定して最新バージョンを取得するといった設定ができる。実行していると、新しいバージョンをチェックしにいくときとそうじゃないときがあって、どういう仕組みで動いているのかよくわからなかったのでデバッグした。言うても DEBUG ログを出力させて、ログの内容をソースで grep しながら関連するところを読んだだけ。

DefaultUpdateCheckManager.isUpdateRequired の中でポリシーが最終チェック日付を確認していいる。ここから辿っていくと ArtifactRepositoryPolicy という仕組みがある。

return ( lastCheckDate == null ) || policy.checkOutOfDate( lastCheckDate );

ドキュメントでそれっぽい内容を調べると updatePolicy を設定できるようになっている。デフォルトは daily なので日次でチェックしにいくような振る舞いをする。バージョンチェックするときとしないときの何が違うのか、よくわかっていなかった振る舞いを理解できた。これはビルドキャッシュの有無に関係ないのでキャッシュがあるからバージョンチェック処理をスキップできるわけではない。もちろん、更新をチェックさせたくないのであれば never に設定してもいいのかもしれない。

updatePolicy

The frequency for downloading updates - can be “always”, “daily” (default), “interval:XXX” (in minutes) or “never” (only if it doesn’t exist locally).

https://maven.apache.org/ref/3.6.3/maven-settings/settings.html

traceparent の生成

1時半に寝て7時半に起きた。ちょっと疲れてて寝坊した。

W3C Trace Context の traceparent ヘッダーの生成

前にお仕事で dapr の分散トレーシングを検証している ことについて書いた。

dapr の分散トレーシングは W3C Trace Context に準拠していて、dapr 経由のリクエストは自動的にこの情報が付与されるが、そうじゃないリクエストもトレーシングできるようにするためには http ヘッダーの traceparent をセットしないといけない。試しにサーバー側に traceparent を生成するのはどうやるのかを調べてみた。Implementations of Trace Context にある java ライブラリを調べていて、Jaeger クライアントは OpenTelemetry に移行したと書いてあって、OpenTracing と OpenCensus は OpenTelemetry に統合されたと書いてあって、どうやら OpenTelemetry を使うのがよさそうだとわかった。

やりたいことは traceparent を生成したいだけだが、OpenTelemetry の Manual Instrumentation を読んでも直接的なやり方は書いてなくて、open-telemetry/opentelemetry-java のテストコードなどもみながら実装した。細かいところの仕様をまだ理解できていないけど、ひとまずこれで生成できたので検証はできると思う。

public class W3cContextUtil {

    private static final String TRACE_PARENT_VERSION = "00";
    private static final OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
            .setTracerProvider(SdkTracerProvider.builder().build())
            .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
            .buildAndRegisterGlobal();
    private static final Tracer tracer = openTelemetry.getTracer(
            "my-tracer", "1.0.0");

    public static String generateTraceParent() {
        Span span = tracer.spanBuilder("parent").startSpan();
        try {
            SpanContext sc = span.getSpanContext();
            return String.format("%s-%s-%s-%s",
                    TRACE_PARENT_VERSION,
                    sc.getTraceId(),
                    sc.getSpanId(),
                    sc.getTraceFlags().asHex());
        } finally {
            span.end();
        }
    }
}

大阪Python もくもく会

大阪Python もくもく会 #66 にオンライン参加した。コロナ禍前に大阪へ通勤していた頃はオフライン勉強会に何回か参加したことがある。主催者のやぎさんは一度 bizpy に参加してくれたこともある。11月からオフライン勉強会を再開したとのこと。久しぶりに参加してやぎさんと話していたら neosvr にも関心をもっているとのこと。私も少し前に oculus quest 2 を購入して触ってみた程度なのでメタバース関連で一緒に勉強会をしてもよいかもしれない。もくもく会では「アジャイル開発とスクラム 第2版」を読んでいて昨日の日記の記事がまさにその成果物。せっかくなので成果発表でこの本の紹介などをした。

log4j2 セキュリティ対応

log4j2 セキュリティ対応

23時に寝て5時に起きたが、だらだらしているうちに2度寝して6時半に起きた。

log4j2 セキュリティ対応

CVE-2021-44228 が金曜日のお昼から私のタイムラインを賑わしている。私がお手伝いしているお仕事はイントラのシステムなのでやや余裕をもって情報を眺めていた。issue のコメント をみても log4j 1.x にも影響があると書かれて、その後に実際には影響ないと書かれて、さらにその後に条件付きだけど影響はあると二転三転してた。自分で実際に試してなくて世の中の開発者の情報をみているだけ。そのため、公式の情報を信頼するといったポジションでしかない。関係者の方々には敬意を払いたい。私は spring の公式ブログで公開されている Log4J2 Vulnerability and Spring Boot を読みながら対応した。

ターコイズ

ふとしたきっかけで ターコイズ の記事を読んだ。12月の誕生石らしく、それでいまの時期に紹介されることも多いのだと推測する。別の記事でターコイズは喉によいと書かれていて、以前 喉に違和感がある ことを書いた。日常生活に困るほどではないけど、もうこの歳だから体調が良くなることはなく悪くなる一方だろうという見通しも含めて験担ぎのような感覚で喉というキーワードでつながったから購入してみた。

近所の原石屋さんに行って尋ねてみたら1-2cmぐらいのサイズ1個240円ほどで売っていたので3個買って、近所のダイソーで入れものを買って、それっぽくオフィスに置いておくことにした。うちのコーポレートカラーはグリーンとブルーなんだけど、ターコイズも ターコイズグリーンターコイズブルー の2種類の色がある。創業も12月なので誕生石としても合致する。共通点があって相性がよさそうなのでうちのコーポレートストーン (そんな言葉ない) はターコイズでいいや。

クロスデフォルト

0時に寝て6時半に起きた。5時台には起きているんだけど、起き上がるところまではなかなかいけない。

恒大集団のデフォルト

11月から利払の期日の日はチェックしていて、支払うときは2-3日前には支払いを完了したというニュースが出ていたように思う。今日の支払いは前日に支払いしたというニュースが出ないからダメなんだろうなと様子をみていた。

1つの債務がデフォルトした場合、残りの債務も一括返済しないといけないことを クロスデフォルト と呼ぶらしい。契約書にクロスデフォルト条項として書いてあるらしい。記事によると、クロスデフォルトによって約190億ドル (約2.1兆円) のオフショア債の返済を一括でしないといけないらしい。リーマンショックのようなことは起きないという見通しだけど、うちみたいな零細企業は世の中の影響を諸に受けるのでお仕事に影響がでなければいいなぁといったところ。

アプリケーションログの調査

昨日の続き。ecs-logging-java で JSON Lines でログを制御するところで spring-boot の設定で tomcat のアクセスログの設定ができる。tomcat のアクセスログを log4j のレイアウトの仕組みを使って ecs-logging-java が提供する EcsLayout に変更できないかを3時間ほど調査して、どうもできないようだというのを教えてもらった。tomcat は apache のログを出力するという目的で実装されているから log4j の柔軟なログに対応していないという理屈。

じゃあ、どうやって apache のアクセスログを JSON Lines にするかというと、PatternLayout のパターンに json のフォーマットを直書きしてしまうというやり方がある。なんかプログラミングでスマートに解決したいところだけど、その仕組みがないなら仕方ないかって感じでこれでやろうと思う。

アプリケーションログの調査

1時に寝て7時に起きた。

アプリケーションログの調査

お仕事でログの整理をやろうとしていて、そのためのライブラリとして ecs-logging-java を調べてた。

一番のモチベーションは JSON Lines でログを管理したいというところ。この手の実装や読み込んでログ分析したりとかはあちこちでやってきたので親近感はある。既存のログからの移行も含めていろいろ設計していかないといけない。私の感覚だとアプリケーションの開発初期にログの設計や出力周りを作り込むものだけど、そうじゃない文化の開発もあるんだなという印象。あとからログ設計するとか、移行や既存のコードに手を入れたり、開発初期に作り込むより手間暇かかる気がするんだけど、そんな時間もなく開発してたってことなのかなぁ。

コーディングスタイルの共通化

1時半に寝て6時に起きた

ソースコードのフォーマッター

go 言語の gofmt が成功をおさめたことからどんなプログラミング言語でもコーディングスタイルはツールで自動整形するのがよいという雰囲気が醸成され、とくに業務の開発においては統一すべしというルールを設けている組織が多いと思う。java はこの領域では後塵を拝していると言ってよいと思う。歴史的に java の言語仕様と ide は相性がよかったため、ide がコーディングスタイルを自動整形していた結果、ide ごとに互換性のない異なるコーディングスタイルが使われるようになってしまった。私がアリエルで開発していた頃、開発者はみんな eclipse しか使ってなかったので問題にならなかった。しかし、いまや私が知っている ide だけでも次のものがある。

この問題を解決するツールとして最もメジャーなのは google-java-format で、当初はこのツールを導入しようと考えていた。しかし、開発チームのテックリードから google-java-format のコーディングスタイルはひどい、一番優れているのは intellij idea だというお気持ちを表明された。導入を止めろと言われたわけではないが、チームに入ったばかりの私はテックリードのお気持ちに忖度して google-java-format の導入を断念した。代わりに intellij idea のデフォルトフォーマットをどうやって他の ide を共有するかを調べたところ、Manage code style on a directory level with EditorConfigEditorConfig の設定 (.editorconfig) を intellij idea で再利用できることがわかった。

うちの開発チームのメンバーは intellij idea と vscode しか使っていないため、現状はこれで解決できるように思えた。intellij idea にはデフォルトで EditorConfig プラグインがバンドルされていて有効になっている。リポジトリのルートディレクトリに .editorconfig があれば自動的にそれを読み込んでくれる。そこで intellij idea のデフォルト設定を .editorconfig 形式でエクスポートして、それをリポジトリのルートディレクトリに配置することでコーディングスタイルのフォーマッターを共通化できた。

Testcontainers を触ってみた

23時頃に寝て3時に起きて、そこから寝たり起きたりしながら6時に半に起きた。怖い夢をみて眠れなくなって中途半端に寝てた。

朝活: 雑談

【三宮.dev オンライン】リモート朝活もくもく会 に参加した。寝坊、、、というよりは起きてたけど、イベントを忘れていて6時45分ぐらいから参加して主催者しかいなかったのでそのまま7時半まで雑談してた。始めが悪いとだらだらしてしまう。気をつけねば。

三宮.dev の主催者や参加者の常連さんたちとはだいぶ身近に話すにようになってきた。コミュニティって人間関係だと思っていて、話したり顔をあわせたりする回数が増えるに従って身近な知人になっていって、それ自体が価値の1つだったりすると思う。いま忘年会の企画を三宮.dev でも行っているが、bizpy と合同でやっていいんじゃないかと考えている。

Testcontainers

DB を使ったユニットテストのために Testcontainers Postgres Module を使ってみた。docker hub からイメージを取得して JUnit のテストプロセスの中からアクセスできるようにするためのライブラリになる。コンテナの扱いをテストコードから管理したいときなどに便利。ちょっと調べて簡単に設定できたのでまた時間のあるときに会社のブログに記事を書こうと思う。

読書とイベント参加

読書とイベント参加

0時頃に寝て8時ぐらいに起きる。やや発熱して疲れてたせいか、久しぶりに早く寝付けた。一日を通して体温は平均36.7℃なのでもう副反応は過ぎたみたい。体調もまったく悪くない。

Joel on Software

過去に働いていた会社での課題管理のやり方や開発方法論について、当時の上司と雑談したところ Joel Spolsky に由来するということを聞いた。そこで今更ながらに More Joel on Software を読むことにした。2000年代に書かれた記事の内容なのでいまとなっては古典に分類される本かもしれない。だいたい半分ぐらい読んだ。技術の詳細に言及した内容は古くなっていてあまり有用ではないものも多いけど、マネジメントや優秀なプログラマーの特性などはいまでも通用する内容に思えた。あとで私が関心をもった内容をブログでまとめることにする。

第10章コンピュータサイエンスの学生へのアドバイスで「卒業するまでにミクロ経済学を学ぶこと」という節がある。著者がミクロ経済学を推奨する理由を引用するとこれら。

ミクロ経済学はビジネスで重要な理論すべての基礎となっている。需要と供給とか、競争優位とか、NPV とか割り引きとか限界効能について知らなければ、ビジネスの仕組みが全然理解できないからだ。

マクロ経済学は、当たっているよりもはずれていることの方が多い。スキップしてよい。それ以降はただ悪くなっていく一方。

ビジネスの基礎を理解しているプログラマは、理解していないプログラマよりもビジネスにおいてずっと価値が高いからだ。

学んだことがなかったので簡単そうな ミクロ経済学入門の入門 を購入した。

読んでて気づきを得てふとツィートした。

Java 17 リリースイベント

【オンライン】 JJUGナイトセミナー「Java 17 リリース記念イベント with Foojay」9/29(水) 開催 に参加した。Java の LTS はいま過渡期でややこしいことになって、8, 11, 17 になる。リリースされたばかりの Java 17 は LTS で重要なバージョンになる。Oracle Java SE Supportロードマップ から Premier Support 期限が次になる。

  • 8: 2022年3月
  • 11: 2023年9月
  • 17: 2026年9月

いま 11 を使っている組織はいいが、8 を使っている組織もまだまだ多いと推測する。8 と 11 の Premier Support 期限が近いことから 8 を使っている組織は 17 に一気にバージョンアップすることが想定される。どこかのタイミングで Java 17 を前提した開発に切り替わっていくだろうと思われる。

最初の発表は Pattern Matching & Sealed Classes に特化した内容。これまでは instanceof と共に使う機能だった。switch 構文とパターンマッチングを組み合わせると、コードが簡潔になって Cognitive complexity を下げるという。発表者が Type Guard という呼び方をしていた。Type Guard をググると TypeScript の記事がヒットする。JEP 406: Pattern Matching for switch (Preview) ではこれを guarded pattern と呼んでいる。まだあまり一般的な用語ではないのかもしれない。あとは Sealed クラスと組み合わせた switch 構文のコード例では、すべてのパターンが網羅されていることをコンパイラが検出して default 句が不要になるコード例も紹介されててよさそうにみえた。但し、switch 構文のパターンマッチングは preview なので実際には 17 ではまだ使われないのかもしれない。今後もさらに switch 構文とパターンマッチングの機能拡張が行われる展望らしい。

2番目の発表は Java 17 の全体的な話し。fix した issues のツリーマップで contributor の分布を紹介していた。oracle, redhat, independent の順番に多い。oracle が過半数以上。日本だと ntt data が一番貢献してた。spring フレームワークの次期バージョンは Java 17 がベースラインになる。java のアップグレードを促す要因の1つにはなるはず。lts なのになぜ preview や incubator があるのか?openjdk 開発側は6ヶ月というリリースサイクルを守っている。lts にするか否かは開発者が決めているらしい。graalvm のリリースサイクルは java とは異なる。こちらは年3回のリリースなので次のリリースで出てくるはず?いくつか jep の内容を紹介してた。jep の概要は Java17の新機能をざっくり紹介 にまとまっている。さくらばさんがパッケージの api レベルでの変更を JEPでは語れないJava 17 にまとめている。ざっと目を通して興味があるものがあればみとくぐらい。8 から 17 への移行の記事やドキュメントなども紹介されてた。移行について基本は Oracle JDK Migration Guide を読めとのこと。8 から 17 の移行せずにその次の 23 を待つと作り直しになってしまいますよと 17 への移行を推奨してた。