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.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.logging.log4j.ThreadContext;
24  import org.apache.logging.log4j.core.ContextDataInjector;
25  import org.apache.logging.log4j.core.config.Property;
26  import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
27  import org.apache.logging.log4j.util.ReadOnlyStringMap;
28  import org.apache.logging.log4j.util.StringMap;
29  
30  /**
31   * {@code ThreadContextDataInjector} contains a number of strategies for copying key-value pairs from the various
32   * {@code ThreadContext} map implementations into a {@code StringMap}. In the case of duplicate keys,
33   * thread context values overwrite configuration {@code Property} values.
34   * <p>
35   * These are the default {@code ContextDataInjector} objects returned by the {@link ContextDataInjectorFactory}.
36   * </p>
37   *
38   * @see org.apache.logging.log4j.ThreadContext
39   * @see Property
40   * @see ReadOnlyStringMap
41   * @see ContextDataInjector
42   * @see ContextDataInjectorFactory
43   * @since 2.7
44   */
45  public class ThreadContextDataInjector {
46  
47      /**
48       * Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is
49       * also the ThreadContext implementation used for web applications).
50       * <p>
51       * This injector always puts key-value pairs into the specified reusable StringMap.
52       */
53      public static class ForDefaultThreadContextMap implements ContextDataInjector {
54  
55          /**
56           * Puts key-value pairs from both the specified list of properties as well as the thread context into the
57           * specified reusable StringMap.
58           *
59           * @param props list of configuration properties, may be {@code null}
60           * @param ignore a {@code StringMap} instance from the log event
61           * @return a {@code StringMap} combining configuration properties with thread context data
62           */
63          @Override
64          public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
65  
66              final Map<String, String> copy = ThreadContext.getImmutableContext();
67  
68              // The DefaultThreadContextMap stores context data in a Map<String, String>.
69              // This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
70              // If there are no configuration properties returning a thin wrapper around the copy
71              // is faster than copying the elements into the LogEvent's reusable StringMap.
72              if (props == null || props.isEmpty()) {
73                  // this will replace the LogEvent's context data with the returned instance.
74                  // NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx)
75                  return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy);
76              }
77              // If the list of Properties is non-empty we need to combine the properties and the ThreadContext
78              // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
79              // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
80              // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
81              final StringMap result = new JdkMapAdapterStringMap(new HashMap<>(copy));
82              for (int i = 0; i < props.size(); i++) {
83                  final Property prop = props.get(i);
84                  if (!copy.containsKey(prop.getName())) {
85                      result.putValue(prop.getName(), prop.getValue());
86                  }
87              }
88              result.freeze();
89              return result;
90          }
91  
92          private static JdkMapAdapterStringMap frozenStringMap(final Map<String, String> copy) {
93              final JdkMapAdapterStringMap result = new JdkMapAdapterStringMap(copy);
94              result.freeze();
95              return result;
96          }
97  
98          @Override
99          public ReadOnlyStringMap rawContextData() {
100             final ReadOnlyThreadContextMap map = ThreadContext.getThreadContextMap();
101             if (map instanceof ReadOnlyStringMap) {
102                 return (ReadOnlyStringMap) map;
103             }
104             // note: default ThreadContextMap is null
105             final Map<String, String> copy = ThreadContext.getImmutableContext();
106             return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : new JdkMapAdapterStringMap(copy);
107         }
108     }
109 
110     /**
111      * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a garbage-free
112      * StringMap-based data structure.
113      * <p>
114      * This injector always puts key-value pairs into the specified reusable StringMap.
115      */
116     public static class ForGarbageFreeThreadContextMap implements ContextDataInjector {
117         /**
118          * Puts key-value pairs from both the specified list of properties as well as the thread context into the
119          * specified reusable StringMap.
120          *
121          * @param props list of configuration properties, may be {@code null}
122          * @param reusable a {@code StringMap} instance that may be reused to avoid creating temporary objects
123          * @return a {@code StringMap} combining configuration properties with thread context data
124          */
125         @Override
126         public StringMap injectContextData(final List<Property> props, final StringMap reusable) {
127             // When the ThreadContext is garbage-free, we must copy its key-value pairs into the specified reusable
128             // StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later
129             // and such modifications should not be reflected in the log event.
130             copyProperties(props, reusable);
131 
132             final ReadOnlyStringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
133             reusable.putAll(immutableCopy);
134             return reusable;
135         }
136 
137         @Override
138         public ReadOnlyStringMap rawContextData() {
139             return ThreadContext.getThreadContextMap().getReadOnlyContextData();
140         }
141     }
142 
143     /**
144      * The {@code ContextDataInjector} used when the ThreadContextMap implementation is a copy-on-write
145      * StringMap-based data structure.
146      * <p>
147      * If there are no configuration properties, this injector will return the thread context's internal data
148      * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
149      * specified reusable StringMap.
150      */
151     public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector {
152         /**
153          * If there are no configuration properties, this injector will return the thread context's internal data
154          * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
155          * specified reusable StringMap.
156          *
157          * @param props list of configuration properties, may be {@code null}
158          * @param ignore a {@code StringMap} instance from the log event
159          * @return a {@code StringMap} combining configuration properties with thread context data
160          */
161         @Override
162         public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
163             // If there are no configuration properties we want to just return the ThreadContext's StringMap:
164             // it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
165             final StringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
166             if (props == null || props.isEmpty()) {
167                 return immutableCopy; // this will replace the LogEvent's context data with the returned instance
168             }
169             // However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext
170             // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
171             // and others not, so the LogEvent's context data may have been replaced with an immutable copy from
172             // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
173             final StringMap result = ContextDataFactory.createContextData(props.size() + immutableCopy.size());
174             copyProperties(props, result);
175             result.putAll(immutableCopy);
176             return result;
177         }
178 
179         @Override
180         public ReadOnlyStringMap rawContextData() {
181             return ThreadContext.getThreadContextMap().getReadOnlyContextData();
182         }
183     }
184 
185     /**
186      * Copies key-value pairs from the specified property list into the specified {@code StringMap}.
187      *
188      * @param properties list of configuration properties, may be {@code null}
189      * @param result the {@code StringMap} object to add the key-values to. Must be non-{@code null}.
190      */
191     public static void copyProperties(final List<Property> properties, final StringMap result) {
192         if (properties != null) {
193             for (int i = 0; i < properties.size(); i++) {
194                 final Property prop = properties.get(i);
195                 result.putValue(prop.getName(), prop.getValue());
196             }
197         }
198     }
199 }