Skip to content

Commit 5fd42a1

Browse files
patch: Fix logback incompatibility issues and copy com.latch.LengthSplittingAppender into project (#143) (#144)
* add test for long message with JsonSplitter * Fix logback incompatibility issues and copy com.latch.LengthSplittingAppender into project * Add license information, use version catalog * fix tests
1 parent bcdcb83 commit 5fd42a1

9 files changed

Lines changed: 450 additions & 3 deletions

File tree

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ guava = { module = "com.google.guava:guava", version.ref = "guava" }
4646
kotlin-logging = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlin-logging" }
4747

4848
logback-logstash-encoder = { module = "net.logstash.logback:logstash-logback-encoder", version.ref = "logstash" }
49-
logback-length-splitter = { module = "com.latch:logback-length-splitting-appender", version = "0.4.0" }
49+
logback-classic = { module = "ch.qos.logback:logback-classic" }
5050

5151
mockk = { module = "io.mockk:mockk-jvm", version = "1.13.11" }
5252

platform-spring-bom/platform-spring-logging-server-config/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ dependencies {
33

44
// encoder for JSON logging
55
runtimeOnly(libs.logback.logstash.encoder)
6-
// log splitter
7-
runtimeOnly(libs.logback.length.splitter)
6+
7+
implementation(libs.logback.classic)
88
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.latch;
2+
3+
import ch.qos.logback.classic.LoggerContext;
4+
import ch.qos.logback.classic.spi.ILoggingEvent;
5+
import ch.qos.logback.classic.spi.LoggingEvent;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.util.ArrayList;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
/*
14+
* MIT License
15+
*
16+
* Copyright (c) 2019 Latchable, Inc.
17+
*
18+
* Permission is hereby granted, free of charge, to any person obtaining a copy
19+
* of this software and associated documentation files (the "Software"), to deal
20+
* in the Software without restriction, including without limitation the rights
21+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22+
* copies of the Software, and to permit persons to whom the Software is
23+
* furnished to do so, subject to the following conditions:
24+
*
25+
* The above copyright notice and this permission notice shall be included in all
26+
* copies or substantial portions of the Software.
27+
*
28+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34+
* SOFTWARE.
35+
*/
36+
public class LengthSplittingAppender extends SplittingAppenderBase<ILoggingEvent> {
37+
38+
private int maxLength;
39+
private String sequenceKey;
40+
41+
public int getMaxLength() {
42+
return maxLength;
43+
}
44+
45+
public void setMaxLength(int maxLength) {
46+
this.maxLength = maxLength;
47+
}
48+
49+
public String getSequenceKey() {
50+
return sequenceKey;
51+
}
52+
53+
public void setSequenceKey(String sequenceKey) {
54+
this.sequenceKey = sequenceKey;
55+
}
56+
57+
@Override
58+
public boolean shouldSplit(ILoggingEvent event) {
59+
return event.getFormattedMessage().length() > maxLength;
60+
}
61+
62+
@Override
63+
public List<ILoggingEvent> split(ILoggingEvent event) {
64+
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
65+
List<String> logMessages = splitString(event.getFormattedMessage(), getMaxLength());
66+
67+
List<ILoggingEvent> splitLogEvents = new ArrayList<>(logMessages.size());
68+
for (int i = 0; i < logMessages.size(); i++) {
69+
70+
LoggingEvent partition = LoggingEventCloner.clone(event, loggerContext);
71+
Map<String, String> seqMDCPropertyMap = new HashMap<>(event.getMDCPropertyMap());
72+
seqMDCPropertyMap.put(getSequenceKey(), Integer.toString(i));
73+
partition.setMDCPropertyMap(seqMDCPropertyMap);
74+
partition.setMessage(logMessages.get(i));
75+
76+
splitLogEvents.add(partition);
77+
}
78+
79+
return splitLogEvents;
80+
}
81+
82+
private List<String> splitString(String str, int chunkSize) {
83+
int fullChunks = str.length() / chunkSize;
84+
int remainder = str.length() % chunkSize;
85+
86+
List<String> results = new ArrayList<>(remainder == 0 ? fullChunks : fullChunks + 1);
87+
for (int i = 0; i < fullChunks; i++) {
88+
results.add(str.substring(i*chunkSize, i*chunkSize + chunkSize));
89+
}
90+
if (remainder != 0) {
91+
results.add(str.substring(str.length() - remainder));
92+
}
93+
return results;
94+
}
95+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.latch;
2+
3+
import ch.qos.logback.classic.LoggerContext;
4+
import ch.qos.logback.classic.spi.ILoggingEvent;
5+
import ch.qos.logback.classic.spi.LoggingEvent;
6+
import org.slf4j.Marker;
7+
8+
import java.util.List;
9+
10+
/*
11+
* MIT License
12+
*
13+
* Copyright (c) 2019 Latchable, Inc.
14+
*
15+
* Permission is hereby granted, free of charge, to any person obtaining a copy
16+
* of this software and associated documentation files (the "Software"), to deal
17+
* in the Software without restriction, including without limitation the rights
18+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19+
* copies of the Software, and to permit persons to whom the Software is
20+
* furnished to do so, subject to the following conditions:
21+
*
22+
* The above copyright notice and this permission notice shall be included in all
23+
* copies or substantial portions of the Software.
24+
*
25+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31+
* SOFTWARE.
32+
*/
33+
class LoggingEventCloner {
34+
35+
static LoggingEvent clone(ILoggingEvent event, LoggerContext loggerContext) {
36+
LoggingEvent logEventPartition = new LoggingEvent();
37+
38+
logEventPartition.setLevel(event.getLevel());
39+
logEventPartition.setLoggerName(event.getLoggerName());
40+
logEventPartition.setTimeStamp(event.getTimeStamp());
41+
logEventPartition.setLoggerContextRemoteView(event.getLoggerContextVO());
42+
logEventPartition.setLoggerContext(loggerContext);
43+
logEventPartition.setThreadName(event.getThreadName());
44+
45+
List<Marker> eventMarkers = event.getMarkerList();
46+
if (eventMarkers != null && !eventMarkers.isEmpty()) {
47+
logEventPartition.getMarkerList().addAll(eventMarkers);
48+
}
49+
50+
if (event.hasCallerData()) {
51+
logEventPartition.setCallerData(event.getCallerData());
52+
}
53+
54+
return logEventPartition;
55+
}
56+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.latch;
2+
3+
import ch.qos.logback.core.Appender;
4+
import ch.qos.logback.core.UnsynchronizedAppenderBase;
5+
import ch.qos.logback.core.spi.AppenderAttachable;
6+
import ch.qos.logback.core.spi.AppenderAttachableImpl;
7+
8+
import java.util.Iterator;
9+
import java.util.List;
10+
11+
/*
12+
* MIT License
13+
*
14+
* Copyright (c) 2019 Latchable, Inc.
15+
*
16+
* Permission is hereby granted, free of charge, to any person obtaining a copy
17+
* of this software and associated documentation files (the "Software"), to deal
18+
* in the Software without restriction, including without limitation the rights
19+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20+
* copies of the Software, and to permit persons to whom the Software is
21+
* furnished to do so, subject to the following conditions:
22+
*
23+
* The above copyright notice and this permission notice shall be included in all
24+
* copies or substantial portions of the Software.
25+
*
26+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32+
* SOFTWARE.
33+
*/
34+
public abstract class SplittingAppenderBase<E> extends UnsynchronizedAppenderBase<E>
35+
implements AppenderAttachable<E> {
36+
37+
private final AppenderAttachableImpl<E> aai = new AppenderAttachableImpl<>();
38+
39+
protected abstract List<E> split(E event);
40+
41+
protected abstract boolean shouldSplit(E eventObject);
42+
43+
@Override
44+
protected void append(E eventObject) {
45+
if (shouldSplit(eventObject)) {
46+
split(eventObject).forEach(aai::appendLoopOnAppenders);
47+
} else {
48+
aai.appendLoopOnAppenders(eventObject);
49+
}
50+
}
51+
52+
public void addAppender(Appender<E> newAppender) {
53+
addInfo("Attaching appender named [" + newAppender.getName() + "] to SplittingAppender.");
54+
aai.addAppender(newAppender);
55+
}
56+
57+
public Iterator<Appender<E>> iteratorForAppenders() {
58+
return aai.iteratorForAppenders();
59+
}
60+
61+
public Appender<E> getAppender(String name) {
62+
return aai.getAppender(name);
63+
}
64+
65+
public boolean isAttached(Appender<E> eAppender) {
66+
return aai.isAttached(eAppender);
67+
}
68+
69+
public void detachAndStopAllAppenders() {
70+
aai.detachAndStopAllAppenders();
71+
}
72+
73+
public boolean detachAppender(Appender<E> eAppender) {
74+
return aai.detachAppender(eAppender);
75+
}
76+
77+
public boolean detachAppender(String name) {
78+
return aai.detachAppender(name);
79+
}
80+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.latch;
2+
3+
import ch.qos.logback.classic.LoggerContext;
4+
import ch.qos.logback.classic.spi.ILoggingEvent;
5+
import ch.qos.logback.classic.spi.LoggingEvent;
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.Test;
8+
import org.slf4j.LoggerFactory;
9+
10+
import java.io.BufferedReader;
11+
import java.io.InputStream;
12+
import java.io.InputStreamReader;
13+
import java.util.Collections;
14+
import java.util.List;
15+
import java.util.stream.Collectors;
16+
17+
/*
18+
* MIT License
19+
*
20+
* Copyright (c) 2019 Latchable, Inc.
21+
*
22+
* Permission is hereby granted, free of charge, to any person obtaining a copy
23+
* of this software and associated documentation files (the "Software"), to deal
24+
* in the Software without restriction, including without limitation the rights
25+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
26+
* copies of the Software, and to permit persons to whom the Software is
27+
* furnished to do so, subject to the following conditions:
28+
*
29+
* The above copyright notice and this permission notice shall be included in all
30+
* copies or substantial portions of the Software.
31+
*
32+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
* SOFTWARE.
39+
*/
40+
public class LengthSplittingAppenderTest {
41+
private static final int MAX_MESSAGE_LENGTH = 50;
42+
private static final String BASE_STRING = "0123456789";
43+
private static final String LOREM_PATH = "logging_message.txt";
44+
45+
private final LoggerContext loggerContext;
46+
private final LengthSplittingAppender splitter;
47+
48+
public LengthSplittingAppenderTest() {
49+
this.loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
50+
this.splitter = new LengthSplittingAppender();
51+
splitter.setMaxLength(MAX_MESSAGE_LENGTH);
52+
splitter.setSequenceKey("seq");
53+
Assertions.assertEquals(MAX_MESSAGE_LENGTH, splitter.getMaxLength());
54+
}
55+
56+
@Test
57+
public void testEmpty() {
58+
LoggingEvent event = createLoggingEvent("");
59+
Assertions.assertFalse(splitter.shouldSplit(event));
60+
}
61+
62+
@Test
63+
public void testLessThanMax() {
64+
LoggingEvent event = createLoggingEvent(String.join("", Collections.nCopies(1, BASE_STRING)));
65+
Assertions.assertFalse(splitter.shouldSplit(event));
66+
}
67+
68+
@Test
69+
public void testEqualToMax() {
70+
LoggingEvent event = createLoggingEvent(String.join("", Collections.nCopies(5, BASE_STRING)));
71+
Assertions.assertEquals(MAX_MESSAGE_LENGTH, 5 * BASE_STRING.length());
72+
Assertions.assertFalse(splitter.shouldSplit(event));
73+
}
74+
75+
@Test
76+
public void testGreaterThanMaxAndMultipleOfMax() {
77+
LoggingEvent event = createLoggingEvent(String.join("", Collections.nCopies(50, BASE_STRING)));
78+
Assertions.assertTrue(splitter.shouldSplit(event));
79+
80+
List<ILoggingEvent> splitEvents = splitter.split(event);
81+
82+
Assertions.assertEquals(
83+
event.getFormattedMessage().length() / MAX_MESSAGE_LENGTH,
84+
splitEvents.size());
85+
}
86+
87+
@Test
88+
public void testGreaterThanMaxAndNotMultipleOfMax() {
89+
LoggingEvent event = createLoggingEvent(String.join("", Collections.nCopies(51, BASE_STRING)));
90+
Assertions.assertTrue(splitter.shouldSplit(event));
91+
92+
List<ILoggingEvent> splitEvents = splitter.split(event);
93+
94+
Assertions.assertEquals(
95+
event.getFormattedMessage().length() / MAX_MESSAGE_LENGTH + 1,
96+
splitEvents.size());
97+
}
98+
99+
@Test
100+
public void testSplitIntegrity() {
101+
String loremIpsum = readTextFromResource(LOREM_PATH);
102+
LoggingEvent event = createLoggingEvent(loremIpsum);
103+
104+
List<ILoggingEvent> splitEvents = splitter.split(event);
105+
106+
Assertions.assertEquals(event.getFormattedMessage(), recreateMessage(splitEvents));
107+
}
108+
109+
private LoggingEvent createLoggingEvent(String message) {
110+
LoggingEvent event = new LoggingEvent();
111+
event.setMessage(message);
112+
event.setLoggerContext(loggerContext);
113+
return event;
114+
}
115+
116+
private String recreateMessage(List<ILoggingEvent> splitEvents) {
117+
StringBuilder sb = new StringBuilder();
118+
119+
for (ILoggingEvent splitEvent : splitEvents) {
120+
sb.append(splitEvent.getFormattedMessage());
121+
}
122+
123+
return sb.toString();
124+
}
125+
126+
private String readTextFromResource(String fileName) {
127+
InputStream is = getClass().getClassLoader().getResourceAsStream(fileName);
128+
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
129+
return reader.lines().collect(Collectors.joining(""));
130+
}
131+
}

0 commit comments

Comments
 (0)