Line 0
Link Here
|
|
|
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 |
|
18 |
package org.apache.log4j.spi; |
19 |
|
20 |
import java.io.BufferedReader; |
21 |
import java.io.IOException; |
22 |
import java.io.InputStream; |
23 |
import java.io.InputStreamReader; |
24 |
import java.net.MalformedURLException; |
25 |
import java.net.URL; |
26 |
import java.text.ParseException; |
27 |
import java.util.ArrayList; |
28 |
import java.util.Iterator; |
29 |
import java.util.List; |
30 |
|
31 |
import org.apache.log4j.LogManager; |
32 |
import org.apache.log4j.helpers.Loader; |
33 |
import org.apache.log4j.helpers.LogLog; |
34 |
import org.apache.log4j.helpers.OptionConverter; |
35 |
|
36 |
/** |
37 |
* This class will go through a properties files with references to DOM or |
38 |
* Properties configurator file, and call the appropriate configurator in the |
39 |
* order they are defined. |
40 |
* <p> |
41 |
* This doesn't mean much in theory, so let's explain why this class exists, and |
42 |
* give an example. |
43 |
* <p> |
44 |
* Log4J lacks a way to spread configuration over several files. You have one |
45 |
* configuration file, either log4j.properties or log4j.xml, and that's it. |
46 |
* <p> |
47 |
* In projects where you have several environments to keep track of, you may |
48 |
* want to have some loggers set to DEBUG in the development environment, but |
49 |
* set to WARN on the production environment. However, you usually want to keep |
50 |
* all the appenders and logging infrastructure the same. |
51 |
* <p> |
52 |
* Because Log4J defines appenders and loggers in the same file, you either have |
53 |
* to define several almost identical files with the appropriate changes for the |
54 |
* environment, or you have to have an Ant script that goes through and replaces |
55 |
* tokens for the appropriate environment. |
56 |
* <p> |
57 |
* The file syntax for the configurators is simple. Here's an example |
58 |
* log4j-config.properties file: |
59 |
* |
60 |
* <pre> |
61 |
* # The complicated appenders are rendered in XML. |
62 |
* base-appenders.xml |
63 |
* |
64 |
* # The logging levels can be defined in properties, and can use system properties |
65 |
* # as parameters. |
66 |
* ${my.environment}-loggers.properties |
67 |
* </pre> |
68 |
* |
69 |
* <p> |
70 |
* The class is called LayeredConfigurator because it's meant to work in layers. |
71 |
* The configurators are not reset, so all the settings from the previous |
72 |
* configurator will still apply unless you override them. |
73 |
* <p> |
74 |
* When you define <code>-Dmy.environment=dev</code>, then your development |
75 |
* settings will be loaded. When you define <code>-Dmy.environment=prod</code>, |
76 |
* then your production settings will be loaded. Either way, your binary |
77 |
* distribution is exactly the same, with only the environment specific |
78 |
* properties |
79 |
* <p> |
80 |
* To enable this class, you must start the JVM with the following system |
81 |
* properties: |
82 |
* |
83 |
* <pre> |
84 |
* -Dlog4j.configuratorClass=com.tersesystems.log4j.LayeredConfigurator |
85 |
* -Dlog4j.configuration=log4j-config.properties |
86 |
* </pre> |
87 |
* |
88 |
* <p> |
89 |
* This class has been written using Java 1.3 syntax, lots of internal methods, |
90 |
* and with the internal methods protected, so that it can be subclassed and |
91 |
* modified as needed. |
92 |
* |
93 |
* @author Will Sargent |
94 |
* |
95 |
* @see org.apache.log4j.spi.Configurator |
96 |
* @see org.apache.log4j.spi.ConfiguratorFactory |
97 |
*/ |
98 |
public class LayeredConfigurator implements Configurator { |
99 |
|
100 |
protected static final String PARAMETER_START = "${"; |
101 |
|
102 |
protected static final String PARAMETER_END = "}"; |
103 |
|
104 |
protected static final String COMMENT = "#"; |
105 |
|
106 |
/** |
107 |
* A static version of {@link #doConfigure(URL, LoggerRepository)}. |
108 |
*/ |
109 |
public static void configure(URL url) { |
110 |
new LayeredConfigurator() |
111 |
.doConfigure(url, LogManager.getLoggerRepository()); |
112 |
} |
113 |
|
114 |
/* |
115 |
* (non-Javadoc) |
116 |
* |
117 |
* @see org.apache.log4j.spi.Configurator#doConfigure(java.net.URL, |
118 |
* org.apache.log4j.spi.LoggerRepository) |
119 |
*/ |
120 |
public void doConfigure(URL configURL, LoggerRepository repository) { |
121 |
LogLog.debug("Reading configuration from URL " + configURL); |
122 |
|
123 |
if (configURL == null) { |
124 |
throw new IllegalArgumentException("null configURL"); |
125 |
} |
126 |
|
127 |
if (repository == null) { |
128 |
throw new IllegalArgumentException("null repository"); |
129 |
} |
130 |
|
131 |
InputStream istream = null; |
132 |
try { |
133 |
istream = configURL.openStream(); |
134 |
doConfigure(istream, repository); |
135 |
} catch (Exception e) { |
136 |
LogLog.error("Could not read configuration file from URL [" + configURL |
137 |
+ "].", e); |
138 |
LogLog.error("Ignoring configuration file [" + configURL + "]."); |
139 |
return; |
140 |
} |
141 |
} |
142 |
|
143 |
/** |
144 |
* Converts the input stream into a reader and then passes to the reader |
145 |
* method. |
146 |
* |
147 |
* @param istream |
148 |
* @param repository |
149 |
* @throws IOException |
150 |
* @throws ParseException |
151 |
*/ |
152 |
protected void doConfigure(InputStream istream, LoggerRepository repository) |
153 |
throws IOException, ParseException { |
154 |
if (istream == null) { |
155 |
return; |
156 |
} |
157 |
|
158 |
BufferedReader reader = new BufferedReader(new InputStreamReader(istream)); |
159 |
List configurationReferences = parseConfigurationList(reader); |
160 |
|
161 |
LogLog.debug("list = " + configurationReferences); |
162 |
|
163 |
doConfigure(configurationReferences, repository); |
164 |
} |
165 |
|
166 |
/** |
167 |
* Parses the lines of text from the reader. This method deals with the |
168 |
* opening and closing of IO handles. |
169 |
* |
170 |
* @param reader |
171 |
* the reader to pull in the file. |
172 |
* @return a list containing the parsed strings. |
173 |
*/ |
174 |
protected List parseConfigurationList(BufferedReader reader) |
175 |
throws IOException, ParseException { |
176 |
if (reader == null) { |
177 |
throw new IllegalArgumentException("null reader"); |
178 |
} |
179 |
|
180 |
List configurationList = new ArrayList(); |
181 |
String line = null; |
182 |
try { |
183 |
while ((line = reader.readLine()) != null) { |
184 |
String parsedLine = parseLine(line); |
185 |
if (parsedLine != null) { |
186 |
configurationList.add(parsedLine); |
187 |
} |
188 |
} |
189 |
} finally { |
190 |
reader.close(); |
191 |
} |
192 |
return configurationList; |
193 |
} |
194 |
|
195 |
/** |
196 |
* See if the line starts with #. If it does, then it's a comment. |
197 |
* |
198 |
* We don't assume that anything after # can be a comment, because there's |
199 |
* always the possibility that someone has factored in a URL with a bookmark. |
200 |
* |
201 |
* This method does not do any substitution of variables; we want to make sure |
202 |
* we can parse everything before we move any further. |
203 |
* |
204 |
* @throws ParseException |
205 |
* if the line cannot be parsed. |
206 |
*/ |
207 |
protected String parseLine(String rawLine) throws ParseException { |
208 |
// If nothing, then nothing. |
209 |
if (rawLine == null) { |
210 |
return null; |
211 |
} |
212 |
|
213 |
String line = rawLine; |
214 |
String trimmedLine = line.trim(); |
215 |
|
216 |
// Ignore a completely empty line. |
217 |
if (trimmedLine.length() == 0) // NOPMD |
218 |
{ |
219 |
return null; |
220 |
} |
221 |
|
222 |
// If the line contains nothing but whitespace and then "#" then we |
223 |
// consider it a comment. |
224 |
if (trimmedLine.startsWith(COMMENT)) { |
225 |
return null; |
226 |
} |
227 |
|
228 |
// If the line contains "${" but not "}" then throw an exception. |
229 |
int fromIndex = line.indexOf(PARAMETER_START); |
230 |
if (fromIndex != -1) { |
231 |
int endIndex = line.indexOf(PARAMETER_END, fromIndex + 1); |
232 |
if (endIndex == -1) { |
233 |
throw new ParseException("Cannot parse line: " + line, fromIndex); |
234 |
} |
235 |
|
236 |
// For simplicity's sake, let's not nest. |
237 |
int newStartIndex = line.indexOf(PARAMETER_START, fromIndex + 1); |
238 |
if (newStartIndex != -1 && newStartIndex < endIndex) { |
239 |
throw new ParseException("Cannot parse line: " + line, newStartIndex); |
240 |
} |
241 |
} |
242 |
|
243 |
return rawLine; |
244 |
} |
245 |
|
246 |
/** |
247 |
* Returns a configurator factory. |
248 |
* |
249 |
* @return a new configurator factory. |
250 |
*/ |
251 |
protected ConfiguratorFactory getConfiguratorFactory() { |
252 |
return new ConfiguratorFactory(); |
253 |
} |
254 |
|
255 |
/** |
256 |
* Configures a list of configuration references, substituting parameters as |
257 |
* needed. |
258 |
* |
259 |
* @param configurationReferences |
260 |
* @param repository |
261 |
*/ |
262 |
protected void doConfigure(List configurationReferences, |
263 |
LoggerRepository repository) { |
264 |
// Run through any substitutions of parameters. Parameters are defined |
265 |
// in the syntax ${parameter}. They must resolve to strings. |
266 |
List parsedReferences = substituteListParameters(configurationReferences); |
267 |
|
268 |
ConfiguratorFactory configuratorFactory = getConfiguratorFactory(); |
269 |
|
270 |
// Go through the list of references, using a configurator factory to |
271 |
// pull out the appropriate configurator. |
272 |
for (Iterator iter = parsedReferences.iterator(); iter.hasNext();) { |
273 |
String reference = (String) iter.next(); |
274 |
Configurator c = configuratorFactory.getConfigurator(reference); |
275 |
|
276 |
URL url; |
277 |
try { |
278 |
url = new URL(reference); |
279 |
} catch (MalformedURLException ex) { |
280 |
// so, resource is not a URL: |
281 |
// attempt to get the resource from the class path |
282 |
url = Loader.getResource(reference); |
283 |
} |
284 |
|
285 |
// The internal reference doesn't resolve to anything either... |
286 |
if (url == null) { |
287 |
String msg = "The internal reference " + reference + " does not resolve to a resource"; |
288 |
throw new IllegalArgumentException(msg); |
289 |
} |
290 |
|
291 |
// Tell the configurator to go do its stuff. |
292 |
c.doConfigure(url, repository); |
293 |
} |
294 |
} |
295 |
|
296 |
/** |
297 |
* Goes through a list of configuration references that may contain |
298 |
* ${parameter} in the string, and resolves the parameters. |
299 |
* |
300 |
* @param configurationReferences |
301 |
* a list of configuration references with parameters. |
302 |
* @return a list of configuration files with the parameters replaced by text. |
303 |
*/ |
304 |
protected List substituteListParameters(List configurationReferences) { |
305 |
List resolvedList = new ArrayList(); |
306 |
for (Iterator iter = configurationReferences.iterator(); iter.hasNext();) { |
307 |
String configRef = (String) iter.next(); |
308 |
String resolvedRef = substituteParameters(configRef); |
309 |
resolvedList.add(resolvedRef); |
310 |
} |
311 |
|
312 |
return resolvedList; |
313 |
} |
314 |
|
315 |
/** |
316 |
* Substitute all the parameters in a reference. |
317 |
* |
318 |
* @param configRef |
319 |
* @return a reference with all the parameters replaced by text. |
320 |
*/ |
321 |
protected String substituteParameters(String configRef) { |
322 |
// If I assume Java 1.4 or 1.5, this could be much easier. But let's do |
323 |
// it the old fashioned way. |
324 |
|
325 |
// state can be 1 {dollar), 2 (parsing), or 0 (raw). |
326 |
// We assume that the string cannot be malformed here. |
327 |
int state = 0; |
328 |
StringBuffer line = new StringBuffer(); |
329 |
StringBuffer parameterName = new StringBuffer(); |
330 |
byte[] characters = configRef.getBytes(); |
331 |
for (int i = 0; i < characters.length; i++) { |
332 |
char ch = (char) characters[i]; |
333 |
switch (ch) { |
334 |
case '$': |
335 |
state = 1; |
336 |
break; |
337 |
|
338 |
case '{': |
339 |
if (state == 1) { |
340 |
state = 2; |
341 |
parameterName.setLength(0); // clear the param name. |
342 |
} |
343 |
break; |
344 |
|
345 |
case '}': |
346 |
state = 0; |
347 |
// look up the parameter name, and append to the line. |
348 |
String parameterValue = substituteParameter(parameterName.toString()); |
349 |
line.append(parameterValue); |
350 |
break; |
351 |
|
352 |
default: |
353 |
if (state == 0) // a raw char |
354 |
{ |
355 |
line.append(ch); |
356 |
} else if (state == 1) // just a raw dollar |
357 |
{ |
358 |
state = 0; |
359 |
line.append(ch); |
360 |
} else if (state == 2) // this is a parameter name |
361 |
{ |
362 |
parameterName.append(ch); |
363 |
} |
364 |
} |
365 |
} |
366 |
|
367 |
return line.toString(); |
368 |
} |
369 |
|
370 |
/** |
371 |
* Substitutes a single parameter. A null value will be replaced by the empty |
372 |
* string. |
373 |
* |
374 |
* @param parameterName |
375 |
* the parameter's name. |
376 |
* @return the parameter value. |
377 |
*/ |
378 |
protected String substituteParameter(String parameterName) { |
379 |
LogLog.debug("substituteParameter: found " + parameterName); |
380 |
|
381 |
String parameterValue = OptionConverter |
382 |
.getSystemProperty(parameterName, ""); |
383 |
LogLog.debug("substituteParameter: replacing " + parameterName + " with " |
384 |
+ parameterValue); |
385 |
return parameterValue; |
386 |
} |
387 |
} |