View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.ServiceLoader;
25  import java.util.concurrent.ConcurrentLinkedDeque;
26  
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.ThreadContext;
29  import org.apache.logging.log4j.core.ContextDataInjector;
30  import org.apache.logging.log4j.core.config.Property;
31  import org.apache.logging.log4j.core.util.ContextDataProvider;
32  import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
33  import org.apache.logging.log4j.status.StatusLogger;
34  import org.apache.logging.log4j.util.LoaderUtil;
35  import org.apache.logging.log4j.util.ReadOnlyStringMap;
36  import org.apache.logging.log4j.util.StringMap;
37  
38  /**
39   * {@code ThreadContextDataInjector} contains a number of strategies for copying key-value pairs from the various
40   * {@code ThreadContext} map implementations into a {@code StringMap}. In the case of duplicate keys,
41   * thread context values overwrite configuration {@code Property} values.
42   * <p>
43   * These are the default {@code ContextDataInjector} objects returned by the {@link ContextDataInjectorFactory}.
44   * </p>
45   *
46   * @see org.apache.logging.log4j.ThreadContext
47   * @see Property
48   * @see ReadOnlyStringMap
49   * @see ContextDataInjector
50   * @see ContextDataInjectorFactory
51   * @since 2.7
52   */
53  public class ThreadContextDataInjector {
54  
55      private static Logger LOGGER = StatusLogger.getLogger();
56  
57      /**
58       * ContextDataProviders loaded via OSGi.
59       */
60      public static Collection<ContextDataProvider> contextDataProviders =
61              new ConcurrentLinkedDeque<>();
62  
63      /**
64       * Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is
65       * also the ThreadContext implementation used for web applications).
66       * <p>
67       * This injector always puts key-value pairs into the specified reusable StringMap.
68       */
69      public static class ForDefaultThreadContextMap implements ContextDataInjector {
70  
71          private final List<ContextDataProvider> providers;
72  
73          public ForDefaultThreadContextMap() {
74              providers = getProviders();
75          }
76  
77          /**
78           * Puts key-value pairs from both the specified list of properties as well as the thread context into the
79           * specified reusable StringMap.
80           *
81           * @param props list of configuration properties, may be {@code null}
82           * @param contextData a {@code StringMap} instance from the log event
83           * @return a {@code StringMap} combining configuration properties with thread context data
84           */
85          @Override
86          public StringMap injectContextData(final List<Property> props, final StringMap contextData) {
87  
88              final Map<String, String> copy;
89  
90              if (providers.size() == 1) {
91                  copy = providers.get(0).supplyContextData();
92              } else {
93                  copy = new HashMap<>();
94                  for (ContextDataProvider provider : providers) {
95                      copy.putAll(provider.supplyContextData());
96                  }
97              }
98  
99              // The DefaultThreadContextMap stores context data in a Map<String, String>.
100             // This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
101             // If there are no configuration properties or providers returning a thin wrapper around the copy
102             // is faster than copying the elements into the LogEvent's reusable StringMap.
103             if ((props == null || props.isEmpty())) {
104                 // this will replace the LogEvent's context data with the returned instance.
105                 // NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx)
106                 return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy);
107             }
108             // If the list of Properties is non-empty we need to combine the properties and the ThreadContext
109             // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
110             // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
111             // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
112             final StringMap result = new JdkMapAdapterStringMap(new HashMap<>(copy));
113             for (int i = 0; i < props.size(); i++) {
114                 final Property prop = props.get(i);
115                 if (!copy.containsKey(prop.getName())) {
116                     result.putValue(prop.getName(), prop.getValue());
117                 }
118             }
119             result.freeze();
120             return result;
121         }
122 
123         private static JdkMapAdapterStringMap frozenStringMap(final Map<String, String> copy) {
124             final JdkMapAdapterStringMap result = new JdkMapAdapterStringMap(copy);
125             result.freeze();
126             return result;
127         }
128 
129         @Override
130         public ReadOnlyStringMap rawContextData() {
131             final ReadOnlyThreadContextMap map = ThreadContext.getThreadContextMap();
132             if (map instanceof ReadOnlyStringMap) {
133                 return (ReadOnlyStringMap) map;
134             }
135             // note: default ThreadContextMap is null
136             final Map<String, String> copy = ThreadContext.getImmutableContext();
137             return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : new JdkMapAdapterStringMap(copy);
138         }
139     }
140 
141     /**
142      * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a garbage-free
143      * StringMap-based data structure.
144      * <p>
145      * This injector always puts key-value pairs into the specified reusable StringMap.
146      */
147     public static class ForGarbageFreeThreadContextMap implements ContextDataInjector {
148         private final List<ContextDataProvider> providers;
149 
150         public ForGarbageFreeThreadContextMap() {
151             this.providers = getProviders();
152         }
153 
154         /**
155          * Puts key-value pairs from both the specified list of properties as well as the thread context into the
156          * specified reusable StringMap.
157          *
158          * @param props list of configuration properties, may be {@code null}
159          * @param reusable a {@code StringMap} instance that may be reused to avoid creating temporary objects
160          * @return a {@code StringMap} combining configuration properties with thread context data
161          */
162         @Override
163         public StringMap injectContextData(final List<Property> props, final StringMap reusable) {
164             // When the ThreadContext is garbage-free, we must copy its key-value pairs into the specified reusable
165             // StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later
166             // and such modifications should not be reflected in the log event.
167             copyProperties(props, reusable);
168             for (int i = 0; i < providers.size(); ++i) {
169                 reusable.putAll(providers.get(i).supplyStringMap());
170             }
171             return reusable;
172         }
173 
174         @Override
175         public ReadOnlyStringMap rawContextData() {
176             return ThreadContext.getThreadContextMap().getReadOnlyContextData();
177         }
178     }
179 
180     /**
181      * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a copy-on-write
182      * StringMap-based data structure.
183      * <p>
184      * If there are no configuration properties, this injector will return the thread context's internal data
185      * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
186      * specified reusable StringMap.
187      */
188     public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector {
189         private final List<ContextDataProvider> providers;
190 
191         public ForCopyOnWriteThreadContextMap() {
192             this.providers = getProviders();
193         }
194         /**
195          * If there are no configuration properties, this injector will return the thread context's internal data
196          * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
197          * specified reusable StringMap.
198          *
199          * @param props list of configuration properties, may be {@code null}
200          * @param ignore a {@code StringMap} instance from the log event
201          * @return a {@code StringMap} combining configuration properties with thread context data
202          */
203         @Override
204         public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
205             // If there are no configuration properties we want to just return the ThreadContext's StringMap:
206             // it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
207             if (providers.size() == 1 && (props == null || props.isEmpty())) {
208                 // this will replace the LogEvent's context data with the returned instance
209                 return providers.get(0).supplyStringMap();
210             }
211             int count = props.size();
212             StringMap[] maps = new StringMap[providers.size()];
213             for (int i = 0; i < providers.size(); ++i) {
214                 maps[i] = providers.get(i).supplyStringMap();
215                 count += maps[i].size();
216             }
217             // However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext
218             // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
219             // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
220             // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
221             final StringMap result = ContextDataFactory.createContextData(count);
222             copyProperties(props, result);
223             for (StringMap map : maps) {
224                 result.putAll(map);
225             }
226             return result;
227         }
228 
229         @Override
230         public ReadOnlyStringMap rawContextData() {
231             return ThreadContext.getThreadContextMap().getReadOnlyContextData();
232         }
233     }
234 
235     /**
236      * Copies key-value pairs from the specified property list into the specified {@code StringMap}.
237      *
238      * @param properties list of configuration properties, may be {@code null}
239      * @param result the {@code StringMap} object to add the key-values to. Must be non-{@code null}.
240      */
241     public static void copyProperties(final List<Property> properties, final StringMap result) {
242         if (properties != null) {
243             for (int i = 0; i < properties.size(); i++) {
244                 final Property prop = properties.get(i);
245                 result.putValue(prop.getName(), prop.getValue());
246             }
247         }
248     }
249 
250     private static List<ContextDataProvider> getProviders() {
251         final List<ContextDataProvider> providers = new ArrayList<>(contextDataProviders);
252         for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
253             try {
254                 for (final ContextDataProvider provider : ServiceLoader.load(ContextDataProvider.class, classLoader)) {
255                     if (providers.stream().noneMatch((p) -> p.getClass().isAssignableFrom(provider.getClass()))) {
256                         providers.add(provider);
257                     }
258                 }
259             } catch (final Throwable ex) {
260                 LOGGER.debug("Unable to access Context Data Providers {}", ex.getMessage());
261             }
262         }
263         return providers;
264     }
265 }