diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f5843e..0b914a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,4 +7,7 @@ jobs: build: uses: ./.github/workflows/run-with-maven.yml with: - COMMAND: mvn --batch-mode -Dmaven.compiler.showDeprecation=true -Dmaven.compiler.showWarnings=true clean verify + COMMAND: > + mvn --batch-mode -Dmaven.compiler.showDeprecation=true -Dmaven.compiler.showWarnings=true -Dproject.version=0.0.0-SNAPSHOT clean install + && cd project-without-logstash + && mvn --batch-mode -Dlog-capture.version=0.0.0-SNAPSHOT clean verify diff --git a/README.md b/README.md index a6b6314..d66f95f 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,26 @@ log.info("did something"); logCapture.info("did something", logger("com.acme.foo")); ``` +#### Key-Value (from Logstash) + +**Note that** this will only work if logstash-logback-encoder is in your classpath - log-capture does not depend on it, so you need to add it manually if you intend to use it. + +```java +import static de.dm.infrastructure.logcapture.ExpectedKeyValue.keyValue; + +... + +log.info("hello", + StructuredArguments.keyValue("myString", "hello"), + StructuredArguments.keyValue("myNumber", 42) +); + +logCapture.assertLogged(info("hello", + keyValue("myString", "hello"), + keyValue("myNumber", 42)) +); +``` + ### Examples #### Unit Test Example: @@ -306,6 +326,12 @@ And with MDC logging context ## Changes +### 3.5.0 + +* Added new Log Event Matcher: `ExpectedKeyValue.keyValue(...)` to assert `StructuredArguments.keyValue(...)` from Logstash +* Improved readability for assertion errors when using `assertNotLogged(...)` +* Updated dependencies + ### 3.4.1 * Improved Javadoc for deprecated methods diff --git a/pom.xml b/pom.xml index 60a64f2..8dabf98 100644 --- a/pom.xml +++ b/pom.xml @@ -29,15 +29,17 @@ - 3.4.1-SNAPSHOT + 3.5.0-SNAPSHOT 1.8 - 1.18.20 - 1.2.5 - 5.7.2 + 1.18.22 + 1.2.11 + 7.0.1 + 5.8.2 UTF-8 - 3.20.1 + 0.23.1 + 3.22.0 8.45.1 0.8.7 2.22.2 @@ -47,16 +49,11 @@ 3.9.0 2.5.3 3.0.1 + 4.4.0 1.6.8 - - org.projectlombok - lombok - ${lombok.version} - provided - ch.qos.logback logback-classic @@ -72,12 +69,34 @@ junit-jupiter-engine ${junit.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + net.logstash.logback + logstash-logback-encoder + ${logstash-logback-encoder.version} + provided + + + org.assertj assertj-core ${assertj-core.version} test + + com.tngtech.archunit + archunit-junit5 + ${archunit.version} + test + diff --git a/project-without-logstash/.gitignore b/project-without-logstash/.gitignore new file mode 100644 index 0000000..84d100c --- /dev/null +++ b/project-without-logstash/.gitignore @@ -0,0 +1,9 @@ +/target/ + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ diff --git a/project-without-logstash/pom.xml b/project-without-logstash/pom.xml new file mode 100644 index 0000000..4813890 --- /dev/null +++ b/project-without-logstash/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + de.acme + test-project-without-logstash + 0.0.1-SNAPSHOT + + + + MIT + https://opensource.org/licenses/MIT + repo + + + + + 1.8 + UTF-8 + 3.4.1-SNAPSHOT + + 1.18.22 + 1.2.11 + 5.8.2 + 3.22.0 + + 2.22.2 + 3.9.0 + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + de.dm.infrastructure + log-capture + ${log-capture.version} + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${encoding} + true + true + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + diff --git a/project-without-logstash/src/test/java/com/acme/LogCaptureTest.java b/project-without-logstash/src/test/java/com/acme/LogCaptureTest.java new file mode 100644 index 0000000..3416887 --- /dev/null +++ b/project-without-logstash/src/test/java/com/acme/LogCaptureTest.java @@ -0,0 +1,34 @@ +package com.acme; + +import de.dm.infrastructure.logcapture.LogCapture; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static de.dm.infrastructure.logcapture.ExpectedKeyValue.keyValue; +import static de.dm.infrastructure.logcapture.LogExpectation.info; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Slf4j +public class LogCaptureTest { + @RegisterExtension + LogCapture logCapture = LogCapture.forCurrentPackage(); + + @Test + void assertionWorksDespiteLogstashNotBeingInTheClasspath() { + log.info("hello"); + logCapture.assertLogged(info("hello")); + } + + @Test + void errorMessageForKeyValueAssertion() { + log.info("hello"); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> + logCapture.assertLogged(info("hello", keyValue("key", "value")))); + + assertThat(thrown).hasMessage("keyValue cannot be used for log assertions if logstash-logback-encoder " + + "that provides StructuredArguments.keyValue(...) is not in the classpath."); + } +} diff --git a/src/main/java/de/dm/infrastructure/logcapture/CapturingAppender.java b/src/main/java/de/dm/infrastructure/logcapture/CapturingAppender.java index 84daf89..2c99ea4 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/CapturingAppender.java +++ b/src/main/java/de/dm/infrastructure/logcapture/CapturingAppender.java @@ -43,6 +43,7 @@ public synchronized void doAppend(ILoggingEvent loggingEvent) { .mdcData(loggingEvent.getMDCPropertyMap()) .loggedException(getLoggedException(loggingEvent.getThrowableProxy())) .marker(loggingEvent.getMarker()) + .argumentArray(loggingEvent.getArgumentArray()) .build()); } } diff --git a/src/main/java/de/dm/infrastructure/logcapture/ExpectedException.java b/src/main/java/de/dm/infrastructure/logcapture/ExpectedException.java index 8ab23dc..03f1f69 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/ExpectedException.java +++ b/src/main/java/de/dm/infrastructure/logcapture/ExpectedException.java @@ -51,8 +51,8 @@ public String getNonMatchingErrorMessage(LoggedEvent loggedEvent) { } @Override - public String getMatchingErrorMessage() { - return format("not expected exception was found: %s", this); + public String getMatcherDetailDescription() { + return format("Exception: %s", this); } private static String loggedExceptionToString(Optional optionalException) { @@ -70,7 +70,7 @@ private static String loggedExceptionToString(Optional argument instanceof SingleFieldAppendingMarker ? Stream.of((SingleFieldAppendingMarker) argument) : Stream.empty()) + .anyMatch(marker -> expectedValue.equals(marker.getFieldValue()) && marker.getFieldName().equals(expectedKey)); + } + + static String getNonMatchingErrorMessage(LoggedEvent loggedEvent, String expectedKey, String expectedValue) { + String actualKeyValue = Arrays.stream(loggedEvent.getArgumentArray()) + .flatMap(argument -> argument instanceof SingleFieldAppendingMarker ? Stream.of((SingleFieldAppendingMarker) argument) : Stream.empty()) + .map(marker -> format(" key: \"%s\", value: \"%s\"", marker.getFieldName(), marker.getFieldValue())) + .collect(Collectors.joining(lineSeparator())); + + return format(" expected key-value content: key: \"%s\", value: \"%s\"", expectedKey, expectedValue) + + lineSeparator() + + (actualKeyValue.isEmpty() ? " but no key-value content was found" : " actual key-value content:" + lineSeparator() + actualKeyValue); + + } + +} diff --git a/src/main/java/de/dm/infrastructure/logcapture/ExpectedLoggerName.java b/src/main/java/de/dm/infrastructure/logcapture/ExpectedLoggerName.java index 7ad92f1..3b860f3 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/ExpectedLoggerName.java +++ b/src/main/java/de/dm/infrastructure/logcapture/ExpectedLoggerName.java @@ -30,13 +30,13 @@ public String getNonMatchingErrorMessage(LoggedEvent loggedEvent) { } @Override - public String getMatcherDescription() { + public String getMatcherTypeDescription() { return "logger name"; } @Override - public String getMatchingErrorMessage() { - return format("not expected logger name (regex) was found: \"%s\"", inputRegex); + public String getMatcherDetailDescription() { + return format("logger name (regex): \"%s\"", inputRegex); } /** diff --git a/src/main/java/de/dm/infrastructure/logcapture/ExpectedMarker.java b/src/main/java/de/dm/infrastructure/logcapture/ExpectedMarker.java index 6e5a7e6..f032fce 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/ExpectedMarker.java +++ b/src/main/java/de/dm/infrastructure/logcapture/ExpectedMarker.java @@ -29,13 +29,13 @@ public String getNonMatchingErrorMessage(LoggedEvent loggedEvent) { } @Override - public String getMatcherDescription() { + public String getMatcherTypeDescription() { return "marker name"; } @Override - public String getMatchingErrorMessage() { - return format("not expected marker name: \"%s\" was found", expectedName); + public String getMatcherDetailDescription() { + return format("marker name: \"%s\"", expectedName); } /** diff --git a/src/main/java/de/dm/infrastructure/logcapture/ExpectedMdcEntry.java b/src/main/java/de/dm/infrastructure/logcapture/ExpectedMdcEntry.java index ce8ef8a..14b86ce 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/ExpectedMdcEntry.java +++ b/src/main/java/de/dm/infrastructure/logcapture/ExpectedMdcEntry.java @@ -70,14 +70,13 @@ public String getNonMatchingErrorMessage(LoggedEvent loggedEvent) { } @Override - public String getMatcherDescription() { + public String getMatcherTypeDescription() { return "MDC value"; } @Override - public String getMatchingErrorMessage() { - - return format("not expected MDCValue with key was found: \"%s\"", key); + public String getMatcherDetailDescription() { + return format("MDCValue with key: \"%s\"", key); } /** diff --git a/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java b/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java index 92211dd..e9f79a0 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java +++ b/src/main/java/de/dm/infrastructure/logcapture/LogAsserter.java @@ -49,8 +49,8 @@ public NothingElseLoggedAsserter assertLoggedInAnyOrder(LogExpectation... logExp "Imprecise matching: Two log expectations have matched the same message. " + "Use more precise matching or in-order matching. " + "(First match: %s | Second match: %s", - getLevelAndRegexExpectation(previousMatch.level, previousMatch.regex, previousMatch.logEventMatchers), - getLevelAndRegexExpectation(assertion.level, assertion.regex, assertion.logEventMatchers))); + getDescriptionForUnwantedLogMessage(previousMatch.level, previousMatch.regex, previousMatch.logEventMatchers), + getDescriptionForUnwantedLogMessage(assertion.level, assertion.regex, assertion.logEventMatchers))); } matches.put(lastCapturedLogEvent.lastAssertedLogMessageIndex, assertion); } @@ -180,7 +180,7 @@ Integer assertCapturedNext(Optional level, Optional regex, int st if (eventMatchingWithoutAdditionalMatchers != null) { throwAssertionForPartiallyMatchingLoggedEvent(level, regex, eventMatchingWithoutAdditionalMatchers, logEventMatchers); } - throw new AssertionError(format("Expected log message has not occurred: %s", getLevelAndRegexExpectation(level, regex, logEventMatchers))); + throw new AssertionError(format("Expected log message has not occurred: %s", getDescriptionForExpectedMessage(level, regex))); } void assertNotCaptured(Optional level, Optional regex, List logEventMatchers) { @@ -189,7 +189,7 @@ void assertNotCaptured(Optional level, Optional regex, List level, Optional regex) { + private static String getDescriptionForExpectedMessage(Optional level, Optional regex) { if (!level.isPresent() && !regex.isPresent()) { return ""; @@ -241,10 +241,11 @@ private static String getLevelAndRegexExpectation(Optional level, Optiona (regex.map(s -> "Regex: \"" + s + "\"").orElse("")); } - private static String getLevelAndRegexExpectation(Optional level, Optional regex, List matchers) { + private static String getDescriptionForUnwantedLogMessage(Optional level, Optional regex, List matchers) { String matchersText = ""; if (!matchers.isEmpty()) { - matchersText = ", with matchers: " + matchers.stream().map(LogEventMatcher::getMatchingErrorMessage).collect(Collectors.joining(", ")); + matchersText = ", with matchers:" + lineSeparator() + " " + matchers.stream().map(LogEventMatcher::getMatcherDetailDescription) + .collect(Collectors.joining(lineSeparator() + " ")); } if (!level.isPresent() && !regex.isPresent()) { return "" + matchersText; diff --git a/src/main/java/de/dm/infrastructure/logcapture/LogEventMatcher.java b/src/main/java/de/dm/infrastructure/logcapture/LogEventMatcher.java index fb17635..8e842f8 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/LogEventMatcher.java +++ b/src/main/java/de/dm/infrastructure/logcapture/LogEventMatcher.java @@ -27,14 +27,14 @@ public interface LogEventMatcher { * * @return matched aspect */ - String getMatcherDescription(); + String getMatcherTypeDescription(); /** - * returns an error message describing why a logged event does match, even of it shouldn't. + * returns an error message describing the concrete matcher, including as much of its expectations as possible * * @return matched aspect */ - String getMatchingErrorMessage(); + String getMatcherDetailDescription(); } diff --git a/src/main/java/de/dm/infrastructure/logcapture/LoggedEvent.java b/src/main/java/de/dm/infrastructure/logcapture/LoggedEvent.java index 6510870..662688b 100644 --- a/src/main/java/de/dm/infrastructure/logcapture/LoggedEvent.java +++ b/src/main/java/de/dm/infrastructure/logcapture/LoggedEvent.java @@ -21,6 +21,7 @@ class LoggedEvent { private final Optional loggedException; private final String loggerName; private final Marker marker; + private final Object[] argumentArray; @AllArgsConstructor(access = PRIVATE) @Builder diff --git a/src/test/java/com/example/app/LogstashKeyValueTest.java b/src/test/java/com/example/app/LogstashKeyValueTest.java new file mode 100644 index 0000000..d48a480 --- /dev/null +++ b/src/test/java/com/example/app/LogstashKeyValueTest.java @@ -0,0 +1,130 @@ +package com.example.app; + +import de.dm.infrastructure.logcapture.LogCapture; +import lombok.extern.slf4j.Slf4j; +import net.logstash.logback.argument.StructuredArguments; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static de.dm.infrastructure.logcapture.ExpectedKeyValue.keyValue; +import static de.dm.infrastructure.logcapture.LogExpectation.info; +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SuppressWarnings("java:S5778") //this rule does not increase the clarity of these tests +@Slf4j +class LogstashKeyValueTest { + @RegisterExtension + LogCapture logCapture = LogCapture.forCurrentPackage(); + + @Test + void worksWithString() { + log.info("hello", StructuredArguments.keyValue("myKey", "myValue")); + + logCapture.assertLogged(info("hello", keyValue("myKey", "myValue"))); + } + + @Test + void failsWithString() { + log.info("hello", StructuredArguments.keyValue("myKey", "actualValue")); + + AssertionError assertionError = assertThrows(AssertionError.class, () -> + logCapture.assertLogged(info("hello", keyValue("myKey", "expectedValue")))); + + assertThat(assertionError).hasMessageFindingMatch( + "Expected log message has occurred, but never with the expected key-value content: Level: INFO, Regex: \"hello\"" + ".*" + + " expected key-value content: key: \"myKey\", value: \"expectedValue\"" + ".*" + + " actual key-value content:" + ".*" + + " key: \"myKey\", value: \"actualValue\""); + } + + @Test + void worksWithInteger() { + log.info("hello", StructuredArguments.keyValue("myKey", 100000)); + + logCapture.assertLogged(info("hello", keyValue("myKey", 100000))); + } + + @Test + void failsWithInteger() { + log.info("hello", StructuredArguments.keyValue("myKey", 100001)); + + AssertionError assertionError = assertThrows(AssertionError.class, () -> + logCapture.assertLogged(info("hello", keyValue("myKey", 100000)))); + + assertThat(assertionError).hasMessageFindingMatch( + "Expected log message has occurred, but never with the expected key-value content: Level: INFO, Regex: \"hello\"" + ".*" + + " expected key-value content: key: \"myKey\", value: \"100000\"" + ".*" + + " actual key-value content:" + ".*" + + " key: \"myKey\", value: \"100001\""); + } + + @Test + void failsWithIntegerThatIsNoInteger() { + log.info("hello", StructuredArguments.keyValue("myKey", "not an int")); + + AssertionError assertionError = assertThrows(AssertionError.class, () -> + logCapture.assertLogged(info("hello", keyValue("myKey", 100000)))); + + assertThat(assertionError).hasMessageFindingMatch( + "Expected log message has occurred, but never with the expected key-value content: Level: INFO, Regex: \"hello\"" + ".*" + + " expected key-value content: key: \"myKey\", value: \"100000\"" + ".*" + + " actual key-value content:" + ".*" + + " key: \"myKey\", value: \"not an int\""); + } + + + @Test + void worksWithLong() { + log.info("hello", StructuredArguments.keyValue("myKey", 1000000000000L)); + + logCapture.assertLogged(info("hello", keyValue("myKey", 1000000000000L))); + } + + @Test + void failsWithLong() { + log.info("hello", StructuredArguments.keyValue("myKey", 1000000000001L)); + + AssertionError assertionError = assertThrows(AssertionError.class, () -> + logCapture.assertLogged(info("hello", keyValue("myKey", 1000000000000L)))); + + assertThat(assertionError).hasMessageFindingMatch( + "Expected log message has occurred, but never with the expected key-value content: Level: INFO, Regex: \"hello\"" + ".*" + + " expected key-value content: key: \"myKey\", value: \"1000000000000\"" + ".*" + + " actual key-value content:" + ".*" + + " key: \"myKey\", value: \"1000000000001\""); + } + + @Test + void failsWithLongThatIsNoLong() { + log.info("hello", StructuredArguments.keyValue("myKey", "not a long")); + + AssertionError assertionError = assertThrows(AssertionError.class, () -> + logCapture.assertLogged(info("hello", keyValue("myKey", 1000000000000L)))); + + assertThat(assertionError).hasMessageFindingMatch( + "Expected log message has occurred, but never with the expected key-value content: Level: INFO, Regex: \"hello\"" + ".*" + + " expected key-value content: key: \"myKey\", value: \"1000000000000\"" + ".*" + + " actual key-value content:" + ".*" + + " key: \"myKey\", value: \"not a long\""); + } + + @Test + void assertNotLoggedSucceeds() { + log.info("info", StructuredArguments.keyValue("key", "actualValue")); + + logCapture.assertNotLogged(info("info", keyValue("key", "forbiddenValue"))); + } + + @Test + void assertNotLoggedFailsWithProperMessage() { + log.info("info", StructuredArguments.keyValue("key", "forbiddenValue")); + + AssertionError actual = assertThrows(AssertionError.class, () -> + logCapture.assertNotLogged(info("info", keyValue("key", "forbiddenValue")))); + + assertThat(actual).hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"info\", with matchers:" + + lineSeparator() + " keyValue content with key: \"key\" and value: \"forbiddenValue\""); + } +} diff --git a/src/test/java/com/example/app/ReadableApiTest.java b/src/test/java/com/example/app/ReadableApiTest.java index a9818a4..ea52e63 100644 --- a/src/test/java/com/example/app/ReadableApiTest.java +++ b/src/test/java/com/example/app/ReadableApiTest.java @@ -179,16 +179,16 @@ void assertNotLoggedFails() { log.info("testlogmessage"); final AssertionError exceptionAny = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(any())); - assertThat(exceptionAny).hasMessage("Expected log message should not occur: "); + assertThat(exceptionAny).hasMessage("Found a log message that should not be logged: "); final AssertionError exceptionWithLevel = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(info())); - assertThat(exceptionWithLevel).hasMessage("Expected log message should not occur: Level: INFO"); + assertThat(exceptionWithLevel).hasMessage("Found a log message that should not be logged: Level: INFO"); final AssertionError exceptionWithRegex = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(any("testlogmessage"))); - assertThat(exceptionWithRegex).hasMessage("Expected log message should not occur: Regex: \"testlogmessage\""); + assertThat(exceptionWithRegex).hasMessage("Found a log message that should not be logged: Regex: \"testlogmessage\""); final AssertionError exceptionWithRegexAndLevel = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(info("testlogmessage"))); - assertThat(exceptionWithRegexAndLevel).hasMessage("Expected log message should not occur: Level: INFO, Regex: \"testlogmessage\""); + assertThat(exceptionWithRegexAndLevel).hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"testlogmessage\""); } } @@ -277,7 +277,8 @@ void markerWithAssertNotLogged() { final AssertionError assertionError = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged( info("hello with marker", marker("expected")))); - assertThat(assertionError).hasMessage("Expected log message should not occur: Level: INFO, Regex: \"hello with marker\", with matchers: not expected marker name: \"expected\" was found"); + assertThat(assertionError).hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"hello with marker\", with matchers:" + + lineSeparator() + " marker name: \"expected\""); } } @@ -332,14 +333,15 @@ void loggerWithAssertNotLogged() { log.info("hello on this logger"); logCapture.assertNotLogged( - info("ello on this logger", + info("hello on this logger", logger("wrongLogger"))); final AssertionError assertionError = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged( info("hello on this logger", logger("ReadableApiTest$")))); assertThat(assertionError) - .hasMessage("Expected log message should not occur: Level: INFO, Regex: \"hello on this logger\", with matchers: not expected logger name (regex) was found: \"ReadableApiTest$\""); + .hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"hello on this logger\", with matchers:" + + lineSeparator() + " logger name (regex): \"ReadableApiTest$\""); } } @@ -516,12 +518,13 @@ void notLoggedWithMdc() { logCapture.assertNotLogged(info("helloWorld", mdc("thirdKey", "value"))); final AssertionError oneKeyMatches = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(info("hello world", mdc("key", "value")))); - assertThat(oneKeyMatches).hasMessage("Expected log message should not occur: Level: INFO, Regex: \"hello world\", with matchers: not expected MDCValue with key was found: \"key\""); + assertThat(oneKeyMatches).hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"hello world\", with matchers:" + + lineSeparator() + " MDCValue with key: \"key\""); final AssertionError bothKeysMatches = assertThrows(AssertionError.class, () -> logCapture.assertNotLogged(info("hello world", mdc("key", "value"), mdc("another_key", "another_value")))); - assertThat(bothKeysMatches).hasMessage("Expected log message should not occur: Level: INFO, Regex: \"hello world\", with matchers: not expected MDCValue with key was found: \"key\", not expected MDCValue with key was found: \"another_key\""); - - + assertThat(bothKeysMatches).hasMessage("Found a log message that should not be logged: Level: INFO, Regex: \"hello world\", with matchers:" + + lineSeparator() + " MDCValue with key: \"key\"" + + lineSeparator() + " MDCValue with key: \"another_key\""); } } diff --git a/src/test/java/de/dm/infrastructure/logcapture/ExpectedKeyValueUnitTest.java b/src/test/java/de/dm/infrastructure/logcapture/ExpectedKeyValueUnitTest.java new file mode 100644 index 0000000..236b9f3 --- /dev/null +++ b/src/test/java/de/dm/infrastructure/logcapture/ExpectedKeyValueUnitTest.java @@ -0,0 +1,16 @@ +package de.dm.infrastructure.logcapture; + +import net.logstash.logback.marker.SingleFieldAppendingMarker; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ExpectedKeyValueUnitTest { + @Test + @SuppressWarnings("squid:S3415") + // assertion arguments are in the right order, despite Sonar thinking otherwise + void logstashMarkerClassCanonicalNameIsCorrect() { + assertThat(ExpectedKeyValue.LOGSTASH_MARKER_CLASS).isEqualTo(SingleFieldAppendingMarker.class.getCanonicalName()); + } + +} diff --git a/src/test/java/de/dm/infrastructure/logcapture/LogCaptureArchTest.java b/src/test/java/de/dm/infrastructure/logcapture/LogCaptureArchTest.java new file mode 100644 index 0000000..ddfabe4 --- /dev/null +++ b/src/test/java/de/dm/infrastructure/logcapture/LogCaptureArchTest.java @@ -0,0 +1,19 @@ +package de.dm.infrastructure.logcapture; + +import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +@AnalyzeClasses(packagesOf = LogCapture.class, importOptions = DoNotIncludeTests.class) +public class LogCaptureArchTest { + @ArchTest + static ArchRule logstashIsOnlyUsedInDelegate = classes() + .that() + .doNotHaveFullyQualifiedName(ExpectedKeyValueLogstashDelegate.class.getCanonicalName()) + .should() + .onlyDependOnClassesThat() + .resideOutsideOfPackage("net.logstash.logback.."); +}