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 }