View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.logging;
21  
22  import java.net.InetSocketAddress;
23  import java.util.EnumSet;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.Arrays;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  import org.apache.mina.core.filterchain.IoFilterEvent;
31  import org.apache.mina.core.session.AttributeKey;
32  import org.apache.mina.core.session.IoSession;
33  import org.apache.mina.filter.util.CommonEventFilter;
34  import org.slf4j.MDC;
35  
36  /**
37   * This filter will inject some key IoSession properties into the Mapped Diagnostic Context (MDC)
38   * <p/>
39   * These properties will be set in the MDC for all logging events that are generated
40   * down the call stack, even in code that is not aware of MINA.
41   *
42   * By default, the following properties will be set for all transports:
43   * <ul>
44   *  <li>"handlerClass"</li>
45   *  <li>"remoteAddress"</li>
46   *  <li>"localAddress"</li>
47   * </ul>
48   *
49   * When <code>session.getTransportMetadata().getAddressType() == InetSocketAddress.class</code>
50   * the following properties will also be set:
51   * <ul>
52   * <li>"remoteIp"</li>
53   * <li>"remotePort"</li>
54   * <li>"localIp"</li>
55   * <li>"localPort"</li>
56   * </ul>
57   *
58   * User code can also add properties to the context, via
59   *
60   * If you only want the MDC to be set for the IoHandler code, it's enough to add
61   * one MdcInjectionFilter at the end of the filter chain.
62   *
63   * If you want the MDC to be set for ALL code, you should
64   *   add an MdcInjectionFilter to the start of the chain
65   *   and add that same MdcInjectionFilter instance after EVERY ExecutorFilter in the chain
66   *
67   * Thus it's ok to have one instance of the MdcInjectionFilter and add it multiple times to the chain
68   * but you should avoid adding multiple instances to the chain.
69   *
70   * @author The Apache MINA Project (dev@mina.apache.org)
71   * @version $Rev: 566952 $, $Date: 2007-08-17 09:25:04 +0200 (vr, 17 aug 2007) $
72   */
73  
74  public class MdcInjectionFilter extends CommonEventFilter {
75  
76      public enum MdcKey {
77          handlerClass, remoteAddress, localAddress, remoteIp, remotePort, localIp, localPort
78      }
79  
80      /** key used for storing the context map in the IoSession */
81      private static final AttributeKey CONTEXT_KEY = new AttributeKey(
82              MdcInjectionFilter.class, "context");
83  
84      private ThreadLocal<Integer> callDepth = new ThreadLocal<Integer>() {
85          @Override
86          protected Integer initialValue() {
87              return 0;
88          }
89      };
90  
91      private EnumSet<MdcKey> mdcKeys;
92  
93      /**
94       * Use this constructor when you want to specify which keys to add to the MDC.
95       * You could still add custom keys via {@link #setProperty(IoSession, String, String)}
96       * @param keys set of keys that should be added to the MDC
97       *
98       * @see #setProperty(org.apache.mina.core.session.IoSession, String, String)
99       */
100     public MdcInjectionFilter(EnumSet<MdcKey> keys) {
101         this.mdcKeys = keys.clone();
102     }
103 
104     /**
105      * Use this constructor when you want to specify which keys to add to the MDC
106      * You could still add custom keys via {@link #setProperty(IoSession, String, String)}
107      * @param keys list of keys that should be added to the MDC
108      *
109      * @see #setProperty(org.apache.mina.core.session.IoSession, String, String)
110      */
111     public MdcInjectionFilter(MdcKey... keys) {
112         Set<MdcKey> keySet = new HashSet<MdcKey>(Arrays.asList(keys));
113         this.mdcKeys = EnumSet.copyOf(keySet);
114     }
115 
116     public MdcInjectionFilter() {
117         this.mdcKeys = EnumSet.allOf(MdcKey.class);
118     }
119 
120     @Override
121     protected void filter(IoFilterEvent event) throws Exception {
122         // since this method can potentially call into itself
123         // we need to check the call depth before clearing the MDC
124         int currentCallDepth = callDepth.get();
125         callDepth.set(currentCallDepth + 1);
126         Map<String, String> context = getAndFillContext(event.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(final IoSession session) {
152         Map<String, String> context = getContext(session);
153         if (context.isEmpty()) {
154             fillContext(session, context);
155         }
156         return context;
157     }
158 
159     @SuppressWarnings("unchecked")
160     private static Map<String, String> getContext(final IoSession session) {
161         Map<String, String> context = (Map<String, String>) session.getAttribute(CONTEXT_KEY);
162         if (context == null) {
163             context = new ConcurrentHashMap<String, String>();
164             session.setAttribute(CONTEXT_KEY, context);
165         }
166         return context;
167     }
168 
169     /**
170      * write key properties of the session to the Mapped Diagnostic Context
171      * sub-classes could override this method to map more/other attributes
172      * @param session the session to map
173      * @param context key properties will be added to this map
174      */
175     protected void fillContext(final IoSession session, final Map<String, String> context) {
176         if (mdcKeys.contains(MdcKey.handlerClass)) {
177             context.put(MdcKey.handlerClass.name(), session.getHandler()
178                     .getClass().getName());
179         }
180         if (mdcKeys.contains(MdcKey.remoteAddress)) {
181             context.put(MdcKey.remoteAddress.name(), session.getRemoteAddress()
182                     .toString());
183         }
184         if (mdcKeys.contains(MdcKey.localAddress)) {
185             context.put(MdcKey.localAddress.name(), session.getLocalAddress()
186                     .toString());
187         }
188         if (session.getTransportMetadata().getAddressType() == InetSocketAddress.class) {
189             InetSocketAddress remoteAddress = (InetSocketAddress) session
190                     .getRemoteAddress();
191             InetSocketAddress localAddress = (InetSocketAddress) session
192                     .getLocalAddress();
193             if (mdcKeys.contains(MdcKey.remoteIp)) {
194                 context.put(MdcKey.remoteIp.name(), remoteAddress.getAddress()
195                         .getHostAddress());
196             }
197             if (mdcKeys.contains(MdcKey.remotePort)) {
198                 context.put(MdcKey.remotePort.name(), String
199                         .valueOf(remoteAddress.getPort()));
200             }
201             if (mdcKeys.contains(MdcKey.localIp)) {
202                 context.put(MdcKey.localIp.name(), localAddress.getAddress()
203                         .getHostAddress());
204             }
205             if (mdcKeys.contains(MdcKey.localPort)) {
206                 context.put(MdcKey.localPort.name(), String
207                         .valueOf(localAddress.getPort()));
208             }
209         }
210     }
211 
212     public static String getProperty(IoSession session, String key) {
213         if (key == null) {
214             throw new NullPointerException("key should not be null");
215         }
216 
217         Map<String, String> context = getContext(session);
218         String answer = context.get(key);
219         if (answer != null) {
220             return answer;
221         }
222 
223         return MDC.get(key);
224     }
225 
226 
227     /**
228      * Add a property to the context for the given session
229      * This property will be added to the MDC for all subsequent events
230      * @param session The session for which you want to set a property
231      * @param key  The name of the property (should not be null)
232      * @param value The value of the property
233      */
234     public static void setProperty(IoSession session, String key, String value) {
235         if (key == null) {
236             throw new NullPointerException("key should not be null");
237         }
238         if (value == null) {
239             removeProperty(session, key);
240         }
241         Map<String, String> context = getContext(session);
242         context.put(key, value);
243         MDC.put(key, value);
244     }
245 
246     public static void removeProperty(IoSession session, String key) {
247         if (key == null) {
248             throw new NullPointerException("key should not be null");
249         }
250         Map<String, String> context = getContext(session);
251         context.remove(key);
252         MDC.remove(key);
253     }
254 }