Skip to content

Commit 1452b81

Browse files
committed
0.2: added mandatory context supplier
1 parent 0afdaf2 commit 1452b81

File tree

8 files changed

+125
-3
lines changed

8 files changed

+125
-3
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ and you would get:
133133
Error occurred during batch processing [email protected] tenantId=SOME_TENANT_ID errorMessage="ORA-14094: Oracle Hates You" hostname=DEV_SERVER1
134134
...followed by regular full stack trace of the exception...
135135

136+
## Specifying mandatory context key/value pairs
137+
138+
If you have specific key/value pairs that you would like logged automatically with every log entry (host nanem and service name are a good example),
139+
then you just have to specify a mandatory context lambda:
140+
141+
StructLog4J.setMandatoryContextSupplier(() -> new Object[]{
142+
"hostname", InetAddress.getLocalHost().getHostName(),
143+
"serviceName","MyService"});
144+
145+
Now these mandatory key/value pairs will be logged automatically on **every** log entry, without the need to specify them manually.
146+
136147
# License
137148

138149
MIT License.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
org.gradle.daemon=true
22

3-
version = 0.1.0
3+
version = 0.2.0
44

55
group = structlog4j
66

structlog4j-api/src/main/java/com/github/structlog4j/ILogger.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66
* @author Jacek Furmankiewicz
77
*/
88
public interface ILogger {
9+
10+
//logging APIs
911
public void error(String message, Object...params);
1012
public void warn(String message, Object...params);
1113
public void info(String message, Object...params);
1214
public void debug(String message, Object...params);
1315
public void trace(String message, Object...params);
16+
17+
// logging level checks, usually never needed but we add them for completion
18+
public boolean isErrorEnabled();
19+
public boolean isWarnEnabled();
20+
public boolean isInfoEnabled();
21+
public boolean isDebugEnabled();
22+
public boolean isTraceEnabled();
1423
}

structlog4j-api/src/main/java/com/github/structlog4j/IToLog.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*
99
* @author Jacek Furmankiewicz
1010
*/
11+
@FunctionalInterface
1112
public interface IToLog {
1213

1314
/**

structlog4j-api/src/main/java/com/github/structlog4j/SLogger.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.slf4j.LoggerFactory;
77
import org.slf4j.event.Level;
88

9+
import java.util.Optional;
10+
911
/**
1012
* Concrete implementation of the ILogger interface
1113
*
@@ -64,6 +66,31 @@ public void trace(String message, Object... params) {
6466
}
6567
}
6668

69+
@Override
70+
public boolean isErrorEnabled() {
71+
return slfjLogger.isErrorEnabled();
72+
}
73+
74+
@Override
75+
public boolean isWarnEnabled() {
76+
return slfjLogger.isWarnEnabled();
77+
}
78+
79+
@Override
80+
public boolean isInfoEnabled() {
81+
return slfjLogger.isInfoEnabled();
82+
}
83+
84+
@Override
85+
public boolean isDebugEnabled() {
86+
return slfjLogger.isDebugEnabled();
87+
}
88+
89+
@Override
90+
public boolean isTraceEnabled() {
91+
return slfjLogger.isTraceEnabled();
92+
}
93+
6794
private void log(Level level, String message, Object...params) {
6895
try {
6996
// just in case...
@@ -116,6 +143,12 @@ private void log(Level level, String message, Object...params) {
116143
}
117144
}
118145

146+
// add mandatory context, if specified
147+
Optional<IToLog> mandatory = StructLog4J.getMandatoryContextSupplier();
148+
if (mandatory.isPresent()) {
149+
handleIToLog(formatter,bld,mandatory.get());
150+
}
151+
119152
formatter.end(bld);
120153

121154
// actual logging via SLF4J

structlog4j-api/src/main/java/com/github/structlog4j/StructLog4J.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.github.structlog4j.format.KeyValuePairFormatter;
55
import lombok.experimental.UtilityClass;
66

7+
import java.util.Optional;
8+
79
/**
810
* Common settings
911
*
@@ -13,6 +15,7 @@
1315
public class StructLog4J {
1416

1517
private IFormatter formatter = KeyValuePairFormatter.getInstance();
18+
private Optional<IToLog> mandatoryContextSupplier = Optional.empty();
1619

1720
// thread local StringBuilder used for all log concatenation
1821
final ThreadLocal<StringBuilder> BLD = new ThreadLocal<StringBuilder>() {
@@ -26,6 +29,8 @@ public StringBuilder get() {
2629
super.get().setLength(0);
2730
return super.get();
2831
}
32+
33+
FunctionalInterface t;
2934
};
3035

3136
/**
@@ -37,8 +42,29 @@ public void setFormatter(IFormatter formatter) {
3742
StructLog4J.formatter = formatter;
3843
}
3944

40-
// Returns the current encoder - internal only
45+
/**
46+
* Allows to pas in a lambda that will be invoked on every log entry to add additional mandatory
47+
* key/value pairs (e.g. hostname, service name, etc). Saves the hassle of having to specify it explicitly
48+
* on every log invocation
49+
* @param mandatoryContextSupplier Lambda that will executed on every log entry.
50+
*/
51+
public void setMandatoryContextSupplier(IToLog mandatoryContextSupplier) {
52+
StructLog4J.mandatoryContextSupplier = Optional.of(mandatoryContextSupplier);
53+
}
54+
55+
// internal
4156
IFormatter getFormatter() {
4257
return formatter;
4358
}
59+
60+
// internal
61+
Optional<IToLog> getMandatoryContextSupplier() {
62+
return mandatoryContextSupplier;
63+
}
64+
65+
// internal testing support
66+
void clearMandatoryContextSupplier() {
67+
mandatoryContextSupplier = Optional.empty();
68+
}
69+
4470
}

structlog4j-api/src/test/java/com/github/structlog4j/BasicKeyValuePairTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class BasicKeyValuePairTests {
2525

2626
@Before
2727
public void setup() {
28+
StructLog4J.clearMandatoryContextSupplier();
29+
2830
log = (SLogger) SLoggerFactory.getLogger(BasicKeyValuePairTests.class);
2931
entries = ((TestLogger)log.getSlfjLogger()).getEntries();
3032
}
@@ -191,6 +193,7 @@ public void kitchenSinkTest() {
191193

192194
@Test
193195
public void allLevelsTest() {
196+
194197
log.error("Error",iToLog);
195198
log.warn("Warning",iToLog);
196199
log.info("Information",iToLog);
@@ -215,4 +218,42 @@ public void allLevelsTest() {
215218
assertEquals(entries.toString(),"Trace userName=\"Test User\" tenantId=TEST_TENANT",entries.get(4).getMessage());
216219

217220
}
221+
222+
@Test
223+
public void kitchenSinkWithMandatoryContextTest() {
224+
225+
BusinessObjectContext ctx = new BusinessObjectContext("Country","CA");
226+
Throwable rootCause = new RuntimeException("This is the root cause of the error");
227+
Throwable t = new RuntimeException("Major exception",rootCause);
228+
229+
// define mandatory context lambfa
230+
StructLog4J.setMandatoryContextSupplier(() -> new Object[]{"hostname","Titanic","serviceName","MyService"});
231+
232+
// mix and match in different order to ensure it all works
233+
log.error("This is an error",iToLog,ctx,"key1",1L,"key2","Value 2",t);
234+
log.error("This is an error",t,iToLog,ctx,"key1",1L,"key2","Value 2");
235+
log.error("This is an error",iToLog,"key1",1L,t,ctx,"key2","Value 2");
236+
237+
assertEquals(entries.toString(),3,entries.size());
238+
for(LogEntry entry : entries) {
239+
assertEquals(entries.toString(), Level.ERROR,entry.getLevel());
240+
assertTrue(entries.toString(), entry.getError().isPresent());
241+
}
242+
243+
// all messages should have mandatory context fields specified at the end
244+
245+
// first
246+
assertEquals(entries.toString(),"This is an error userName=\"Test User\" tenantId=TEST_TENANT entityName=Country entityId=CA key1=1 key2=\"Value 2\" errorMessage=\"This is the root cause of the error\" hostname=Titanic serviceName=MyService",
247+
entries.get(0).getMessage());
248+
// second
249+
assertEquals(entries.toString(),"This is an error errorMessage=\"This is the root cause of the error\" userName=\"Test User\" tenantId=TEST_TENANT entityName=Country entityId=CA key1=1 key2=\"Value 2\" hostname=Titanic serviceName=MyService",
250+
entries.get(1).getMessage());
251+
// third
252+
assertEquals(entries.toString(),"This is an error userName=\"Test User\" tenantId=TEST_TENANT key1=1 errorMessage=\"This is the root cause of the error\" entityName=Country entityId=CA key2=\"Value 2\" hostname=Titanic serviceName=MyService",
253+
entries.get(2).getMessage());
254+
}
255+
256+
257+
258+
218259
}

structlog4j-api/src/test/java/com/github/structlog4j/ErrorKeyValuePairTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static org.junit.Assert.*;
44

55
import com.github.structlog4j.samples.TestSecurityContext;
6-
import oracle.jrockit.jfr.jdkevents.ThrowableTracer;
76
import org.junit.Before;
87
import org.junit.Test;
98
import org.slf4j.impl.LogEntry;
@@ -21,6 +20,8 @@ public class ErrorKeyValuePairTests {
2120

2221
@Before
2322
public void setup() {
23+
StructLog4J.clearMandatoryContextSupplier();
24+
2425
log = (SLogger) SLoggerFactory.getLogger(BasicKeyValuePairTests.class);
2526
entries = ((TestLogger)log.getSlfjLogger()).getEntries();
2627
}

0 commit comments

Comments
 (0)