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.spring.boot; 018 019import java.io.File; 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.io.UnsupportedEncodingException; 023import java.net.MalformedURLException; 024import java.net.URISyntaxException; 025import java.net.URL; 026import java.net.URLConnection; 027import java.net.URLDecoder; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Properties; 032import javax.net.ssl.HttpsURLConnection; 033 034import org.apache.logging.log4j.LogManager; 035import org.apache.logging.log4j.Logger; 036import org.apache.logging.log4j.core.LoggerContext; 037import org.apache.logging.log4j.core.config.AbstractConfiguration; 038import org.apache.logging.log4j.core.config.Configuration; 039import org.apache.logging.log4j.core.config.ConfigurationFactory; 040import org.apache.logging.log4j.core.config.ConfigurationSource; 041import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; 042import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier; 043import org.apache.logging.log4j.core.net.ssl.SslConfiguration; 044import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; 045import org.apache.logging.log4j.core.util.AuthorizationProvider; 046import org.apache.logging.log4j.core.util.FileUtils; 047import org.apache.logging.log4j.status.StatusLogger; 048import org.apache.logging.log4j.util.PropertiesUtil; 049import org.apache.logging.log4j.util.Strings; 050import org.springframework.boot.logging.LogFile; 051import org.springframework.boot.logging.LoggingInitializationContext; 052import org.springframework.boot.logging.log4j2.Log4J2LoggingSystem; 053import org.springframework.util.Assert; 054import org.springframework.util.ClassUtils; 055import org.springframework.util.ResourceUtils; 056 057/** 058 * Override Spring's implementation of the Log4j 2 Logging System to properly support Spring Cloud Config. 059 */ 060public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem { 061 private static final String HTTPS = "https"; 062 public static final String ENVIRONMENT_KEY = "SpringEnvironment"; 063 private static final String OVERRIDE_PARAM = "override"; 064 private static Logger LOGGER = StatusLogger.getLogger(); 065 066 public Log4j2CloudConfigLoggingSystem(ClassLoader loader) { 067 super(loader); 068 } 069 070 /** 071 * Set the environment into the ExternalContext field so that it can be obtained by SpringLookup when it 072 * is constructed. Spring will replace the ExternalContext field with a String once initialization is 073 * complete. 074 * @param initializationContext The initialization context. 075 * @param configLocation The configuration location. 076 * @param logFile the log file. 077 */ 078 @Override 079 public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { 080 getLoggerContext().putObjectIfAbsent(ENVIRONMENT_KEY, initializationContext.getEnvironment()); 081 super.initialize(initializationContext, configLocation, logFile); 082 } 083 084 @Override 085 protected String[] getStandardConfigLocations() { 086 String[] locations = super.getStandardConfigLocations(); 087 PropertiesUtil props = new PropertiesUtil(new Properties()); 088 String location = props.getStringProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); 089 if (location != null) { 090 List<String> list = new ArrayList<>(Arrays.asList(super.getStandardConfigLocations())); 091 list.add(location); 092 locations = list.toArray(Strings.EMPTY_ARRAY); 093 } 094 return locations; 095 } 096 097 @Override 098 protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { 099 if (logFile != null) { 100 this.loadConfiguration(this.getBootPackagedConfigFile("log4j2-file.xml"), logFile); 101 } else { 102 this.loadConfiguration(this.getBootPackagedConfigFile("log4j2.xml"), logFile); 103 } 104 } 105 106 private String getBootPackagedConfigFile(String fileName) { 107 String defaultPath = ClassUtils.getPackageName(Log4J2LoggingSystem.class); 108 defaultPath = defaultPath.replace('.', '/'); 109 defaultPath = defaultPath + "/" + fileName; 110 defaultPath = "classpath:" + defaultPath; 111 return defaultPath; 112 } 113 114 @Override 115 protected void loadConfiguration(String location, LogFile logFile) { 116 Assert.notNull(location, "Location must not be null"); 117 try { 118 LoggerContext ctx = getLoggerContext(); 119 String[] locations = parseConfigLocations(location); 120 if (locations.length == 1) { 121 final URL url = ResourceUtils.getURL(location); 122 final ConfigurationSource source = getConfigurationSource(url); 123 if (source != null) { 124 ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source)); 125 } 126 } else { 127 final List<AbstractConfiguration> configs = new ArrayList<>(); 128 for (final String sourceLocation : locations) { 129 final ConfigurationSource source = getConfigurationSource(ResourceUtils.getURL(sourceLocation)); 130 if (source != null) { 131 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, source); 132 if (config instanceof AbstractConfiguration) { 133 configs.add((AbstractConfiguration) config); 134 } else { 135 LOGGER.warn("Configuration at {} cannot be combined in a CompositeConfiguration", sourceLocation); 136 return; 137 } 138 } 139 } 140 if (configs.size() > 1) { 141 ctx.start(new CompositeConfiguration(configs)); 142 } else { 143 ctx.start(configs.get(0)); 144 } 145 } 146 } 147 catch (Exception ex) { 148 throw new IllegalStateException( 149 "Could not initialize Log4J2 logging from " + location, ex); 150 } 151 } 152 153 @Override 154 public void cleanUp() { 155 getLoggerContext().removeObject(ENVIRONMENT_KEY); 156 super.cleanUp(); 157 } 158 159 private String[] parseConfigLocations(String configLocations) { 160 final String[] uris = configLocations.split("\\?"); 161 final List<String> locations = new ArrayList<>(); 162 if (uris.length > 1) { 163 locations.add(uris[0]); 164 try { 165 final URL url = new URL(configLocations); 166 final String[] pairs = url.getQuery().split("&"); 167 for (String pair : pairs) { 168 final int idx = pair.indexOf("="); 169 try { 170 final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; 171 if (key.equalsIgnoreCase(OVERRIDE_PARAM)) { 172 locations.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); 173 } 174 } catch (UnsupportedEncodingException ex) { 175 LOGGER.warn("Bad data in configuration string: {}", pair); 176 } 177 } 178 return locations.toArray(Strings.EMPTY_ARRAY); 179 } catch (MalformedURLException ex) { 180 LOGGER.warn("Unable to parse configuration URL {}", configLocations); 181 } 182 } 183 return new String[] {uris[0]}; 184 } 185 186 private ConfigurationSource getConfigurationSource(URL url) throws IOException, URISyntaxException { 187 URLConnection urlConnection = url.openConnection(); 188 AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); 189 provider.addAuthorization(urlConnection); 190 if (url.getProtocol().equals(HTTPS)) { 191 SslConfiguration sslConfiguration = SslConfigurationFactory.getSslConfiguration(); 192 if (sslConfiguration != null) { 193 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory()); 194 if (!sslConfiguration.isVerifyHostName()) { 195 ((HttpsURLConnection) urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); 196 } 197 } 198 } 199 File file = FileUtils.fileFromUri(url.toURI()); 200 try { 201 if (file != null) { 202 return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI())); 203 } 204 return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified()); 205 } catch (FileNotFoundException ex) { 206 LOGGER.info("Unable to locate file {}, ignoring.", url.toString()); 207 return null; 208 } 209 } 210 211 private LoggerContext getLoggerContext() { 212 return (LoggerContext) LogManager.getContext(false); 213 } 214}