001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.filter.logging;
021:
022: import java.net.InetSocketAddress;
023: import java.util.EnumSet;
024: import java.util.HashMap;
025: import java.util.HashSet;
026: import java.util.Map;
027: import java.util.Set;
028:
029: import org.apache.mina.common.AttributeKey;
030: import org.apache.mina.common.IoFilterEvent;
031: import org.apache.mina.common.IoSession;
032: import org.apache.mina.filter.util.CommonEventFilter;
033: import org.slf4j.MDC;
034:
035: /**
036: * This filter will inject some key IoSession properties into the Mapped Diagnostic Context (MDC)
037: * <p/>
038: * These properties will be set in the MDC for all logging events that are generated
039: * down the call stack, even in code that is not aware of MINA.
040: *
041: * By default, the following properties will be set for all transports:
042: * <ul>
043: * <li>"handlerClass"</li>
044: * <li>"remoteAddress"</li>
045: * <li>"localAddress"</li>
046: * </ul>
047: *
048: * When <code>session.getTransportMetadata().getAddressType() == InetSocketAddress.class</code>
049: * the following properties will also be set:
050: * <ul>
051: * <li>"remoteIp"</li>
052: * <li>"remotePort"</li>
053: * <li>"localIp"</li>
054: * <li>"localPort"</li>
055: * </ul>
056: *
057: * User code can also add properties to the context, via
058: *
059: * If you only want the MDC to be set for the IoHandler code, it's enough to add
060: * one MdcInjectionFilter at the end of the filter chain.
061: *
062: * If you want the MDC to be set for ALL code, you should
063: * add an MdcInjectionFilter to the start of the chain
064: * and add one after EVERY ExecutorFilter in the chain
065: *
066: * @author The Apache MINA Project (dev@mina.apache.org)
067: * @version $Rev: 566952 $, $Date: 2007-08-17 09:25:04 +0200 (vr, 17 aug 2007) $
068: */
069:
070: public class MdcInjectionFilter extends CommonEventFilter {
071:
072: public enum MdcKey {
073: handlerClass, remoteAddress, localAddress, remoteIp, remotePort, localIp, localPort
074: }
075:
076: /** key used for storing the context map in the IoSession */
077: private static final AttributeKey CONTEXT_KEY = new AttributeKey(
078: MdcInjectionFilter.class, "context");
079:
080: private ThreadLocal<Integer> callDepth = new ThreadLocal<Integer>() {
081: @Override
082: protected Integer initialValue() {
083: return 0;
084: }
085: };
086:
087: private EnumSet<MdcKey> mdcKeys;
088:
089: /**
090: * Use this constructor when you want to specify which keys to add to the MDC.
091: * You could still add custom keys via {@link #setProperty(IoSession, String, String)}
092: * @param keys set of keys that should be added to the MDC
093: *
094: * @see #setProperty(org.apache.mina.common.IoSession, String, String)
095: */
096: public MdcInjectionFilter(EnumSet<MdcKey> keys) {
097: this .mdcKeys = keys.clone();
098: }
099:
100: /**
101: * Use this constructor when you want to specify which keys to add to the MDC
102: * You could still add custom keys via {@link #setProperty(IoSession, String, String)}
103: * @param keys list of keys that should be added to the MDC
104: *
105: * @see #setProperty(org.apache.mina.common.IoSession, String, String)
106: */
107: public MdcInjectionFilter(MdcKey... keys) {
108: Set<MdcKey> keySet = new HashSet<MdcKey>();
109: for (MdcKey key : keys) {
110: keySet.add(key);
111: }
112: this .mdcKeys = EnumSet.copyOf(keySet);
113: }
114:
115: public MdcInjectionFilter() {
116: this .mdcKeys = EnumSet.allOf(MdcKey.class);
117: }
118:
119: @Override
120: protected void filter(IoFilterEvent event) throws Exception {
121: // since this method can potentially call into itself
122: // we need to check the call depth before clearing the MDC
123: int currentCallDepth = callDepth.get();
124: callDepth.set(currentCallDepth + 1);
125: Map<String, String> context = getAndFillContext(event
126: .getSession());
127:
128: if (currentCallDepth == 0) {
129: /* copy context to the MDC when necessary. */
130: for (Map.Entry<String, String> e : context.entrySet()) {
131: MDC.put(e.getKey(), e.getValue());
132: }
133: }
134:
135: try {
136: /* propagate event down the filter chain */
137: event.fire();
138: } finally {
139: if (currentCallDepth == 0) {
140: /* remove context from the MDC */
141: for (String key : context.keySet()) {
142: MDC.remove(key);
143: }
144: callDepth.remove();
145: } else {
146: callDepth.set(currentCallDepth);
147: }
148: }
149: }
150:
151: private Map<String, String> getAndFillContext(
152: final IoSession session) {
153: Map<String, String> context = getContext(session);
154: if (context.isEmpty()) {
155: fillContext(session, context);
156: }
157: return context;
158: }
159:
160: @SuppressWarnings("unchecked")
161: private static Map<String, String> getContext(
162: final IoSession session) {
163: Map<String, String> context = (Map<String, String>) session
164: .getAttribute(CONTEXT_KEY);
165: if (context == null) {
166: context = new HashMap<String, String>();
167: session.setAttribute(CONTEXT_KEY, context);
168: }
169: return context;
170: }
171:
172: /**
173: * write key properties of the session to the Mapped Diagnostic Context
174: * sub-classes could override this method to map more/other attributes
175: * @param session the session to map
176: * @param context key properties will be added to this map
177: */
178: protected void fillContext(final IoSession session,
179: final Map<String, String> context) {
180: if (mdcKeys.contains(MdcKey.handlerClass)) {
181: context.put(MdcKey.handlerClass.name(), session
182: .getHandler().getClass().getName());
183: }
184: if (mdcKeys.contains(MdcKey.remoteAddress)) {
185: context.put(MdcKey.remoteAddress.name(), session
186: .getRemoteAddress().toString());
187: }
188: if (mdcKeys.contains(MdcKey.localAddress)) {
189: context.put(MdcKey.localAddress.name(), session
190: .getLocalAddress().toString());
191: }
192: if (session.getTransportMetadata().getAddressType() == InetSocketAddress.class) {
193: InetSocketAddress remoteAddress = (InetSocketAddress) session
194: .getRemoteAddress();
195: InetSocketAddress localAddress = (InetSocketAddress) session
196: .getLocalAddress();
197: if (mdcKeys.contains(MdcKey.remoteIp)) {
198: context.put(MdcKey.remoteIp.name(), remoteAddress
199: .getAddress().getHostAddress());
200: }
201: if (mdcKeys.contains(MdcKey.remotePort)) {
202: context.put(MdcKey.remotePort.name(), String
203: .valueOf(remoteAddress.getPort()));
204: }
205: if (mdcKeys.contains(MdcKey.localIp)) {
206: context.put(MdcKey.localIp.name(), localAddress
207: .getAddress().getHostAddress());
208: }
209: if (mdcKeys.contains(MdcKey.localPort)) {
210: context.put(MdcKey.localPort.name(), String
211: .valueOf(localAddress.getPort()));
212: }
213: }
214: }
215:
216: public static String getProperty(IoSession session, String key) {
217: if (key == null) {
218: throw new NullPointerException("key should not be null");
219: }
220:
221: Map<String, String> context = getContext(session);
222: String answer = context.get(key);
223: if (answer != null) {
224: return answer;
225: }
226:
227: return MDC.get(key);
228: }
229:
230: /**
231: * Add a property to the context for the given session
232: * This property will be added to the MDC for all subsequent events
233: * @param session The session for which you want to set a property
234: * @param key The name of the property (should not be null)
235: * @param value The value of the property
236: */
237: public static void setProperty(IoSession session, String key,
238: String value) {
239: if (key == null) {
240: throw new NullPointerException("key should not be null");
241: }
242: if (value == null) {
243: removeProperty(session, key);
244: }
245: Map<String, String> context = getContext(session);
246: context.put(key, value);
247: MDC.put(key, value);
248: }
249:
250: public static void removeProperty(IoSession session, String key) {
251: if (key == null) {
252: throw new NullPointerException("key should not be null");
253: }
254: Map<String, String> context = getContext(session);
255: context.remove(key);
256: MDC.remove(key);
257: }
258: }
|