Javaのスレッドで発生したキャッチされてない例外をログに出力する

概要

  • Thread#setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler) を呼び出すことでアプリケーション全体のログ設定を変更することができる。
  • 上の設定をThreadGroupで上書きすることができる。ただし、ThreadGroup自体がUncaughtExceptionHandlerを継承しているため、ThreadGroupのサブクラスを自前で作成する必要がある。
  • 上の設定をThread#setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)を呼び出すことで上書きすることができる。

説明

Javaのスレッドの実行では、通常、エラーハンドリングは自前で行うのですが、キャッチされないRuntimeExceptionの処理を忘れてしまったりすることもあるため、共通のログ処理等を入れておくと便利です。

Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)を呼び出してエラー処理を書き換える

Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) を呼び出すことでアプリケーション全体のログ設定を変更することができます。
ただ、何かしらのフレームワークを使用している場合は、すでに処理が含まれている可能性があるため、使用前には必ず何も設定されていないことを確認してください。

Thread.UncaughtExceptionHandler dueh = Thread.getDefaultUncaughtExceptionHandler();
if (dueh == null) {
    Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
        System.out.println("これはデフォルトのUncaughtExceptionHandlerで出力されています。");
        System.out.println("ThreadGroup=" + t.getThreadGroup().getName() + "," + "Thread=" + t.getName());
        e.printStackTrace();//適切なロガーで出力してください。
    });
}
ThreadGroupでエラー処理を書き換える

Javaにはいくつかのスレッドをまとめるスレッドグループというものが存在します。エラーハンドリングを複数のスレッドにまとめて設定することができます。
ThreadGroupクラスになりますが、このクラス自体がUncaughtExceptionHandlerインターフェースを実装しているため、ログ設定をいじるためにはThreadGroupクラスを継承したクラスを作成する必要があります。また、Javaのスレッドがどのスレッドグループに所属するかどうかの設定は通常、スレッドの作成時のみにしかできないため、ExecutorServiceの作成時にThreadFactoryを渡して所属するスレッドグループを指定してあげる必要があります。

import java.util.concurrent.ThreadFactory;

class LoggingByThreadGroupThreadFactory implements ThreadFactory {

    private static final ThreadGroup loggingThreadGroup = new LoggingThreadGroup("loggingThreadGroup");

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(loggingThreadGroup, r); //スレッドを作成するときにスレッドグループを渡すことでそのスレッドグループに所属するスレッドになる。
        // t.setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());ここで設定するとThreadGroupは使用されない
        return t;
    }

    static class LoggingThreadGroup extends ThreadGroup {

        public LoggingThreadGroup(String name) {
            super(name);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("これはスレッドグループで出力されています。");
            System.out.println("ThreadGroup=" + t.getThreadGroup().getName() + "," + "Thread=" + t.getName());
            e.printStackTrace();//適切なロガーで出力してください。
        }
    }
}
ExecutorService service = Executors.newFixedThreadPool(1, new LoggingByThreadGroupThreadFactory());
Thread毎にエラー処理を書き換える

Thread#setUncaughtExceptionHandler(UncaughtExceptionHandler)を呼び出すことでスレッド毎のエラー処理を書き換えることができます。
ThreadGroupと同様にExecutorServiceの作成時にThreadFactoryを渡すことで、スレッド内で実行されるロジックからログ出力の設定を隠ぺいすることができます。

import java.util.concurrent.ThreadFactory;

public class LoggingThreadFactory implements ThreadFactory {

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());
        return t;
    }

    private class UncaughtExceptionHandlerImpl implements Thread.UncaughtExceptionHandler {

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("これはスレッドに設定されたUncaughtExceptionHandlerで出力されています。");
            System.out.println("ThreadGroup=" + t.getThreadGroup().getName() + "," + "Thread=" + t.getName());
            e.printStackTrace();//適切なロガーで出力してください。
        }
    }
}
ExecutorService service = Executors.newFixedThreadPool(1, new LoggingThreadFactory());

もちろん、自分自身のスレッドのエラー処理を書き換えてしまうこともできます。
ただ、わけがわからなくなると思うのでピンポイントでのデバッグ用途以外ではやらないほうが良いでしょう。

Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());

まとめ

説明したAPIを使用することでスレッド内で発生したキャッチされない例外を捕捉することができるようになります。
通常は標準エラーにスタックトレースが出力されてしまいますが、必要に応じてカスタマイズするようにしてください。

ソースコードの全体は以下に。
https://gist.github.com/megascus/0961698e426bf7d1f23b2f7be9be8210

Oracleにjdbcで接続したときのタイムゾーンの設定

以下のような感じらしい。

デフォルトではクライアントのユーザーが使用しているタイムゾーンが使用される。=システム環境変数(user.timezone)の値


システム環境変数で指定されているため、実行時のオプションで上書きすることが出来る

java -jar XXXX.jar -Duser.timezone=Asia/Tokyo


特定のセッションだけタイムゾーンを変更したい場合は、ALTER SESSIONで変更することが出来る。
タイムゾーンを変更するためのAPIは調べた限り存在しないっぽい。

ALTER SESSION SET TIME_ZONE='Asia/Tokyo'


現在のセッションのタイムゾーンを確認したい場合は以下のSQLを実行する。

select SESSIONTIMEZONE from dual


なお、コネクションプールを使用している場合はセッションの変更は引き継がれてしまうので注意。

Tomcatのバージョンごとのweb.xmlのヘッダー

それぞれバージョンがあってないと一部使用できない機能があるので注意。
※metadata-completeのデフォルト値はfalseで、trueの場合はウェブアプリケーションに関係するアノテーションが読み込まれない。

Tomcat 10.1

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
  version="6.0"
  metadata-complete="false">

Tomcat 10.0

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
  version="5.0"
  metadata-complete="false">

Tomcat 9.X

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="false">

Tomcat 8.X

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	 version="3.1"
         metadata-complete="false">

Tomcat 7.X

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
           version="3.0"
           metadata-complete="false">

JenkinsのGitプラグインで更新チェックに複数のブランチが引っかかった場合

JenkinsのGitプラグインで更新チェックに複数のブランチが引っかかった場合は複数のブランチに対してそれぞれビルドが走る。

ひとつのビルドタスクに以下のような記述が出て、他のはそれに引きずられてビルドされる感じ。

Multiple candidate revisions
Scheduling another build to catch up with ${project_name}

Java EE 8がそろそろ固まってきたのでJPA 2.2のJavaDocを日本語に翻訳し始めた(2/3完了)

タイトルの通りですが、Java EE 8準拠のJPA 2.2のJavaDocを日本語訳に翻訳し始めてみました。
サブパッケージを残して翻訳できたので変な場所があったら指摘をお願いします。

用語類は結構不安だったので、アンケート取ったけど、まあ、こんな感じなのかなぁという感想。
relationshipは関係と翻訳してたけど、そりゃ関連と翻訳したほうが良いと @aoetkさんから指摘をもらい、修正するかなーと思ってたら、意外とリレーションシップでよいという人が多かったので、そのまま採用。

他にも変な場所があったら教えてください。
https://megascus.github.io/jpa-spec/docs/apidocs/