Skip to content

Commit 161b98a

Browse files
maarztctrueden
authored andcommitted
Add LogSource to encapsulate source of a log event
Signed-off-by: Curtis Rueden <ctrueden@wisc.edu>
1 parent c42f433 commit 161b98a

File tree

8 files changed

+261
-21
lines changed

8 files changed

+261
-21
lines changed

src/main/java/org/scijava/log/AbstractLogService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public void alwaysLog(final int level, final Object msg, final Throwable t) {
9292
}
9393

9494
@Override
95-
public String getName() {
96-
return rootLogger.getName();
95+
public LogSource getSource() {
96+
return rootLogger.getSource();
9797
}
9898

9999
@Override
@@ -104,8 +104,8 @@ public int getLevel() {
104104
}
105105

106106
@Override
107-
public Logger subLogger(String nameExtension, int level) {
108-
return rootLogger.subLogger(nameExtension, level);
107+
public Logger subLogger(String name, int level) {
108+
return rootLogger.subLogger(name, level);
109109
}
110110

111111
// -- Listenable methods --
@@ -174,7 +174,8 @@ private Map<String, Integer> setupMapFromProperties(Properties properties,
174174
private class RootLogger extends DefaultLogger {
175175

176176
public RootLogger() {
177-
super(AbstractLogService.this::notifyListeners, "", LogLevel.NONE);
177+
super(AbstractLogService.this::notifyListeners, LogSource.newRoot(),
178+
LogLevel.NONE);
178179
}
179180

180181
@Override

src/main/java/org/scijava/log/DefaultLogger.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,25 @@ public class DefaultLogger implements Logger, LogListener {
4545

4646
private final LogListener destination;
4747

48-
private final String name;
48+
private final LogSource source;
4949

5050
private final int level;
5151

5252
private final List<LogListener> listeners = new CopyOnWriteArrayList<>();
5353

54-
public DefaultLogger(final LogListener destination, final String name,
55-
final int level)
54+
public DefaultLogger(final LogListener destination,
55+
final LogSource source, final int level)
5656
{
5757
this.destination = destination;
58-
this.name = name;
58+
this.source = source;
5959
this.level = level;
6060
}
6161

6262
// -- Logger methods --
6363

6464
@Override
65-
public String getName() {
66-
return name;
65+
public LogSource getSource() {
66+
return source;
6767
}
6868

6969
@Override
@@ -73,11 +73,11 @@ public int getLevel() {
7373

7474
@Override
7575
public void alwaysLog(final int level, final Object msg, final Throwable t) {
76-
messageLogged(new LogMessage(level, msg, t));
76+
messageLogged(new LogMessage(source, level, msg, t));
7777
}
7878

7979
public Logger subLogger(final String name, final int level) {
80-
return new DefaultLogger(this, name, level);
80+
return new DefaultLogger(this, source.subSource(name), level);
8181
}
8282

8383
// -- Listenable methods --

src/main/java/org/scijava/log/LogMessage.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,32 @@
4545
*/
4646
public class LogMessage {
4747

48+
private final LogSource source;
4849
private final int level;
4950
private final String message;
5051
private final Throwable throwable;
5152
private final Date time;
5253

5354
private Collection<Object> attachments;
5455

55-
public LogMessage(int level, Object message,
56+
public LogMessage(LogSource source, int level, Object message,
5657
Throwable throwable)
5758
{
59+
this.source = source;
5860
this.attachments = null;
5961
this.level = level;
6062
this.message = message == null ? null : message.toString();
6163
this.throwable = throwable;
6264
this.time = new Date();
6365
}
6466

65-
public LogMessage(int level, Object msg) {
66-
this(level, msg, null);
67+
public LogMessage(LogSource source, int level, Object msg) {
68+
this(source, level, msg, null);
69+
}
70+
71+
/** Represents the source of the message. */
72+
public LogSource source() {
73+
return source;
6774
}
6875

6976
/**
@@ -121,6 +128,7 @@ public static String format(final LogMessage message) {
121128
final PrintWriter printer = new PrintWriter(sw);
122129
printer.print("[" + message.time() + "] ");
123130
printer.print("[" + LogLevel.prefix(message.level()) + "] ");
131+
printer.print("[" + message.source() + "] ");
124132
printer.println(message.text());
125133
if (message.throwable() != null) {
126134
message.throwable().printStackTrace(printer);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.log;
33+
34+
import java.util.ArrayList;
35+
import java.util.Collections;
36+
import java.util.List;
37+
import java.util.StringJoiner;
38+
import java.util.concurrent.ConcurrentMap;
39+
import java.util.concurrent.ConcurrentSkipListMap;
40+
41+
/**
42+
* Identifies where a {@link LogMessage} came from.
43+
*
44+
* @author Matthias Arzt
45+
*/
46+
public class LogSource {
47+
48+
private final LogSource parent;
49+
50+
private final List<String> path;
51+
52+
private final ConcurrentMap<String, LogSource> children =
53+
new ConcurrentSkipListMap<>();
54+
55+
private String formatted = null;
56+
57+
private LogSource(LogSource parent, String name) {
58+
this.parent = parent;
59+
List<String> parentPath = parent.path();
60+
List<String> list = new ArrayList<>(parentPath.size() + 1);
61+
list.addAll(parentPath);
62+
list.add(name);
63+
this.path = Collections.unmodifiableList(list);
64+
}
65+
66+
private LogSource() {
67+
this.parent = null;
68+
this.path = Collections.emptyList();
69+
}
70+
71+
/** Returns the root log source. This LogSource represents the empty list. */
72+
public static LogSource newRoot() {
73+
return new LogSource();
74+
}
75+
76+
/** Returns the list of strings which is represented by this LogSource. */
77+
public List<String> path() {
78+
return path;
79+
}
80+
81+
/** Returns the last entry in the list of strings. */
82+
public String name() {
83+
if (path.isEmpty()) return "";
84+
return path.get(path.size() - 1);
85+
}
86+
87+
/**
88+
* Returns the LogSource which represents the path of this LogSource extended
89+
* by name.
90+
*/
91+
public LogSource subSource(String name) {
92+
LogSource child = children.get(name);
93+
if (child != null) return child;
94+
child = new LogSource(this, name);
95+
children.putIfAbsent(name, child);
96+
return children.get(name);
97+
}
98+
99+
@Override
100+
public String toString() {
101+
if (formatted != null) return formatted;
102+
StringJoiner joiner = new StringJoiner(":");
103+
path.forEach(s -> joiner.add(s));
104+
formatted = joiner.toString();
105+
return formatted;
106+
}
107+
108+
public boolean isRoot() {
109+
return parent == null;
110+
}
111+
112+
/** Gets the parent of this source, or null if the source is a root. */
113+
public LogSource parent() {
114+
return parent;
115+
}
116+
}

src/main/java/org/scijava/log/Logger.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ default void log(final int level, final Object msg, final Throwable t) {
185185
void alwaysLog(int level, Object msg, Throwable t);
186186

187187
/** Returns the name of this logger. */
188-
String getName();
188+
default String getName() {
189+
return getSource().name();
190+
}
191+
192+
/** Returns the {@link LogSource} associated with this logger. */
193+
LogSource getSource();
189194

190195
/** Returns the log level of this logger. see {@link LogLevel} */
191196
int getLevel();
@@ -201,6 +206,7 @@ default Logger subLogger(String name) {
201206
/**
202207
* Creates a sub logger, that forwards the message it gets to this logger.
203208
*
209+
* @param name The name of the sub logger.
204210
* @param level The log level of the sub logger.
205211
*/
206212
Logger subLogger(String name, int level);

src/test/java/org/scijava/log/DefaultLoggerTest.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@
3131

3232
package org.scijava.log;
3333

34+
import static org.junit.Assert.assertEquals;
3435
import static org.junit.Assert.assertFalse;
3536
import static org.junit.Assert.assertTrue;
3637

38+
import java.util.Arrays;
39+
3740
import org.junit.Before;
3841
import org.junit.Test;
3942

@@ -49,7 +52,7 @@ public class DefaultLoggerTest {
4952

5053
@Before
5154
public void setup() {
52-
logger = new DefaultLogger(message -> {}, "", LogLevel.INFO);
55+
logger = new DefaultLogger(message -> {}, LogSource.newRoot(), LogLevel.INFO);
5356
listener = new TestLogListener();
5457
logger.addListener(listener);
5558
}
@@ -71,7 +74,20 @@ public void testSubLogger() {
7174

7275
sub.error("Hello World!");
7376

74-
assertTrue(listener.hasLogged(m -> m.text().equals("Hello World!")));
77+
assertTrue(listener.hasLogged(m -> m.source().path().contains("sub")));
78+
}
79+
80+
@Test
81+
public void testHierarchicLogger() {
82+
listener.clear();
83+
Logger subA = logger.subLogger("subA");
84+
Logger subB = subA.subLogger("subB");
85+
86+
subB.error("Hello World!");
87+
88+
assertTrue(listener.hasLogged(m -> m.source().equals(subB.getSource())));
89+
assertEquals(Arrays.asList("subA"), subA.getSource().path());
90+
assertEquals(Arrays.asList("subA", "subB"), subB.getSource().path());
7591
}
7692

7793
@Test

src/test/java/org/scijava/log/LogMessageTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class LogMessageTest {
4646
public void testToString() {
4747
String nameOfThisMethod = "testToString";
4848
// setup
49-
LogMessage message = new LogMessage(LogLevel.DEBUG, 42, new NullPointerException());
49+
LogMessage message = new LogMessage(LogSource.newRoot(), LogLevel.DEBUG, 42, new NullPointerException());
5050
// process
5151
String s = message.toString();
5252
//test
@@ -59,7 +59,7 @@ public void testToString() {
5959
@Test
6060
public void testToStringOptionalParameters() {
6161
// setup
62-
LogMessage message = new LogMessage(LogLevel.WARN, null, null);
62+
LogMessage message = new LogMessage(LogSource.newRoot(), LogLevel.WARN, null, null);
6363

6464
// process
6565
// Can it still format the message if optional parameters are null?

0 commit comments

Comments
 (0)