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