Skip to content
This repository was archived by the owner on Apr 23, 2024. It is now read-only.

Commit ae50171

Browse files
committed
Fix missing file URLs for spring configs #40
1 parent 2d7dea7 commit ae50171

File tree

5 files changed

+322
-5
lines changed

5 files changed

+322
-5
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ subprojects {
5353
dependencies {
5454
testCompile 'org.mockito:mockito-core:1.9.0'
5555
testCompile 'junit:junit:4.12'
56+
testCompile 'org.hamcrest:hamcrest-library:1.3'
5657
}
5758

5859
task sourcesJar(type: Jar, dependsOn: classes) {

spring/build.gradle

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11

22
description = POM_DESCRIPTION
33
dependencies {
4-
compileOnly 'ch.qos.logback:logback-classic:1.2.3'
5-
compileOnly('org.springframework:spring-context:3.2.2.RELEASE') {
4+
testCompile 'org.springframework:spring-test:5.0.3.RELEASE'
5+
testCompile 'org.slf4j:jcl-over-slf4j:1.7.25'
6+
7+
compile 'ch.qos.logback:logback-classic:1.2.3'
8+
compileOnly('org.springframework:spring-context:5.0.3.RELEASE') {
69
exclude(module: 'commons-logging')
710
}
8-
compileOnly('org.springframework:spring-web:3.2.2.RELEASE') {
11+
compileOnly('org.springframework:spring-web:5.0.3.RELEASE') {
912
exclude(module: 'commons-logging')
1013
}
11-
compileOnly 'javax.servlet:servlet-api:2.5'
14+
compileOnly 'javax.servlet:javax.servlet-api:4.0.0'
1215
}

spring/src/main/java/ch/qos/logback/ext/spring/LogbackConfigurer.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.io.File;
1919
import java.io.FileNotFoundException;
20+
import java.io.IOException;
21+
import java.io.InputStream;
2022
import java.net.URL;
2123

2224
import org.slf4j.impl.StaticLoggerBinder;
@@ -62,13 +64,29 @@ private LogbackConfigurer() {
6264
* (e.g. "classpath:logback.xml"), an absolute file URL
6365
* (e.g. "file:C:/logback.xml), or a plain absolute path in the file system
6466
* (e.g. "C:/logback.xml")
65-
* @throws java.io.FileNotFoundException if the location specifies an invalid file path
67+
* @throws java.io.FileNotFoundException if the location is not found or if the location specifies an invalid file path
6668
* @throws ch.qos.logback.core.joran.spi.JoranException
6769
* Thrown
6870
*/
6971
public static void initLogging(String location) throws FileNotFoundException, JoranException {
7072
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
7173
URL url = ResourceUtils.getURL(resolvedLocation);
74+
InputStream check = null;
75+
try {
76+
check = url.openStream();
77+
} catch (FileNotFoundException e) {
78+
throw e;
79+
} catch (IOException e) {
80+
// Ignore and let the configuration continue in case Logback can handle it successfully
81+
} finally {
82+
if (check != null) {
83+
try {
84+
check.close();
85+
} catch (IOException e) {
86+
// We can probably eat this safely and let Logback trip the error
87+
}
88+
}
89+
}
7290
LoggerContext loggerContext = (LoggerContext)StaticLoggerBinder.getSingleton().getLoggerFactory();
7391

7492
// in the current version logback automatically configures at startup the context, so we have to reset it
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/**
2+
* Copyright (C) 2016 The MITRE Corporation ([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package ch.qos.logback.ext.spring;
17+
18+
import ch.qos.logback.classic.BasicConfigurator;
19+
import ch.qos.logback.classic.LoggerContext;
20+
import ch.qos.logback.classic.spi.ILoggingEvent;
21+
import ch.qos.logback.core.UnsynchronizedAppenderBase;
22+
import ch.qos.logback.core.joran.spi.JoranException;
23+
import ch.qos.logback.ext.spring.web.WebLogbackConfigurer;
24+
import java.io.File;
25+
import java.io.FileOutputStream;
26+
import java.io.InputStream;
27+
import java.net.URL;
28+
import java.util.ArrayList;
29+
import java.util.Random;
30+
import org.hamcrest.BaseMatcher;
31+
import org.hamcrest.Matchers;
32+
import org.junit.After;
33+
import org.junit.Before;
34+
import org.junit.ClassRule;
35+
import org.junit.Rule;
36+
import org.junit.Test;
37+
import org.junit.rules.TemporaryFolder;
38+
import org.junit.rules.TestRule;
39+
import org.junit.rules.TestWatcher;
40+
import org.junit.runner.Description;
41+
import org.slf4j.Logger;
42+
import org.slf4j.LoggerFactory;
43+
import org.slf4j.impl.StaticLoggerBinder;
44+
import org.springframework.mock.web.MockServletContext;
45+
import static org.junit.Assert.*;
46+
47+
/**
48+
* Tests the behavior of the logging configuration if the configuration files are missing.
49+
* @author John Gibson
50+
*/
51+
public class MissingConfigurationTest {
52+
/**
53+
* Parameter specifying the location of the logback config file
54+
*/
55+
private static final String CONFIG_LOCATION_PARAM = "logbackConfigLocation";
56+
57+
private static Random rng = new Random();
58+
59+
@ClassRule
60+
public static final TemporaryFolder PLAYGROUND = new TemporaryFolder();
61+
62+
private LogSensitiveMockServletContext context = new LogSensitiveMockServletContext();
63+
64+
@Before
65+
public void clearFakeLogs() {
66+
FakeAppender.logs.clear();
67+
}
68+
69+
/**
70+
* Aside from printing the test name this also ensures that the logging system is in the known default state.
71+
*/
72+
@Rule
73+
public TestRule ensureLoggingInitialized = new TestWatcher() {
74+
@Override
75+
protected void starting(Description description) {
76+
LoggerContext loggerContext = doCleanup();
77+
new BasicConfigurator().configure(loggerContext);
78+
Logger log = LoggerFactory.getLogger(MissingConfigurationTest.class);
79+
log.info("Starting test: " + description.getMethodName());
80+
}
81+
};
82+
83+
private LoggerContext doCleanup() {
84+
LoggerContext loggerContext = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory();
85+
loggerContext.reset();
86+
return loggerContext;
87+
}
88+
89+
@After
90+
public void cleanupLogging() {
91+
doCleanup();
92+
}
93+
94+
@Test(timeout = 3000L)
95+
public void testMissingFileConfiguration() throws Exception {
96+
File missingConfig = null;
97+
do {
98+
missingConfig = new File("missingConfigTest" + rng.nextInt() + ".xml");
99+
} while (missingConfig.exists());
100+
101+
context.addInitParameter(CONFIG_LOCATION_PARAM, missingConfig.toURI().toURL().toString());
102+
103+
WebLogbackConfigurer.initLogging(context);
104+
assertThat("Missing configuration file wasn't seen.", context.messages,
105+
Matchers.hasItem((Matchers.containsString(missingConfig.getName()))));
106+
}
107+
108+
@Test
109+
public void testMissingClasspathConfiguration() throws Exception {
110+
final String fakeEntry = "classpath:ch/qos/logback/ext/spring/does/not/exist/" + rng.nextInt() + ".xml";
111+
context.addInitParameter(CONFIG_LOCATION_PARAM, fakeEntry);
112+
WebLogbackConfigurer.initLogging(context);
113+
assertThat("Missing configuration classpath wasn't seen.", context.messages,
114+
Matchers.hasItem((Matchers.containsString(fakeEntry))));
115+
}
116+
117+
@Test
118+
public void testInvalidConfiguration() throws Exception {
119+
File empty = PLAYGROUND.newFile("empty.xml");
120+
context.addInitParameter(CONFIG_LOCATION_PARAM, empty.toURI().toURL().toString());
121+
try {
122+
WebLogbackConfigurer.initLogging(context);
123+
fail("Error expected.");
124+
} catch (RuntimeException e) {
125+
assertEquals("Unexpected error while configuring logback", e.getMessage());
126+
assertTrue("Expected a RuntimeException wrapping a JoranException, but it was a different cause: " + e.getCause(), e.getCause() instanceof JoranException);
127+
}
128+
}
129+
130+
@Test(timeout = 3000L)
131+
public void testMissingFollowedByNormalConfiguration() throws Exception {
132+
File missingConfig = null;
133+
do {
134+
missingConfig = new File("missingConfigTest" + rng.nextInt() + ".xml");
135+
} while (missingConfig.exists());
136+
137+
File fileConfig = PLAYGROUND.newFile("fakeLogger.xml");
138+
URL u = getClass().getResource("fakeLogger.xml");
139+
InputStream is = null;
140+
FileOutputStream os = null;
141+
try {
142+
is = u.openStream();
143+
os = new FileOutputStream(fileConfig);
144+
byte[] buff = new byte[2048];
145+
for (int read = is.read(buff); read != -1; read = is.read(buff)) {
146+
os.write(buff, 0, read);
147+
}
148+
} finally {
149+
if (is != null) {
150+
is.close();
151+
}
152+
if (os != null) {
153+
os.close();
154+
}
155+
}
156+
157+
context.addInitParameter(CONFIG_LOCATION_PARAM, missingConfig.toURI().toURL().toString() + "," +
158+
fileConfig.toURI().toURL().toString());
159+
WebLogbackConfigurer.initLogging(context);
160+
assertThat("Missing configuration file wasn't seen.", context.messages,
161+
Matchers.hasItem((Matchers.containsString(missingConfig.getName()))));
162+
assertThat("Actual configuration file wasn't seen.", context.messages,
163+
Matchers.hasItem((Matchers.containsString(fileConfig.toURI().toURL().toString()))));
164+
165+
Logger log = LoggerFactory.getLogger(MissingConfigurationTest.class);
166+
String key = "missingConfig" + rng.nextInt();
167+
log.error(key);
168+
ArrayList<ILoggingEvent> logs;
169+
synchronized (FakeAppender.class) {
170+
logs = new ArrayList<ILoggingEvent>(FakeAppender.logs);
171+
}
172+
assertThat(logs, Matchers.hasItem(new LoggingEventMatcher(key)));
173+
}
174+
175+
@Test(timeout = 3000L)
176+
public void testMissingFollowedByNormalClasspathConfiguration() throws Exception {
177+
File missingConfig = null;
178+
do {
179+
missingConfig = new File("missingConfigTest" + rng.nextInt() + ".xml");
180+
} while (missingConfig.exists());
181+
182+
String cpConfig = "classpath:ch/qos/logback/ext/spring/fakeLogger.xml";
183+
context.addInitParameter(CONFIG_LOCATION_PARAM, missingConfig.toURI().toURL().toString() + "," +
184+
cpConfig);
185+
WebLogbackConfigurer.initLogging(context);
186+
assertThat("Missing configuration file wasn't seen.", context.messages,
187+
Matchers.hasItem((Matchers.containsString(missingConfig.getName()))));
188+
assertThat("Actual configuration file wasn't seen.", context.messages,
189+
Matchers.hasItem((Matchers.containsString(cpConfig))));
190+
191+
Logger log = LoggerFactory.getLogger(MissingConfigurationTest.class);
192+
String key = "missingConfig" + rng.nextInt();
193+
log.error(key);
194+
ArrayList<ILoggingEvent> logs;
195+
synchronized (FakeAppender.class) {
196+
logs = new ArrayList<ILoggingEvent>(FakeAppender.logs);
197+
}
198+
assertThat(logs, Matchers.hasItem(new LoggingEventMatcher(key)));
199+
}
200+
201+
public static class FakeAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
202+
private static final ArrayList<ILoggingEvent> logs = new ArrayList<ILoggingEvent>();
203+
204+
@Override
205+
protected void append(ILoggingEvent eventObject) {
206+
synchronized(FakeAppender.class) {
207+
logs.add(eventObject);
208+
}
209+
}
210+
}
211+
212+
private static class LoggingEventMatcher extends BaseMatcher<ILoggingEvent> {
213+
private final String message;
214+
215+
public LoggingEventMatcher(String message) {
216+
this.message = message;
217+
}
218+
219+
@Override
220+
public boolean matches(Object item) {
221+
if (!(item instanceof ILoggingEvent)) {
222+
return false;
223+
}
224+
return message.equals(((ILoggingEvent) item).getMessage());
225+
}
226+
227+
@Override
228+
public void describeTo(org.hamcrest.Description description) {
229+
description.appendText("Logging event with message").appendValue(message);
230+
}
231+
}
232+
233+
/**
234+
* Spring's MockServletContext class uses a logger to write messages. This interferes with testing the actual
235+
* logging system. In addition it makes it difficult to capture messages that we should expect to be logged to the
236+
* servlet.
237+
*/
238+
private static class LogSensitiveMockServletContext extends MockServletContext {
239+
public final ArrayList<String> messages = new ArrayList<String>();
240+
private static final String PREFIX = "MockServlet: ";
241+
242+
@Override
243+
public void log(String message) {
244+
System.out.println(PREFIX + message);
245+
messages.add(message);
246+
}
247+
248+
@Override
249+
public void log(Exception ex, String message) {
250+
log(message, ex);
251+
}
252+
253+
@Override
254+
public void log(String message, Throwable ex) {
255+
System.err.println(PREFIX + message);
256+
messages.add(message);
257+
System.err.println(PREFIX + ex.toString());
258+
messages.add(ex.toString());
259+
ex.printStackTrace();
260+
}
261+
}
262+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2012 Ceki Gulcu, Les Hazlewood, et. al.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ 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+
<configuration debug="true">
18+
19+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
20+
<encoder>
21+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %throwable{5}%n</pattern>
22+
</encoder>
23+
</appender>
24+
25+
<appender name="springTest" class="ch.qos.logback.ext.spring.MissingConfigurationTest$FakeAppender"/>
26+
<root level="WARN">
27+
<appender-ref ref="springTest"/>
28+
</root>
29+
30+
<root level="warn">
31+
<appender-ref ref="STDOUT"/>
32+
</root>
33+
</configuration>

0 commit comments

Comments
 (0)