TestRule、特に apply() を拡張する必要があります。例として、org.junit.rules.ExternalResource & org.junit.rules.TemporaryFolder を見てください。
ExternalResource は次のようになります。
public abstract class ExternalResource implements TestRule {
    public Statement apply(Statement base, Description description) {
        return statement(base);
    }
    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }
    /**
     * Override to set up your specific external resource.
     * @throws if setup fails (which will disable {@code after}
     */
    protected void before() throws Throwable {
        // do nothing
    }
    /**
     * Override to tear down your specific external resource.
     */
    protected void after() {
        // do nothing
    }
}
次に、TemporaryFolder はこれを拡張し、before() と after() を実装します。
public class TemporaryFolder extends ExternalResource {
    private File folder;
    @Override
    protected void before() throws Throwable {
        // create the folder
    }
    @Override
    protected void after() {
        // delete the folder
    }
したがって、before は testMethod の前に呼び出され、after は finally で呼び出されますが、次のように例外をキャッチしてログに記録できます。
    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } catch (Exception e) {
                    log.error("caught Exception", e);
                } finally {
                    after();
                }
            }
        };
    }
編集:次の作品:
public class SoTest {
    public class ExceptionLoggingRule implements TestRule {
        public Statement apply(Statement base, Description description) {
            return statement(base);
        }
        private Statement statement(final Statement base) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    try {
                        base.evaluate();
                    } catch (Exception e) {
                        System.out.println("caught an exception");
                        e.printStackTrace(System.out);
                        throw e;
                    }
                }
            };
        }
    }
    @Rule public ExceptionLoggingRule exceptionLoggingRule = new ExceptionLoggingRule();
    @Rule public ExpectedException expectedException = ExpectedException.none();
    @Test
    public void testMe() throws Exception {
        expectedException.expect(IOException.class);
        throw new IOException("here we are");
    }
}
テストに合格し、次の出力が得られます。
caught an exception
java.io.IOException: here we are
    at uk.co.farwell.junit.SoTest.testMe(SoTest.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
...
ルールが適用される順序は、testMe メソッドを呼び出す ExceptionLoggingRule を呼び出す ExpectedException です。ExceptionLoggingRule は例外をキャッチし、ログに記録して再スローし、ExpectedException によって処理されます。
予期しない例外のみをログに記録する場合は、ルールの宣言順序を切り替えるだけです。
    @Rule public ExpectedException expectedException = ExpectedException.none();
    @Rule public ExceptionLoggingRule exceptionLoggingRule = new ExceptionLoggingRule();
このようにして、expectedException が最初に適用され (つまり、exceptionLoggingRule にネストされ)、予期されていない例外のみが再スローされます。さらに、何らかの例外が予想され、何も発生しなかった場合、expectedException は AssertionError をスローし、これもログに記録されます。
この評価順序は保証されていませんが、非常に異なる JVM で遊んだり、Test クラス間で継承したりしない限り、変化する可能性はほとんどありません。
評価順序が重要な場合は、いつでも一方のルールを他方のルールに渡して評価することができます。
編集: 最近リリースされた Junit 4.10 では、 @RuleChain を使用してルールを正しくチェーンできます。
public static class UseRuleChain {
   @Rule
   public TestRule chain= RuleChain
                          .outerRule(new LoggingRule("outer rule")
                          .around(new LoggingRule("middle rule")
                          .around(new LoggingRule("inner rule");
   @Test
   public void example() {
           assertTrue(true);
   }
}
ログを書き込みます
starting outer rule
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule