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.spring.boot;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.UnsupportedEncodingException;
23  import java.net.MalformedURLException;
24  import java.net.URISyntaxException;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.net.URLDecoder;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Properties;
32  import javax.net.ssl.HttpsURLConnection;
33  
34  import org.apache.logging.log4j.LogManager;
35  import org.apache.logging.log4j.Logger;
36  import org.apache.logging.log4j.core.LoggerContext;
37  import org.apache.logging.log4j.core.config.AbstractConfiguration;
38  import org.apache.logging.log4j.core.config.Configuration;
39  import org.apache.logging.log4j.core.config.ConfigurationFactory;
40  import org.apache.logging.log4j.core.config.ConfigurationSource;
41  import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
42  import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier;
43  import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
44  import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
45  import org.apache.logging.log4j.core.util.AuthorizationProvider;
46  import org.apache.logging.log4j.core.util.FileUtils;
47  import org.apache.logging.log4j.status.StatusLogger;
48  import org.apache.logging.log4j.util.PropertiesUtil;
49  import org.apache.logging.log4j.util.Strings;
50  import org.springframework.boot.logging.LogFile;
51  import org.springframework.boot.logging.LoggingInitializationContext;
52  import org.springframework.boot.logging.log4j2.Log4J2LoggingSystem;
53  import org.springframework.util.Assert;
54  import org.springframework.util.ClassUtils;
55  import org.springframework.util.ResourceUtils;
56  
57  /**
58   * Override Spring's implementation of the Log4j 2 Logging System to properly support Spring Cloud Config.
59   */
60  public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem {
61      private static final String HTTPS = "https";
62      public static final String ENVIRONMENT_KEY = "SpringEnvironment";
63      private static final String OVERRIDE_PARAM = "override";
64      private static Logger LOGGER = StatusLogger.getLogger();
65  
66      public Log4j2CloudConfigLoggingSystem(ClassLoader loader) {
67          super(loader);
68      }
69  
70      /**
71       * Set the environment into the ExternalContext field so that it can be obtained by SpringLookup when it
72       * is constructed. Spring will replace the ExternalContext field with a String once initialization is
73       * complete.
74       * @param initializationContext The initialization context.
75       * @param configLocation The configuration location.
76       * @param logFile the log file.
77       */
78      @Override
79      public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
80          getLoggerContext().putObjectIfAbsent(ENVIRONMENT_KEY, initializationContext.getEnvironment());
81          super.initialize(initializationContext, configLocation, logFile);
82      }
83  
84      @Override
85      protected String[] getStandardConfigLocations() {
86          String[] locations = super.getStandardConfigLocations();
87          PropertiesUtil props = new PropertiesUtil(new Properties());
88          String location = props.getStringProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
89          if (location != null) {
90              List<String> list = new ArrayList<>(Arrays.asList(super.getStandardConfigLocations()));
91              list.add(location);
92              locations = list.toArray(Strings.EMPTY_ARRAY);
93          }
94          return locations;
95      }
96  
97      @Override
98      protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
99          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 }