Skip to content

Commit 816da3d

Browse files
committed
fixing SLF4J-515
Signed-off-by: Ceki Gulcu <[email protected]>
1 parent 33e7bf2 commit 816da3d

File tree

3 files changed

+287
-3
lines changed

3 files changed

+287
-3
lines changed

slf4j-simple/src/main/java/org/slf4j/impl/SimpleLogger.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
import java.io.PrintStream;
2828
import java.util.Date;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.concurrent.locks.ReentrantLock;
2931

3032
import org.slf4j.Logger;
3133
import org.slf4j.event.LoggingEvent;
@@ -159,6 +161,9 @@ public class SimpleLogger extends MarkerIgnoringBase {
159161
private static boolean INITIALIZED = false;
160162
static SimpleLoggerConfiguration CONFIG_PARAMS = null;
161163

164+
private static final long MAX_TRY_LOCK_DURATION = 200L;
165+
static ReentrantLock lock = new ReentrantLock();
166+
162167
static void lazyInit() {
163168
if (INITIALIZED) {
164169
return;
@@ -315,9 +320,15 @@ protected String renderLevel(int level) {
315320
void write(StringBuilder buf, Throwable t) {
316321
PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream();
317322

318-
targetStream.println(buf.toString());
319-
writeThrowable(t, targetStream);
320-
targetStream.flush();
323+
try {
324+
lock.tryLock(MAX_TRY_LOCK_DURATION, TimeUnit.MILLISECONDS);
325+
targetStream.println(buf.toString());
326+
writeThrowable(t, targetStream);
327+
targetStream.flush();
328+
} catch (InterruptedException e) {
329+
} finally {
330+
lock.unlock();
331+
}
321332
}
322333

323334
protected void writeThrowable(Throwable t, PrintStream targetStream) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* Copyright (c) 2004-2021 QOS.ch
3+
* All rights reserved.
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining
6+
* a copy of this software and associated documentation files (the
7+
* "Software"), to deal in the Software without restriction, including
8+
* without limitation the rights to use, copy, modify, merge, publish,
9+
* distribute, sublicense, and/or sell copies of the Software, and to
10+
* permit persons to whom the Software is furnished to do so, subject to
11+
* the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be
14+
* included in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
*
24+
*/
25+
package org.slf4j.simple.multiThreadedExecution;
26+
27+
import java.io.PrintStream;
28+
29+
import org.junit.After;
30+
import org.junit.Before;
31+
import org.junit.Test;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
/**
36+
* Tests that output in multi-threaded environments is not mingled.
37+
*
38+
* See also https://jira.qos.ch/browse/SLF4J-515
39+
*/
40+
public class MultithereadedExecutionTest {
41+
42+
private static int THREAD_COUNT = 2;
43+
private Thread[] threads = new Thread[THREAD_COUNT];
44+
45+
private final PrintStream oldOut = System.out;
46+
StateCheckingPrintStream scps = new StateCheckingPrintStream(oldOut);
47+
48+
volatile boolean signal = false;
49+
50+
@Before
51+
public void setup() {
52+
System.setErr(scps);
53+
// System.setProperty(SimpleLogger.LOG_FILE_KEY, "System.err");
54+
// LoggerFactoryFriend.reset();
55+
}
56+
57+
@After
58+
public void tearDown() throws Exception {
59+
// LoggerFactoryFriend.reset();
60+
// System.clearProperty(SimpleLogger.LOG_FILE_KEY);
61+
System.setErr(oldOut);
62+
}
63+
64+
@Test
65+
public void test() throws Throwable {
66+
WithException withException = new WithException();
67+
Other other = new Other();
68+
threads[0] = new Thread(withException);
69+
threads[1] = new Thread(other);
70+
threads[0].start();
71+
threads[1].start();
72+
Thread.sleep(100);
73+
signal = true;
74+
threads[0].join();
75+
threads[1].join();
76+
77+
if (withException.throwable != null) {
78+
throw withException.throwable;
79+
}
80+
81+
if (other.throwable != null) {
82+
throw other.throwable;
83+
}
84+
85+
}
86+
87+
class WithException implements Runnable {
88+
89+
Throwable throwable;
90+
Logger logger = LoggerFactory.getLogger(WithException.class);
91+
92+
@Override
93+
public void run() { // TODO Auto-generated method stub
94+
int i = 0;
95+
96+
while (!signal) {
97+
try {
98+
logger.info("Hello {}", i, new Throwable("i=" + i));
99+
i++;
100+
} catch (Throwable t) {
101+
throwable = t;
102+
MultithereadedExecutionTest.this.signal = true;
103+
return;
104+
}
105+
}
106+
107+
}
108+
}
109+
110+
class Other implements Runnable {
111+
Throwable throwable;
112+
Logger logger = LoggerFactory.getLogger(Other.class);
113+
114+
@Override
115+
public void run() {
116+
int i = 0;
117+
while (!signal) {
118+
try {
119+
logger.info("Other {}", i++);
120+
} catch (Throwable t) {
121+
throwable = t;
122+
MultithereadedExecutionTest.this.signal = true;
123+
return;
124+
}
125+
}
126+
}
127+
}
128+
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Copyright (c) 2004-2021 QOS.ch
3+
* All rights reserved.
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining
6+
* a copy of this software and associated documentation files (the
7+
* "Software"), to deal in the Software without restriction, including
8+
* without limitation the rights to use, copy, modify, merge, publish,
9+
* distribute, sublicense, and/or sell copies of the Software, and to
10+
* permit persons to whom the Software is furnished to do so, subject to
11+
* the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be
14+
* included in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
*
24+
*/
25+
package org.slf4j.simple.multiThreadedExecution;
26+
27+
import java.io.PrintStream;
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.regex.Pattern;
32+
33+
public class StateCheckingPrintStream extends PrintStream {
34+
35+
enum State {
36+
INITIAL, UNKNOWN, HELLO, THROWABLE, AT1, AT2, OTHER;
37+
}
38+
39+
PrintStream other;
40+
41+
List<String> stringList = Collections.synchronizedList(new ArrayList<String>());
42+
43+
State currentState = State.INITIAL;
44+
45+
public StateCheckingPrintStream(PrintStream ps) {
46+
super(ps);
47+
}
48+
49+
public void print(String s) {
50+
}
51+
52+
public void println(String s) {
53+
54+
State next = computeState(s);
55+
//System.out.println(next + " " + s);
56+
switch (currentState) {
57+
case INITIAL:
58+
currentState = next;
59+
break;
60+
61+
case UNKNOWN:
62+
currentState = next;
63+
break;
64+
65+
case OTHER:
66+
if (next == State.UNKNOWN) {
67+
currentState = State.UNKNOWN;
68+
return;
69+
}
70+
71+
if (next != State.OTHER && next != State.HELLO) {
72+
throw badState(s, currentState, next);
73+
}
74+
currentState = next;
75+
break;
76+
77+
case HELLO:
78+
if (next != State.THROWABLE) {
79+
throw badState(s, currentState, next);
80+
}
81+
currentState = next;
82+
break;
83+
case THROWABLE:
84+
if (next != State.AT1) {
85+
throw badState(s, currentState, next);
86+
}
87+
currentState = next;
88+
break;
89+
90+
case AT1:
91+
if (next != State.AT2) {
92+
throw badState(s, currentState, next);
93+
}
94+
currentState = next;
95+
break;
96+
97+
case AT2:
98+
currentState = next;
99+
break;
100+
default:
101+
throw new IllegalStateException("Unreachable code");
102+
}
103+
104+
stringList.add(s);
105+
}
106+
107+
private IllegalStateException badState(String s, State currentState2, State next) {
108+
return new IllegalStateException("Unexpected state " + next + " for current state " + currentState2 + " for " + s);
109+
110+
}
111+
112+
String OTHER_PATTERN_STR = ".*Other \\d{1,5}";
113+
String HELLO_PATTERN_STR = ".*Hello \\d{1,5}";
114+
String THROWABLE_PATTERN_STR = "java.lang.Throwable: i=\\d{1,5}";
115+
String AT1_PATTERN_STR = "\\s*at " + this.getClass().getPackage().getName() + ".*";
116+
String AT2_PATTERN_STR = "\\s*at " + ".*Thread.java.*";
117+
118+
Pattern PATTERN_OTHER = Pattern.compile(OTHER_PATTERN_STR);
119+
Pattern PATTERN_HELLO = Pattern.compile(HELLO_PATTERN_STR);
120+
Pattern PATTERN_THROWABLE = Pattern.compile(THROWABLE_PATTERN_STR);
121+
Pattern PATTERN_AT1 = Pattern.compile(AT1_PATTERN_STR);
122+
Pattern PATTERN_AT2 = Pattern.compile(AT2_PATTERN_STR);
123+
124+
private State computeState(String s) {
125+
126+
if (PATTERN_OTHER.matcher(s).matches()) {
127+
return State.OTHER;
128+
} else if (PATTERN_HELLO.matcher(s).matches()) {
129+
return State.HELLO;
130+
} else if (PATTERN_THROWABLE.matcher(s).matches()) {
131+
return State.THROWABLE;
132+
} else if (PATTERN_AT1.matcher(s).matches()) {
133+
return State.AT1;
134+
} else if (PATTERN_AT2.matcher(s).matches()) {
135+
return State.AT2;
136+
} else {
137+
return State.UNKNOWN;
138+
}
139+
}
140+
141+
public void println(Object o) {
142+
println(o.toString());
143+
}
144+
}

0 commit comments

Comments
 (0)