diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index c56a90c..4cadf85 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -13,7 +13,8 @@ object Version { const val junit = "5.6.2" const val log4j2 = "2.13.1" const val logback = "1.2.3" - const val spring = "2.2.6.RELEASE" + const val logstash = "7.2" + const val spring = "2.7.0" const val slf4j = "1.7.30" // Gradle plugins diff --git a/compliance-logging-logback-logstash/build.gradle.kts b/compliance-logging-logback-logstash/build.gradle.kts new file mode 100644 index 0000000..6dc48e3 --- /dev/null +++ b/compliance-logging-logback-logstash/build.gradle.kts @@ -0,0 +1,8 @@ +description = "Compliance Logstash Logback Encoder library" + +dependencies { + api(project(":compliance-logging-logback")) + api(group = "net.logstash.logback", name = "logstash-logback-encoder", version = Version.logstash) + + testImplementation(group = "org.junit.jupiter", name = "junit-jupiter") +} \ No newline at end of file diff --git a/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java b/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java new file mode 100644 index 0000000..fd8ce2f --- /dev/null +++ b/compliance-logging-logback-logstash/src/main/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProvider.java @@ -0,0 +1,101 @@ +/** + * MIT License + * + *

Copyright (c) 2020 Slalom LLC + * + *

Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + *

The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + *

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.slalom.logging.compliance.logback.logstash; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.fasterxml.jackson.core.JsonGenerator; +import com.slalom.logging.compliance.core.MaskService; +import com.slalom.logging.compliance.core.MaskType; +import com.slalom.logging.compliance.core.impl.JsonMaskService; +import com.slalom.logging.compliance.core.impl.LombokMaskService; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.AccessLevel; +import lombok.Getter; +import net.logstash.logback.composite.AbstractFieldJsonProvider; +import net.logstash.logback.composite.FieldNamesAware; +import net.logstash.logback.composite.JsonWritingUtils; +import net.logstash.logback.fieldnames.LogstashFieldNames; +import org.slf4j.Marker; + +/** Logtsash encoder in charge of calling the right service to mask sensitive data. */ +public class MaskingMessageJsonProvider extends AbstractFieldJsonProvider + implements FieldNamesAware { + + @Getter(AccessLevel.PACKAGE) + private boolean enabled = false; + + private MaskService jsonMaskService = new JsonMaskService(Collections.emptyList()); + private MaskService lombokMaskService = new LombokMaskService(Collections.emptyList()); + + private static final String FIELD_NAME = "msg"; + + public MaskingMessageJsonProvider() { + setFieldName(FIELD_NAME); + } + + /** + * Method called by logback with fields provided in the configuration file. + * + * @param fields the fields to be masked + */ + public void addFields(final String fields) { + final List regex = + Stream.of(fields.split(",")).map(String::trim).collect(Collectors.toList()); + enabled = true; + jsonMaskService = new JsonMaskService(regex); + lombokMaskService = new LombokMaskService(regex); + } + + @Override + public void setFieldNames(final LogstashFieldNames fieldNames) { + setFieldName(fieldNames.getMessage()); + } + + @Override + public void writeTo(final JsonGenerator generator, final ILoggingEvent event) throws IOException { + final String message = event.getFormattedMessage(); + if (enabled) { + final Marker marker = event.getMarker(); + if (marker == null) { + JsonWritingUtils.writeStringField(generator, getFieldName(), message); + } else { + writeMaskMessageTo(generator, message, marker); + } + } else { + JsonWritingUtils.writeStringField(generator, getFieldName(), message); + } + } + + private void writeMaskMessageTo( + final JsonGenerator generator, final String message, final Marker marker) throws IOException { + String maskedMessage = message; + if (MaskType.JSON.getName().equals(marker.getName())) { + maskedMessage = jsonMaskService.maskMessage(message); + } else if (MaskType.LOMBOK.getName().equals(marker.getName())) { + maskedMessage = lombokMaskService.maskMessage(message); + } + JsonWritingUtils.writeStringField(generator, getFieldName(), maskedMessage); + } +} diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/Employee.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/Employee.java new file mode 100644 index 0000000..44f59fb --- /dev/null +++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/Employee.java @@ -0,0 +1,35 @@ +/** + * MIT License + * + *

Copyright (c) 2020 Slalom LLC + * + *

Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + *

The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + *

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.slalom.logging.compliance.logback.logstash; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class Employee { + private final String firstName; + private final String lastName; + private final String password; + private final String ssn; +} diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java new file mode 100644 index 0000000..6014b11 --- /dev/null +++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/MaskingMessageJsonProviderTest.java @@ -0,0 +1,132 @@ +package com.slalom.logging.compliance.logback.logstash; + +import static org.assertj.core.api.Assertions.assertThat; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.slalom.logging.compliance.core.MaskType; +import com.slalom.logging.compliance.logback.logstash.stub.JsonGeneratorStub; +import com.slalom.logging.compliance.logback.logstash.stub.LoggingEventStub; +import java.io.IOException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +public class MaskingMessageJsonProviderTest { + + @DisplayName("Check that the masking is disabled when addFields method is not called") + @Test + public void testCheckStateDisabled() { + // when + MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider(); + + // then + assertThat(provider.isEnabled()).isFalse(); + } + + @DisplayName("Check that the masking is disabled when addFields method is not called") + @Test + public void testCheckStateDisabled2() { + // given + MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider(); + + // when + provider.addFields("password,ssn"); + + // then + assertThat(provider.isEnabled()).isTrue(); + } + + @DisplayName("Check that the encoder is masking json strings") + @Test + public void testMaskJsonMessage() throws IOException { + // given + String fields = "password,ssn"; + String message = + "my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}"; + MaskingMessageJsonProvider provider = buildProvider(fields); + Marker marker = MarkerFactory.getMarker(MaskType.JSON_MARKER_NAME); + ILoggingEvent logEvent = new LoggingEventStub(message, marker); + JsonGeneratorStub jsonGenerator = new JsonGeneratorStub(); + + // when + provider.writeTo(jsonGenerator, logEvent); + + // then + assertThat(jsonGenerator.getText()) + .contains( + "my message with json {\"login\":\"john\",\"password\":\"***********\",\"ssn\":\"***********\"}"); + } + + @DisplayName("Check that the encoder is masking lombok strings") + @Test + public void testMaskLombokMessage() throws IOException { + // given + String fields = "password,ssn"; + String message = + "my message with lombok " + + Employee.builder() + .firstName("john") + .lastName("doe") + .password("mypassword") + .ssn("123-12-1234") + .build(); + MaskingMessageJsonProvider provider = buildProvider(fields); + Marker marker = MarkerFactory.getMarker(MaskType.LOMBOK_MARKER_NAME); + ILoggingEvent logEvent = new LoggingEventStub(message, marker); + JsonGeneratorStub jsonGenerator = new JsonGeneratorStub(); + + // when + provider.writeTo(jsonGenerator, logEvent); + + // then + assertThat(jsonGenerator.getText()) + .contains( + "my message with lombok Employee(firstName=john, lastName=doe, password=***********, ssn=***********)"); + } + + @DisplayName("Check that the encoder is not doing anything when no marker is used") + @Test + public void testWithoutMarker() throws IOException { + // given + String fields = "password,ssn"; + String message = + "my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}"; + MaskingMessageJsonProvider provider = buildProvider(fields); + ILoggingEvent logEvent = new LoggingEventStub(message); + JsonGeneratorStub jsonGenerator = new JsonGeneratorStub(); + + // when + provider.writeTo(jsonGenerator, logEvent); + + // then + assertThat(jsonGenerator.getText()).contains(message); + } + + @DisplayName("Check that the encoder is not doing anything when it is disabled") + @Test + public void testWithPluginDisabled() throws IOException { + // given + String message = + "my message with json {\"login\":\"john\",\"password\":\"mypassword\",\"ssn\":\"123-12-1234\"}"; + MaskingMessageJsonProvider provider = buildDisabledProvider(); + ILoggingEvent logEvent = new LoggingEventStub(message); + JsonGeneratorStub jsonGenerator = new JsonGeneratorStub(); + + // when + provider.writeTo(jsonGenerator, logEvent); + + // then + assertThat(jsonGenerator.getText()).contains(message); + } + + private MaskingMessageJsonProvider buildProvider(String fields) { + MaskingMessageJsonProvider provider = new MaskingMessageJsonProvider(); + provider.addFields(fields); + return provider; + } + + private MaskingMessageJsonProvider buildDisabledProvider() { + return new MaskingMessageJsonProvider(); + } +} diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java new file mode 100644 index 0000000..b1b83b5 --- /dev/null +++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/JsonGeneratorStub.java @@ -0,0 +1,179 @@ +package com.slalom.logging.compliance.logback.logstash.stub; + +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.SerializableString; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.core.Version; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import lombok.Getter; + +public class JsonGeneratorStub extends JsonGenerator { + + @Getter private String text; + + @Override + public JsonGenerator setCodec(ObjectCodec oc) { + return null; + } + + @Override + public ObjectCodec getCodec() { + return null; + } + + @Override + public Version version() { + return null; + } + + @Override + public JsonStreamContext getOutputContext() { + return null; + } + + @Override + public JsonGenerator enable(Feature f) { + return null; + } + + @Override + public JsonGenerator disable(Feature f) { + return null; + } + + @Override + public boolean isEnabled(Feature f) { + return false; + } + + @Override + public int getFeatureMask() { + return 0; + } + + @Deprecated + @Override + public JsonGenerator setFeatureMask(int values) { + return null; + } + + @Override + public JsonGenerator useDefaultPrettyPrinter() { + return null; + } + + @Override + public void writeStartArray() throws IOException {} + + @Override + public void writeEndArray() throws IOException {} + + @Override + public void writeStartObject() throws IOException {} + + @Override + public void writeEndObject() throws IOException {} + + @Override + public void writeFieldName(String name) throws IOException {} + + @Override + public void writeFieldName(SerializableString name) throws IOException {} + + @Override + public void writeString(String text) throws IOException { + System.out.println("text"); + this.text = text; + } + + @Override + public void writeString(char[] buffer, int offset, int len) throws IOException {} + + @Override + public void writeString(SerializableString text) throws IOException {} + + @Override + public void writeRawUTF8String(byte[] buffer, int offset, int len) throws IOException {} + + @Override + public void writeUTF8String(byte[] buffer, int offset, int len) throws IOException {} + + @Override + public void writeRaw(String text) throws IOException {} + + @Override + public void writeRaw(String text, int offset, int len) throws IOException {} + + @Override + public void writeRaw(char[] text, int offset, int len) throws IOException {} + + @Override + public void writeRaw(char c) throws IOException {} + + @Override + public void writeRawValue(String text) throws IOException {} + + @Override + public void writeRawValue(String text, int offset, int len) throws IOException {} + + @Override + public void writeRawValue(char[] text, int offset, int len) throws IOException {} + + @Override + public void writeBinary(Base64Variant bv, byte[] data, int offset, int len) throws IOException {} + + @Override + public int writeBinary(Base64Variant bv, InputStream data, int dataLength) throws IOException { + return 0; + } + + @Override + public void writeNumber(int v) throws IOException {} + + @Override + public void writeNumber(long v) throws IOException {} + + @Override + public void writeNumber(BigInteger v) throws IOException {} + + @Override + public void writeNumber(double v) throws IOException {} + + @Override + public void writeNumber(float v) throws IOException {} + + @Override + public void writeNumber(BigDecimal v) throws IOException {} + + @Override + public void writeNumber(String encodedValue) throws IOException {} + + @Override + public void writeBoolean(boolean state) throws IOException {} + + @Override + public void writeNull() throws IOException {} + + @Override + public void writeObject(Object pojo) throws IOException {} + + @Override + public void writeTree(TreeNode rootNode) throws IOException {} + + @Override + public void flush() throws IOException {} + + @Override + public boolean isClosed() { + return false; + } + + @Override + public void close() throws IOException {} +} diff --git a/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java new file mode 100644 index 0000000..5c63ae4 --- /dev/null +++ b/compliance-logging-logback-logstash/src/test/java/com/slalom/logging/compliance/logback/logstash/stub/LoggingEventStub.java @@ -0,0 +1,120 @@ +/** + * MIT License + * + *

Copyright (c) 2020 Slalom LLC + * + *

Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + *

The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + *

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.slalom.logging.compliance.logback.logstash.stub; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.LoggerContextVO; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Map; +import org.slf4j.Marker; + +public class LoggingEventStub implements ILoggingEvent { + + private final String message; + private final Marker marker; + + public LoggingEventStub(final String message) { + this.message = message; + this.marker = null; + } + + public LoggingEventStub(final String message, final Marker marker) { + this.message = message; + this.marker = marker; + } + + @Override + public String getThreadName() { + return null; + } + + @Override + public Level getLevel() { + return Level.INFO; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public Object[] getArgumentArray() { + return new Object[0]; + } + + @Override + public String getFormattedMessage() { + return message; + } + + @Override + public String getLoggerName() { + return "logger"; + } + + @Override + public LoggerContextVO getLoggerContextVO() { + return null; + } + + @Override + public IThrowableProxy getThrowableProxy() { + return null; + } + + @Override + public StackTraceElement[] getCallerData() { + return new StackTraceElement[0]; + } + + @Override + public boolean hasCallerData() { + return false; + } + + @Override + public Marker getMarker() { + return marker; + } + + @Override + public Map getMDCPropertyMap() { + return null; + } + + @SuppressWarnings("deprecation") + @Override + public Map getMdc() { + return null; + } + + @Override + public long getTimeStamp() { + return LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); + } + + @Override + public void prepareForDeferredProcessing() {} +} diff --git a/examples/spring-boot-logback-logstash-example/build.gradle.kts b/examples/spring-boot-logback-logstash-example/build.gradle.kts new file mode 100644 index 0000000..34c0f3b --- /dev/null +++ b/examples/spring-boot-logback-logstash-example/build.gradle.kts @@ -0,0 +1,10 @@ +dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:${Version.spring}")) + implementation(group = "org.springframework.boot", name = "spring-boot-starter-web") + implementation(project(":compliance-logging-logback-logstash")) +} + +// Do not publish artifact +project.afterEvaluate { + project.tasks.getByName("publishMavenPublicationToMavenLocal").enabled = false +} \ No newline at end of file diff --git a/examples/spring-boot-logback-logstash-example/src/main/java/com/slalom/logging/compliance/example/LogbackLogstashExample.java b/examples/spring-boot-logback-logstash-example/src/main/java/com/slalom/logging/compliance/example/LogbackLogstashExample.java new file mode 100644 index 0000000..5e9916d --- /dev/null +++ b/examples/spring-boot-logback-logstash-example/src/main/java/com/slalom/logging/compliance/example/LogbackLogstashExample.java @@ -0,0 +1,61 @@ +/** + * MIT License + * + *

Copyright (c) 2020 Slalom LLC + * + *

Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + *

The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + *

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.slalom.logging.compliance.example; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.slalom.logging.compliance.core.MaskType; +import javax.annotation.PostConstruct; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@Slf4j +@SpringBootApplication +public class LogbackLogstashExample { + + public static void main(String[] args) { + SpringApplication.run(LogbackLogstashExample.class, args); + } + + @PostConstruct + public void postConstruct() throws JsonProcessingException { + final User user = + User.builder().login("mylogin").password("mypassword").ssn("123-123-1234").build(); + log.info("================================================================================="); + log.info("No Marker: {}", user); + log.info(MaskType.LOMBOK, "Marker Lombok: {}", user); + log.info(MaskType.JSON, "Marker Json: {}", new ObjectMapper().writeValueAsString(user)); + log.info("================================================================================="); + } + + @Data + @ToString + @Builder + public static class User { + private final String login; + private final String password; + private final String ssn; + } +} diff --git a/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml b/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml new file mode 100644 index 0000000..43b552f --- /dev/null +++ b/examples/spring-boot-logback-logstash-example/src/main/resources/logback.xml @@ -0,0 +1,33 @@ + + + + + + + ts + UTC + + + + { + "logger": "%logger", + "level": "%level", + "class": "%class", + "method": "%method", + "line": "%line", + "file": "%file", + "thread": "%thread" + } + + + + password,ssn + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index cfebbe3..71525a1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,13 +3,16 @@ include( ":compliance-logging-core", ":compliance-logging-log4j2", ":compliance-logging-logback", + ":compliance-logging-logback-logstash", ":spring-boot-log4j2-example", ":spring-boot-logback-example", ":simple-log4j2-example", - ":simple-logback-example" + ":simple-logback-example", + ":simple-logback-logstash-example" ) project(":spring-boot-log4j2-example").projectDir = file("examples/spring-boot-log4j2-example") project(":spring-boot-logback-example").projectDir = file("examples/spring-boot-logback-example") +project(":simple-logback-logstash-example").projectDir = file("examples/spring-boot-logback-logstash-example") project(":simple-log4j2-example").projectDir = file("examples/simple-log4j2-example") -project(":simple-logback-example").projectDir = file("examples/simple-logback-example") \ No newline at end of file +project(":simple-logback-example").projectDir = file("examples/simple-logback-example")