Skip to content

Commit 97e5175

Browse files
committed
ongoing work on JsonEnconder
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
1 parent f7ae802 commit 97e5175

File tree

3 files changed

+177
-38
lines changed

3 files changed

+177
-38
lines changed

logback-classic/src/main/java/ch/qos/logback/classic/encoder/JsonEncoder.java

+133-13
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717
import ch.qos.logback.classic.Level;
1818
import ch.qos.logback.classic.spi.ILoggingEvent;
1919
import ch.qos.logback.core.encoder.EncoderBase;
20-
import ch.qos.logback.core.util.DirectJson;
20+
import org.slf4j.Marker;
21+
import org.slf4j.event.KeyValuePair;
2122

22-
import java.nio.charset.Charset;
23+
import java.util.List;
24+
import java.util.Map;
2325

2426
import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
2527
import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
2628
import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
2729
import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
30+
import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
2831
import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
2932

3033
/**
@@ -43,22 +46,35 @@ public class JsonEncoder extends EncoderBase<ILoggingEvent> {
4346

4447
public static final String CONTEXT_ATTR_NAME = "context";
4548
public static final String TIMESTAMP_ATTR_NAME = "timestamp";
49+
50+
public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
51+
52+
public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumbers";
53+
54+
4655
public static final String LEVEL_ATTR_NAME = "level";
4756
public static final String MARKERS_ATTR_NAME = "markers";
4857
public static final String THREAD_ATTR_NAME = "thread";
4958
public static final String MDC_ATTR_NAME = "mdc";
5059
public static final String LOGGER_ATTR_NAME = "logger";
51-
public static final String MESSAGE_ATTR_NAME = "raw-message";
60+
public static final String MESSAGE_ATTR_NAME = "rawMessage";
61+
62+
public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
63+
public static final String KEY_VALUE_PAIRS_ATTR_NAME = "keyValuePairs";
64+
5265
public static final String THROWABLE_ATTR_NAME = "throwable";
5366

5467
private static final char OPEN_OBJ = '{';
5568
private static final char CLOSE_OBJ = '}';
56-
private static final char OPEN_ARR = '[';
57-
private static final char CLOSE_ARR = ']';
69+
private static final char OPEN_ARRAY = '[';
70+
private static final char CLOSE_ARRAY = ']';
5871

5972
private static final char QUOTE = DOUBLE_QUOTE_CHAR;
6073
private static final char SP = ' ';
6174
private static final char ENTRY_SEPARATOR = COLON_CHAR;
75+
76+
private static final char COL_SP = COLON_CHAR+SP;
77+
6278
private static final char VALUE_SEPARATOR = COMMA_CHAR;
6379

6480

@@ -72,26 +88,130 @@ public byte[] headerBytes() {
7288
public byte[] encode(ILoggingEvent event) {
7389
final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE: DEFAULT_SIZE_WITH_THROWABLE;
7490
StringBuilder sb = new StringBuilder(initialCapacity);
91+
sb.append(OPEN_OBJ);
92+
93+
94+
sb.append(SEQUENCE_NUMBER_ATTR_NAME).append(COL_SP).append(event.getSequenceNumber());
95+
sb.append(VALUE_SEPARATOR);
96+
97+
98+
sb.append(TIMESTAMP_ATTR_NAME).append(COL_SP).append(event.getTimeStamp());
99+
sb.append(VALUE_SEPARATOR);
100+
101+
sb.append(NANOSECONDS_ATTR_NAME).append(COL_SP).append(event.getNanoseconds());
102+
sb.append(VALUE_SEPARATOR);
103+
104+
105+
String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
106+
sb.append(LEVEL_ATTR_NAME).append(COL_SP).append(QUOTE).append(levelStr).append(QUOTE);
107+
sb.append(VALUE_SEPARATOR);
108+
109+
sb.append(THREAD_ATTR_NAME).append(COL_SP).append(QUOTE).append(jsonSafeStr(event.getThreadName())).append(QUOTE);
110+
sb.append(VALUE_SEPARATOR);
75111

112+
sb.append(LOGGER_ATTR_NAME).append(COL_SP).append(QUOTE).append(event.getLoggerName()).append(QUOTE);
113+
sb.append(VALUE_SEPARATOR);
76114

115+
appendMarkers(sb, event);
116+
appendMDC(sb, event);
117+
appendKeyValuePairs(sb, event);
77118

119+
sb.append(MESSAGE_ATTR_NAME).append(COL_SP).append(QUOTE).append(jsonSafeStr(event.getMessage())).append(QUOTE);
120+
sb.append(VALUE_SEPARATOR);
121+
122+
appendArgumentArray(sb, event);
123+
124+
sb.append(CLOSE_OBJ);
78125
return sb.toString().getBytes(UTF_8_CHARSET);
79126
}
80127

128+
private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
129+
List<KeyValuePair> kvpList = event.getKeyValuePairs();
130+
if(kvpList == null || kvpList.isEmpty())
131+
return;
132+
133+
sb.append(KEY_VALUE_PAIRS_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
134+
final int len = kvpList.size();
135+
for(int i = 0; i < len; i++) {
136+
KeyValuePair kvp = kvpList.get(i);
137+
sb.append(QUOTE).append(jsonSafeToString(kvp.key)).append(QUOTE);
138+
sb.append(COL_SP);
139+
sb.append(QUOTE).append(jsonSafeToString(kvp.value)).append(QUOTE);
140+
141+
if(i != len)
142+
sb.append(VALUE_SEPARATOR);
143+
}
144+
sb.append(CLOSE_ARRAY);
145+
}
146+
147+
private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
148+
Object[] argumentArray = event.getArgumentArray();
149+
if(argumentArray == null)
150+
return;
151+
152+
sb.append(ARGUMENT_ARRAY_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
153+
final int len = argumentArray.length;
154+
for(int i = 0; i < len; i++) {
155+
sb.append(QUOTE).append(jsonSafeToString(argumentArray[i])).append(QUOTE);
156+
if(i != len)
157+
sb.append(VALUE_SEPARATOR);
158+
}
159+
sb.append(CLOSE_ARRAY);
160+
}
161+
162+
private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
163+
List<Marker> markerList = event.getMarkerList();
164+
if(markerList == null)
165+
return;
166+
167+
sb.append(MARKERS_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
168+
final int len = markerList.size();
169+
for(int i = 0; i < len; i++) {
170+
sb.append(QUOTE).append(jsonSafeToString(markerList.get(i))).append(QUOTE);
171+
if(i != len)
172+
sb.append(VALUE_SEPARATOR);
173+
}
174+
sb.append(CLOSE_ARRAY);
175+
}
176+
177+
private String jsonSafeToString(Object o) {
178+
if(o == null)
179+
return NULL_STR;
180+
return jsonEscapeString(o.toString());
181+
}
182+
183+
private String jsonSafeStr(String s) {
184+
if(s == null)
185+
return NULL_STR;
186+
return jsonEscapeString(s);
187+
}
188+
189+
190+
private void appendMDC(StringBuilder sb, ILoggingEvent event) {
191+
Map<String, String> map = event.getMDCPropertyMap();
192+
193+
sb.append(MDC_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_OBJ);
194+
if(isNotEmptyMap(map)) {
195+
map.entrySet().stream().forEach(e -> appendMapEntry(sb, e));
196+
}
197+
sb.append(CLOSE_OBJ);
81198

82-
public void writeLevel(StringBuilder sb, Level level) {
83-
String levelString = level != null? level.toString() : NULL_STR;
84-
writeStringValue(sb, LEVEL_ATTR_NAME, levelString);
85199
}
86200

87-
void writeStringValue(StringBuilder sb, String attrName, String value) {
88-
sb.append(attrName).append(ENTRY_SEPARATOR).append(SP).append(QUOTE).append(value);
89-
Character c = ' ';
201+
private void appendMapEntry(StringBuilder sb, Map.Entry<String, String> entry) {
202+
if(entry == null)
203+
return;
204+
205+
sb.append(QUOTE).append(jsonSafeToString(entry.getKey())).append(QUOTE).append(COL_SP).append(QUOTE)
206+
.append(jsonSafeToString(entry.getValue())).append(QUOTE);
90207
}
91208

92-
public void writeSep(StringBuilder sb) {
93-
sb.append(',');
209+
boolean isNotEmptyMap(Map map) {
210+
if(map == null)
211+
return false;
212+
return !map.isEmpty();
94213
}
214+
95215
@Override
96216
public byte[] footerBytes() {
97217
return EMPTY_BYTES;

logback-core/src/main/java/ch/qos/logback/core/encoder/JsonEscapeUtil.java

+28-23
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ public class JsonEscapeUtil {
1818

1919
protected final static char[] HEXADECIMALS_TABLE = "0123456789ABCDEF".toCharArray();
2020

21-
2221
static final int ESCAPE_CODES_COUNT = 32;
2322

2423
static final String[] ESCAPE_CODES = new String[ESCAPE_CODES_COUNT];
2524

26-
2725
// From RFC-8259 page 5
2826

2927
// %x22 / ; " quotation mark U+0022
@@ -37,26 +35,33 @@ public class JsonEscapeUtil {
3735
// %x72 / ; r carriage return U+000D
3836

3937
static {
40-
for(char c = 0; c < ESCAPE_CODES_COUNT; c++) {
38+
for (char c = 0; c < ESCAPE_CODES_COUNT; c++) {
4139

42-
switch(c) {
43-
case 0x08: ESCAPE_CODES[c] = "\\b";
44-
break;
45-
case 0x09: ESCAPE_CODES[c] = "\\t";
40+
switch (c) {
41+
case 0x08:
42+
ESCAPE_CODES[c] = "\\b";
43+
break;
44+
case 0x09:
45+
ESCAPE_CODES[c] = "\\t";
4646
break;
47-
case 0x0A: ESCAPE_CODES[c] = "\\n";
47+
case 0x0A:
48+
ESCAPE_CODES[c] = "\\n";
4849
break;
49-
case 0x0C: ESCAPE_CODES[c] = "\\f";
50+
case 0x0C:
51+
ESCAPE_CODES[c] = "\\f";
5052
break;
51-
case 0x0D: ESCAPE_CODES[c] = "\\r";
53+
case 0x0D:
54+
ESCAPE_CODES[c] = "\\r";
5255
break;
5356
default:
54-
ESCAPE_CODES[c] = getEscapeCodeBelowASCII32(c);
57+
ESCAPE_CODES[c] = _computeEscapeCodeBelowASCII32(c);
5558
}
5659
}
5760
}
58-
static String getEscapeCodeBelowASCII32(char c) {
59-
if(c > 32) {
61+
62+
// this method should not be called by methods except the static initializer
63+
private static String _computeEscapeCodeBelowASCII32(char c) {
64+
if (c > 32) {
6065
throw new IllegalArgumentException("input must be less than 32");
6166
}
6267

@@ -69,36 +74,36 @@ static String getEscapeCodeBelowASCII32(char c) {
6974
int lowPart = c & 0x0F;
7075
sb.append(HEXADECIMALS_TABLE[lowPart]);
7176

72-
7377
return sb.toString();
7478
}
7579

7680
// %x22 / ; " quotation mark U+0022
7781
// %x5C / ; \ reverse solidus U+005C
7882

7983
static String getObligatoryEscapeCode(char c) {
80-
if(c < 32)
81-
return getEscapeCodeBelowASCII32(c);
82-
if(c == 0x22)
84+
if (c < 32)
85+
return ESCAPE_CODES[c];
86+
if (c == 0x22)
8387
return "\\\"";
84-
if(c == 0x5C)
88+
if (c == 0x5C)
8589
return "\\/";
8690

8791
return null;
8892
}
8993

90-
static String jsonEscapeString(String input) {
94+
static public String jsonEscapeString(String input) {
9195
int length = input.length();
92-
int lenthWithLeeway = (int) (length*1.1);
96+
int lenthWithLeeway = (int) (length * 1.1);
9397

9498
StringBuilder sb = new StringBuilder(lenthWithLeeway);
95-
for(int i = 0; i < length; i++) {
99+
for (int i = 0; i < length; i++) {
96100
final char c = input.charAt(i);
97101
String escaped = getObligatoryEscapeCode(c);
98-
if(escaped == null)
102+
if (escaped == null)
99103
sb.append(c);
100-
else
104+
else {
101105
sb.append(escaped);
106+
}
102107
}
103108

104109
return sb.toString();

logback-core/src/test/java/ch/qos/logback/core/encoder/JsonEscapeUtilTest.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class JsonEscapeUtilTest {
2222

2323
@Test
24-
public void testEscapeCodes() {
24+
public void smokeTestEscapeCodes() {
2525
assertEquals("\\u0001", JsonEscapeUtil.ESCAPE_CODES[1]);
2626
assertEquals("\\u0005", JsonEscapeUtil.ESCAPE_CODES[5]);
2727
assertEquals("\\b", JsonEscapeUtil.ESCAPE_CODES[8]);
@@ -36,9 +36,23 @@ public void testEscapeCodes() {
3636
}
3737

3838
@Test
39-
public void testEscapeString() {
39+
public void smokeTestEscapeString() {
4040
assertEquals("abc", JsonEscapeUtil.jsonEscapeString("abc"));
4141
assertEquals("{world: \\\"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
4242
assertEquals("{world: "+'\\'+'"'+"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
4343
}
44+
45+
@Test
46+
public void testEscapingLF() {
47+
String input = "{\nhello: \"wo\nrld\"}";
48+
System.out.println(input);
49+
assertEquals("{\\nhello: "+'\\'+'"'+"wo\\nrld\\\"}", JsonEscapeUtil.jsonEscapeString(input));
50+
}
51+
52+
@Test
53+
public void testEscapingTab() {
54+
String input = "{hello: \"\tworld\"}";
55+
System.out.println(input);
56+
assertEquals("{hello: "+'\\'+'"'+"\\tworld\\\"}", JsonEscapeUtil.jsonEscapeString(input));
57+
}
4458
}

0 commit comments

Comments
 (0)