001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.lookup; 018 019import java.util.HashMap; 020import java.util.List; 021import java.util.Locale; 022import java.util.Map; 023 024import org.apache.logging.log4j.Logger; 025import org.apache.logging.log4j.core.LogEvent; 026import org.apache.logging.log4j.core.config.ConfigurationAware; 027import org.apache.logging.log4j.core.config.plugins.util.PluginManager; 028import org.apache.logging.log4j.core.config.plugins.util.PluginType; 029import org.apache.logging.log4j.core.net.JndiManager; 030import org.apache.logging.log4j.core.util.Loader; 031import org.apache.logging.log4j.core.util.ReflectionUtil; 032import org.apache.logging.log4j.status.StatusLogger; 033import org.apache.logging.log4j.util.Constants; 034 035/** 036 * Proxies all the other {@link StrLookup}s. 037 */ 038public class Interpolator extends AbstractConfigurationAwareLookup { 039 040 /** Constant for the prefix separator. */ 041 public static final char PREFIX_SEPARATOR = ':'; 042 043 private static final String LOOKUP_KEY_WEB = "web"; 044 045 private static final String LOOKUP_KEY_DOCKER = "docker"; 046 047 private static final String LOOKUP_KEY_KUBERNETES = "kubernetes"; 048 049 private static final String LOOKUP_KEY_SPRING = "spring"; 050 051 private static final String LOOKUP_KEY_JNDI = "jndi"; 052 053 private static final String LOOKUP_KEY_JVMRUNARGS = "jvmrunargs"; 054 055 private static final Logger LOGGER = StatusLogger.getLogger(); 056 057 private final Map<String, StrLookup> strLookupMap = new HashMap<>(); 058 059 private final StrLookup defaultLookup; 060 061 public Interpolator(final StrLookup defaultLookup) { 062 this(defaultLookup, null); 063 } 064 065 /** 066 * Constructs an Interpolator using a given StrLookup and a list of packages to find Lookup plugins in. 067 * 068 * @param defaultLookup the default StrLookup to use as a fallback 069 * @param pluginPackages a list of packages to scan for Lookup plugins 070 * @since 2.1 071 */ 072 public Interpolator(final StrLookup defaultLookup, final List<String> pluginPackages) { 073 this.defaultLookup = defaultLookup == null ? new MapLookup(new HashMap<String, String>()) : defaultLookup; 074 final PluginManager manager = new PluginManager(CATEGORY); 075 manager.collectPlugins(pluginPackages); 076 final Map<String, PluginType<?>> plugins = manager.getPlugins(); 077 078 for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) { 079 try { 080 final Class<? extends StrLookup> clazz = entry.getValue().getPluginClass().asSubclass(StrLookup.class); 081 if (!clazz.getName().equals(JndiLookup.class.getName()) || JndiManager.isJndiEnabled()) { 082 strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz)); 083 } 084 } catch (final Throwable t) { 085 handleError(entry.getKey(), t); 086 } 087 } 088 } 089 090 /** 091 * Create the default Interpolator using only Lookups that work without an event. 092 */ 093 public Interpolator() { 094 this((Map<String, String>) null); 095 } 096 097 /** 098 * Creates the Interpolator using only Lookups that work without an event and initial properties. 099 */ 100 public Interpolator(final Map<String, String> properties) { 101 this.defaultLookup = new MapLookup(properties == null ? new HashMap<String, String>() : properties); 102 // TODO: this ought to use the PluginManager 103 strLookupMap.put("log4j", new Log4jLookup()); 104 strLookupMap.put("sys", new SystemPropertiesLookup()); 105 strLookupMap.put("env", new EnvironmentLookup()); 106 strLookupMap.put("main", MainMapLookup.MAIN_SINGLETON); 107 strLookupMap.put("marker", new MarkerLookup()); 108 strLookupMap.put("java", new JavaLookup()); 109 strLookupMap.put("lower", new LowerLookup()); 110 strLookupMap.put("upper", new UpperLookup()); 111 // JNDI 112 if (JndiManager.isJndiEnabled()) { 113 try { 114 // [LOG4J2-703] We might be on Android 115 strLookupMap.put(LOOKUP_KEY_JNDI, 116 Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class)); 117 } catch (final LinkageError | Exception e) { 118 handleError(LOOKUP_KEY_JNDI, e); 119 } 120 } 121 // JMX input args 122 try { 123 // We might be on Android 124 strLookupMap.put(LOOKUP_KEY_JVMRUNARGS, 125 Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup", 126 StrLookup.class)); 127 } catch (final LinkageError | Exception e) { 128 handleError(LOOKUP_KEY_JVMRUNARGS, e); 129 } 130 strLookupMap.put("date", new DateLookup()); 131 strLookupMap.put("ctx", new ContextMapLookup()); 132 if (Constants.IS_WEB_APP) { 133 try { 134 strLookupMap.put(LOOKUP_KEY_WEB, 135 Loader.newCheckedInstanceOf("org.apache.logging.log4j.web.WebLookup", StrLookup.class)); 136 } catch (final Exception ignored) { 137 handleError(LOOKUP_KEY_WEB, ignored); 138 } 139 } else { 140 LOGGER.debug("Not in a ServletContext environment, thus not loading WebLookup plugin."); 141 } 142 try { 143 strLookupMap.put(LOOKUP_KEY_DOCKER, 144 Loader.newCheckedInstanceOf("org.apache.logging.log4j.docker.DockerLookup", StrLookup.class)); 145 } catch (final Exception ignored) { 146 handleError(LOOKUP_KEY_DOCKER, ignored); 147 } 148 try { 149 strLookupMap.put(LOOKUP_KEY_SPRING, 150 Loader.newCheckedInstanceOf("org.apache.logging.log4j.spring.cloud.config.client.SpringLookup", StrLookup.class)); 151 } catch (final Exception ignored) { 152 handleError(LOOKUP_KEY_SPRING, ignored); 153 } 154 try { 155 strLookupMap.put(LOOKUP_KEY_KUBERNETES, 156 Loader.newCheckedInstanceOf("org.apache.logging.log4j.kubernetes.KubernetesLookup", StrLookup.class)); 157 } catch (final Exception | NoClassDefFoundError error) { 158 handleError(LOOKUP_KEY_KUBERNETES, error); 159 } 160 } 161 162 public Map<String, StrLookup> getStrLookupMap() { 163 return strLookupMap; 164 } 165 166 private void handleError(final String lookupKey, final Throwable t) { 167 switch (lookupKey) { 168 case LOOKUP_KEY_JNDI: 169 // java.lang.VerifyError: org/apache/logging/log4j/core/lookup/JndiLookup 170 LOGGER.warn( // LOG4J2-1582 don't print the whole stack trace (it is just a warning...) 171 "JNDI lookup class is not available because this JRE does not support JNDI." + 172 " JNDI string lookups will not be available, continuing configuration. Ignoring " + t); 173 break; 174 case LOOKUP_KEY_JVMRUNARGS: 175 // java.lang.VerifyError: org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup 176 LOGGER.warn( 177 "JMX runtime input lookup class is not available because this JRE does not support JMX. " + 178 "JMX lookups will not be available, continuing configuration. Ignoring " + t); 179 break; 180 case LOOKUP_KEY_WEB: 181 LOGGER.info("Log4j appears to be running in a Servlet environment, but there's no log4j-web module " + 182 "available. If you want better web container support, please add the log4j-web JAR to your " + 183 "web archive or server lib directory."); 184 break; 185 case LOOKUP_KEY_DOCKER: case LOOKUP_KEY_SPRING: 186 break; 187 case LOOKUP_KEY_KUBERNETES: 188 if (t instanceof NoClassDefFoundError) { 189 LOGGER.warn("Unable to create Kubernetes lookup due to missing dependency: {}", t.getMessage()); 190 } 191 break; 192 default: 193 LOGGER.error("Unable to create Lookup for {}", lookupKey, t); 194 } 195 } 196 197 /** 198 * Resolves the specified variable. This implementation will try to extract 199 * a variable prefix from the given variable name (the first colon (':') is 200 * used as prefix separator). It then passes the name of the variable with 201 * the prefix stripped to the lookup object registered for this prefix. If 202 * no prefix can be found or if the associated lookup object cannot resolve 203 * this variable, the default lookup object will be used. 204 * 205 * @param event The current LogEvent or null. 206 * @param var the name of the variable whose value is to be looked up 207 * @return the value of this variable or <b>null</b> if it cannot be 208 * resolved 209 */ 210 @Override 211 public String lookup(final LogEvent event, String var) { 212 if (var == null) { 213 return null; 214 } 215 216 final int prefixPos = var.indexOf(PREFIX_SEPARATOR); 217 if (prefixPos >= 0) { 218 final String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US); 219 final String name = var.substring(prefixPos + 1); 220 final StrLookup lookup = strLookupMap.get(prefix); 221 if (lookup instanceof ConfigurationAware) { 222 ((ConfigurationAware) lookup).setConfiguration(configuration); 223 } 224 String value = null; 225 if (lookup != null) { 226 value = event == null ? lookup.lookup(name) : lookup.lookup(event, name); 227 } 228 229 if (value != null) { 230 return value; 231 } 232 var = var.substring(prefixPos + 1); 233 } 234 if (defaultLookup != null) { 235 return event == null ? defaultLookup.lookup(var) : defaultLookup.lookup(event, var); 236 } 237 return null; 238 } 239 240 @Override 241 public String toString() { 242 final StringBuilder sb = new StringBuilder(); 243 for (final String name : strLookupMap.keySet()) { 244 if (sb.length() == 0) { 245 sb.append('{'); 246 } else { 247 sb.append(", "); 248 } 249 250 sb.append(name); 251 } 252 if (sb.length() > 0) { 253 sb.append('}'); 254 } 255 return sb.toString(); 256 } 257 258}