Skip to content

Commit 089cd90

Browse files
Merge pull request #87 from classmethod/feature/mdc-set-request-before-logbookfilter
MDC の情報を request scope から設定する Filter 追加
2 parents 318b13f + c65bc24 commit 089cd90

File tree

3 files changed

+206
-0
lines changed

3 files changed

+206
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package jp.xet.sparwings.aws.ec2;
17+
18+
import java.io.IOException;
19+
import java.util.Map;
20+
21+
import javax.servlet.Filter;
22+
import javax.servlet.FilterChain;
23+
import javax.servlet.ServletException;
24+
import javax.servlet.ServletRequest;
25+
import javax.servlet.ServletResponse;
26+
27+
import org.slf4j.MDC;
28+
29+
/**
30+
* MDC の情報を request scope から設定する Filter.
31+
*
32+
* <p>
33+
* 経緯は以下の通り
34+
* 1. Springでコントローラーから例外をスローすると2週目のフィルターチェーンが動く
35+
* 2. FilterでMDCを操作する際にdoFilterの後MDCをクリアすると、2週目のフィルターチェーンでMDCがクリアされている、という状況になる
36+
* 3. 1リクエストに対して1回だけMDCに何かを登録したい場合、OncePerRequestFilterで一度だけMDCにを操作したい
37+
* - つまり、1つのトランザクションに対して1度だけMDCを操作したい場合、同じトランザクションの2回目以降のフィルターチェーンでもMDCを参照したい
38+
* 4. 初回のフィルターチェーンでMDCをリクエストスコープにコピー &amp; 2回目以降のフィルターチェーンで
39+
* リクエストスコープにコピーしたMDCをThreadLocalのMDCにセットすることで、
40+
* 1回目に組み立てたMDCを2回目以降のフィルターチェーンでも使い回せるようにする
41+
*
42+
* 本 Filter は、LogbookFilter の直前に登録する必要があります。
43+
* DispatcherType には REQUEST, ASYNC, ERROR を含めてください。
44+
* </p>
45+
*/
46+
public class MDCInsertingForRequestServletFilter implements Filter {
47+
48+
static final String STORED_MDC_KEY =
49+
MDCInsertingForRequestServletFilter.class.getName() + "_STORED_MDC_KEY";
50+
51+
52+
/**
53+
* filter 処理.
54+
*
55+
* <p>
56+
* 初回リクエストの場合、以下を行う
57+
*
58+
* - request scope に MDC の値をコピー
59+
* - 後続の filter 呼び出し
60+
*
61+
* 2回目以降のリクエストの場合、以下を行う
62+
*
63+
* - request scope から MDC の値を設定
64+
* - 後続の filter 呼び出し
65+
* - MDC の値をクリア
66+
*
67+
* </p>
68+
* @param request request
69+
* @param response response
70+
* @param chain chain
71+
* @throws IOException 例外
72+
* @throws ServletException 例外
73+
*/
74+
@Override
75+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
76+
throws IOException, ServletException {
77+
78+
@SuppressWarnings("unchecked")
79+
Map<String, String> storedMdc = (Map<String, String>) request.getAttribute(STORED_MDC_KEY);
80+
if (storedMdc == null) {
81+
// request scope に未設定の場合、初回扱い
82+
firstTimeDoFilter(request, response, chain);
83+
} else {
84+
// request scope に設定済みの場合、2回目以降の呼び出し
85+
afterSecondTimeDoFilter(request, response, chain, storedMdc);
86+
}
87+
}
88+
89+
private void firstTimeDoFilter(ServletRequest request, ServletResponse response, FilterChain chain)
90+
throws IOException, ServletException {
91+
// request scope に mdc の値をコピー
92+
request.setAttribute(STORED_MDC_KEY, MDC.getCopyOfContextMap());
93+
chain.doFilter(request, response);
94+
}
95+
96+
private void afterSecondTimeDoFilter(ServletRequest request, ServletResponse response, FilterChain chain,
97+
Map<String, String> storedMdc) throws IOException, ServletException {
98+
// request scope の mdc を MDC に展開
99+
MDC.setContextMap(storedMdc);
100+
try {
101+
chain.doFilter(request, response);
102+
} finally {
103+
MDC.clear();
104+
}
105+
}
106+
}

spar-wings-aws-logging/src/test/java/.gitkeep

Whitespace-only changes.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package jp.xet.sparwings.aws.ec2;
17+
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.ArgumentMatchers.anyString;
20+
import static org.mockito.ArgumentMatchers.eq;
21+
import static org.mockito.Mockito.doNothing;
22+
import static org.mockito.Mockito.never;
23+
import static org.mockito.Mockito.verify;
24+
import static org.mockito.Mockito.when;
25+
26+
import java.io.IOException;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
import javax.servlet.FilterChain;
31+
import javax.servlet.ServletException;
32+
import javax.servlet.ServletRequest;
33+
import javax.servlet.ServletResponse;
34+
35+
import org.junit.Test;
36+
import org.junit.runner.RunWith;
37+
import org.mockito.Mock;
38+
import org.mockito.junit.MockitoJUnitRunner;
39+
40+
/**
41+
* Test for {@link MDCInsertingForRequestServletFilter}.
42+
*/
43+
@RunWith(MockitoJUnitRunner.class)
44+
public class MDCInsertingForRequestServletFilterTest {
45+
46+
@Mock
47+
ServletRequest request;
48+
49+
@Mock
50+
ServletResponse response;
51+
52+
@Mock
53+
FilterChain chain;
54+
55+
56+
/**
57+
* request scope に MDC が存在しない時のテスト.
58+
*/
59+
@Test
60+
public void testDoFilter_FirstTimeDoFilter() throws IOException, ServletException {
61+
// setup
62+
when(request.getAttribute(anyString())).thenReturn(null);
63+
doNothing().when(request).setAttribute(anyString(), any());
64+
doNothing().when(chain).doFilter(any(), any());
65+
66+
// exercise
67+
MDCInsertingForRequestServletFilter sut = new MDCInsertingForRequestServletFilter();
68+
sut.doFilter(request, response, chain);
69+
70+
// verify
71+
verify(request).getAttribute(MDCInsertingForRequestServletFilter.STORED_MDC_KEY);
72+
verify(request).setAttribute(eq(MDCInsertingForRequestServletFilter.STORED_MDC_KEY), any()); // 呼べていれば良い
73+
verify(chain).doFilter(request, response);
74+
}
75+
76+
/**
77+
* request scope に MDC が存在する時のテスト.
78+
*/
79+
@Test
80+
public void testDoFilter_AfterSecondTimeDoFilter() throws IOException, ServletException {
81+
// setup
82+
Map<String, String> storedMdc = new HashMap<>();
83+
storedMdc.put("foo", "foo1");
84+
storedMdc.put("bar", "bar1");
85+
storedMdc.put("baz", "baz1");
86+
when(request.getAttribute(anyString())).thenReturn(storedMdc);
87+
doNothing().when(chain).doFilter(any(), any());
88+
89+
// exercise
90+
MDCInsertingForRequestServletFilter sut = new MDCInsertingForRequestServletFilter();
91+
sut.doFilter(request, response, chain);
92+
93+
// verify
94+
verify(request).getAttribute(MDCInsertingForRequestServletFilter.STORED_MDC_KEY);
95+
verify(chain).doFilter(request, response);
96+
97+
verify(request, never()).setAttribute(any(), any());
98+
}
99+
100+
}

0 commit comments

Comments
 (0)