Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

SLF4Jの使い方をfindbugsで自動検証する

今日、拙作findbugs-slf4jGitHub Pageを作りました。このブログではこのプロジェクトについて日本語で紹介したいと思います。

概要

  • 拙作PMDプラグインをもとに、2012年8月から実装を開始したfindbugsプラグイン
  • SLF4J利用時のよくあるトラブルを未然に防ぎ、コードの品質を担保する
  • 全自動なので、開発者各々の個性に影響されずに品質の底上を実現できる

既存の課題:SLF4Jの直感的ではないインタフェース

SLF4J(Simple Logging Facade for Java)はJava向けに開発された、ログ用のFacadeです。開発者はSLF4Jが提供するインタフェースを利用することで、「高パフォーマンスなログ処理」と「利用者に好きなログ基盤を選ばせる自由」を提供することができます。例えばLog4j 2のような新しいログ基盤でも、プログラムがSLF4Jを使っていれば、プログラムには手を加えること無く利用が可能です。

ただしSLF4Jのインタフェースはやや独特で、学習と慣れが必要です。例えば下記に挙げる例ではexample 1のほうが直感的ですが、SLF4Jではexample 2の方を推奨しています。この方法はパフォーマンス上有利なのですが、コードに長年手が加えられていくとプレースホルダとパラメータ数の不一致などが容易に発生するため、ログに残すべき値が残せていなかったなどのトラブルに繋がります。

// example 1: not good, because JVM will concat strings even if debug log is needless
logger.debug("User id is: " + userId);

// example 2: better, because it will not generate needless string if debug log is needless
logger.debug("User id is: {}", userId);

また例外のstacktraceを出力するためのメソッドinfo(String format, Object... arguments)だというのもわかりにくい点です。以下の例のように可変長配列の最後の要素がThrowableであればstacktraceを出力してくれるのですが、これはドキュメントを読むか人に聞くかしなければわからないでしょう。

// example 3: not good, because stacktrace and log messages are separated into two log records.
// And user cannot configure how to log stacktrace.
logger.warn("Exception ({}) occurred when userId is {}", e.getMessage(), userId);
e.printStacktrace();

// example 4: better, it's configurable and not separated into multiple log records.
logger.warn("Exception occurred when userId is {}", userId, e);

SLF4Jは現在のJava開発でデファクトスタンダードと言えるログ用ライブラリですが、このような難しさがあるため利用時は経験ある開発者からのサポートやレビューが欠かせません。ただし、アプリケーションコードのどこにでも書かれうるログ処理のすべてを人力でレビューし品質を担保するのは至難の業です。

提案する解決:静的解析による自動的かつ横断的な検証

私が開発したfindbugsプラグインの発想はごくシンプルなもので、上記で挙げた問題を機械による検証によって解決するというものです。 個人の経験と尽力によって回避するのではなく、検証を機械化し自動化することでコストの削減とスケーラビリティの向上、抜け漏れ防止を実現します。

技術的な特徴

主な技術としては、findbugsが提供するオペランドスタック解析を主に使用しています。これによりプレースホルダとパラメータの数の不一致を検出したり、e.getMessage()をパラメータとして使っているコードを検出したり、LoggerFactory.getLogger(Class)メソッドの引数を検証したりしています。また.classファイルに書かれた定数文字列を解析することで、簡易ではあるもののログフォーマットの人間にとっての可読性を検証するといったことも取り入れています。

findbugsプロジェクトは息が長いということもあり、コードが非常に難解です。またテストも困難です。 このプラグイン開発では統合テストを重視テストパターンを増やすことで、TDDを可能にしつつエンバグを減らすことに努めています。またバグ修正時はまず再現テストを書く、ということを徹底しています。

またTravis CIが提供する機能により、Java 7&8SLF4J 1.6&1.7の2軸でビルドマトリックスを組んでいます。これも多くの環境をサポートする上で非常に役立っています。

どのくらい使われているか

OSSプロジェクトでの利用実績はほとんど無いと認識していますが、クローズドなプロジェクトではそこそこ使われているようです。2012年11月にいただいたIssueをはじめとして、今でもバグ報告・新規機能要望などをいただきます。「このコードを解析するとエラーになる」と、.javaファイルや.classファイルをメールでいただくこともあります。

特にこの5月ごろから月あたりのDL数が平均40から1,700(ユニークIPアドレスベースでの計数)へと大幅に上がったのですが、どこかの大きなプロジェクトで使っていただいているのでしょうか。

f:id:eller:20150920134539p:plain

以上です。 もし皆さんのプロジェクトでSLF4Jを使っていたら、ぜひ当findbugsプラグインの利用を検討してみてください。Mavenで使う方法はこちらに書いてあります

関連する記事