Skip to content

Commit dadebfe

Browse files
committed
ongoing work on JsonEscapeUtil
Signed-off-by: Ceki Gulcu <[email protected]>
1 parent 3d2c108 commit dadebfe

6 files changed

Lines changed: 302 additions & 1 deletion

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package ch.qos.logback.classic.encoder;public class JsonEncoder {
2+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package ch.qos.logback.classic.encoder;
2+
3+
import ch.qos.logback.classic.spi.ILoggingEvent;
4+
import ch.qos.logback.core.encoder.JsonEncoderBase;
5+
import ch.qos.logback.core.util.DirectJson;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
/**
11+
* This is a concrete JsonEncoder for {@link ILoggingEvent} that emits fields according to object's configuration.
12+
* It is partially imported from <a href="https://github.com/hkupty/penna">penna</a>, but adapted to logback's structure.
13+
*
14+
* @author Henry John Kupty
15+
*/
16+
public class JsonEncoder extends JsonEncoderBase<ILoggingEvent> {
17+
18+
19+
// Excerpt below imported from
20+
// ch.qos.logback.contrib.json.classic.JsonLayout
21+
public static final String TIMESTAMP_ATTR_NAME = "timestamp";
22+
public static final String LEVEL_ATTR_NAME = "level";
23+
public static final String MARKERS_ATTR_NAME = "tags";
24+
public static final String THREAD_ATTR_NAME = "thread";
25+
public static final String MDC_ATTR_NAME = "mdc";
26+
public static final String LOGGER_ATTR_NAME = "logger";
27+
public static final String FORMATTED_MESSAGE_ATTR_NAME = "message";
28+
public static final String MESSAGE_ATTR_NAME = "raw-message";
29+
public static final String EXCEPTION_ATTR_NAME = "exception";
30+
public static final String CONTEXT_ATTR_NAME = "context";
31+
32+
protected boolean includeLevel;
33+
protected boolean includeThreadName;
34+
protected boolean includeMDC;
35+
protected boolean includeLoggerName;
36+
protected boolean includeFormattedMessage;
37+
protected boolean includeMessage;
38+
protected boolean includeException;
39+
protected boolean includeContextName;
40+
41+
private final List<Emitter<ILoggingEvent>> emitters;
42+
43+
44+
public JsonEncoder() {
45+
super();
46+
47+
emitters = new ArrayList<>();
48+
this.includeLevel = true;
49+
this.includeThreadName = true;
50+
this.includeMDC = true;
51+
this.includeLoggerName = true;
52+
this.includeFormattedMessage = true;
53+
this.includeException = true;
54+
this.includeContextName = true;
55+
}
56+
57+
//protected = new DirectJson();
58+
59+
60+
public void writeMessage(DirectJson jsonWriter, ILoggingEvent event) {
61+
jsonWriter.writeStringValue(MESSAGE_ATTR_NAME, event.getMessage());
62+
}
63+
64+
public void writeFormattedMessage(DirectJson jsonWriter, ILoggingEvent event) {
65+
jsonWriter.writeStringValue(FORMATTED_MESSAGE_ATTR_NAME, event.getFormattedMessage());
66+
}
67+
68+
public void writeLogger(DirectJson jsonWriter, ILoggingEvent event) {
69+
jsonWriter.writeStringValue(LOGGER_ATTR_NAME, event.getLoggerName());
70+
}
71+
72+
public void writeThreadName(DirectJson jsonWriter, ILoggingEvent event) {
73+
jsonWriter.writeStringValue(THREAD_ATTR_NAME, event.getThreadName());
74+
}
75+
76+
public void writeLevel(DirectJson jsonWriter, ILoggingEvent event) {
77+
jsonWriter.writeStringValue(LEVEL_ATTR_NAME, event.getLevel().levelStr);
78+
}
79+
80+
81+
public void writeMarkers(DirectJson jsonWriter, ILoggingEvent event) {
82+
var markers = event.getMarkerList();
83+
if (!markers.isEmpty()) {
84+
jsonWriter.openArray(MARKERS_ATTR_NAME);
85+
for (var marker : markers) {
86+
jsonWriter.writeString(marker.getName());
87+
jsonWriter.writeSep();
88+
}
89+
// Close array will overwrite the last "," in the buffer, so we are OK
90+
jsonWriter.closeArray();
91+
jsonWriter.writeSep();
92+
}
93+
}
94+
95+
public void writeMdc(DirectJson jsonWriter, ILoggingEvent event) {
96+
var mdc = event.getMDCPropertyMap();
97+
if (!mdc.isEmpty()) {
98+
jsonWriter.openObject(MDC_ATTR_NAME);
99+
for (var entry : mdc.entrySet()) {
100+
jsonWriter.writeStringValue(entry.getKey(), entry.getValue());
101+
}
102+
jsonWriter.closeObject();
103+
jsonWriter.writeSep();
104+
}
105+
}
106+
107+
private void buildEmitterList() {
108+
// This method should be re-entrant and allow for reconfiguring the emitters if something change;
109+
emitters.clear();
110+
111+
// TODO figure out order
112+
if (includeLevel) emitters.add(this::writeLevel);
113+
if (includeMDC) emitters.add(this::writeMdc);
114+
if (includeMessage) emitters.add(this::writeMessage);
115+
if (includeFormattedMessage) emitters.add(this::writeFormattedMessage);
116+
if (includeThreadName) emitters.add(this::writeThreadName);
117+
if (includeLoggerName) emitters.add(this::writeLogger);
118+
// TODO add fields missing:
119+
// context
120+
// exception
121+
// custom data
122+
// marker
123+
}
124+
125+
@Override
126+
public byte[] encode(ILoggingEvent event) {
127+
if (emitters.isEmpty()) {
128+
buildEmitterList();
129+
}
130+
DirectJson jsonWriter = new DirectJson();
131+
jsonWriter.openObject();
132+
133+
for (var emitter: emitters) {
134+
emitter.write(jsonWriter, event);
135+
}
136+
137+
jsonWriter.closeObject();
138+
return jsonWriter.flush();
139+
}
140+
}

logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
*/
1414
package ch.qos.logback.core;
1515

16+
import java.nio.charset.Charset;
17+
import java.nio.charset.StandardCharsets;
18+
1619
public class CoreConstants {
1720

1821
final public static String DISABLE_SERVLET_CONTAINER_INITIALIZER_KEY = "logbackDisableServletContainerInitializer";
@@ -107,6 +110,8 @@ public class CoreConstants {
107110
*/
108111
public static final String[] EMPTY_STRING_ARRAY = new String[] {};
109112

113+
public static final Charset UTF_8_CHARSET = StandardCharsets.UTF_8;
114+
110115
/**
111116
* An empty Class array.
112117
*/
@@ -129,6 +134,7 @@ public class CoreConstants {
129134
public static final char DASH_CHAR = '-';
130135
public static final String DEFAULT_VALUE_SEPARATOR = ":-";
131136

137+
public static final String NULL_STR = "null";
132138
/**
133139
* Number of rows before in an HTML table before, we close the table and create
134140
* a new one
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.core.encoder;
16+
17+
public class JsonEscapeUtil {
18+
19+
protected final static char[] HEXADECIMALS_TABLE = "0123456789ABCDEF".toCharArray();
20+
21+
22+
static final int ESCAPE_CODES_COUNT = 32;
23+
24+
static final String[] ESCAPE_CODES = new String[ESCAPE_CODES_COUNT];
25+
26+
27+
// From RFC-8259 page 5
28+
29+
// %x22 / ; " quotation mark U+0022
30+
// %x5C / ; \ reverse solidus U+005C
31+
// %x2F / ; / solidus U+002F
32+
33+
// %x62 / ; b backspace U+0008
34+
// %x74 / ; t tab U+0009
35+
// %x6E / ; n line feed U+000A
36+
// %x66 / ; f form feed U+000C
37+
// %x72 / ; r carriage return U+000D
38+
39+
static {
40+
for(char c = 0; c < ESCAPE_CODES_COUNT; c++) {
41+
42+
switch(c) {
43+
case 0x08: ESCAPE_CODES[c] = "\\b";
44+
break;
45+
case 0x09: ESCAPE_CODES[c] = "\\t";
46+
break;
47+
case 0x0A: ESCAPE_CODES[c] = "\\n";
48+
break;
49+
case 0x0C: ESCAPE_CODES[c] = "\\f";
50+
break;
51+
case 0x0D: ESCAPE_CODES[c] = "\\r";
52+
break;
53+
default:
54+
ESCAPE_CODES[c] = getEscapeCodeBelowASCII32(c);
55+
}
56+
}
57+
}
58+
static String getEscapeCodeBelowASCII32(char c) {
59+
if(c > 32) {
60+
throw new IllegalArgumentException("input must be less than 32");
61+
}
62+
63+
StringBuilder sb = new StringBuilder(6);
64+
sb.append("\\u00");
65+
66+
int highPart = c >> 4;
67+
sb.append(HEXADECIMALS_TABLE[highPart]);
68+
69+
int lowPart = c & 0x0F;
70+
sb.append(HEXADECIMALS_TABLE[lowPart]);
71+
72+
73+
return sb.toString();
74+
}
75+
76+
// %x22 / ; " quotation mark U+0022
77+
// %x5C / ; \ reverse solidus U+005C
78+
79+
static String getObligatoryEscapeCode(char c) {
80+
if(c < 32)
81+
return getEscapeCodeBelowASCII32(c);
82+
if(c == 0x22)
83+
return "\\\"";
84+
if(c == 0x5C)
85+
return "\\/";
86+
87+
return null;
88+
}
89+
90+
static String jsonEscapeString(String input) {
91+
int length = input.length();
92+
int lenthWithLeeway = (int) (length*1.1);
93+
94+
StringBuilder sb = new StringBuilder(lenthWithLeeway);
95+
for(int i = 0; i < length; i++) {
96+
final char c = input.charAt(i);
97+
String escaped = getObligatoryEscapeCode(c);
98+
if(escaped == null)
99+
sb.append(c);
100+
else
101+
sb.append(escaped);
102+
}
103+
104+
return sb.toString();
105+
}
106+
107+
}

logback-core/src/main/java/ch/qos/logback/core/model/ModelConstants.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
*/
1414
package ch.qos.logback.core.model;
1515

16+
import ch.qos.logback.core.CoreConstants;
17+
1618
public class ModelConstants {
1719

1820

1921
public static final String DEBUG_SYSTEM_PROPERTY_KEY = "logback.debug";
20-
public static final String NULL_STR = "null";
22+
public static final String NULL_STR = CoreConstants.NULL_STR;
2123

2224
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.core.encoder;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
class JsonEscapeUtilTest {
22+
23+
@Test
24+
public void testEscapeCodes() {
25+
assertEquals("\\u0001", JsonEscapeUtil.ESCAPE_CODES[1]);
26+
assertEquals("\\u0005", JsonEscapeUtil.ESCAPE_CODES[5]);
27+
assertEquals("\\b", JsonEscapeUtil.ESCAPE_CODES[8]);
28+
assertEquals("\\t", JsonEscapeUtil.ESCAPE_CODES[9]);
29+
assertEquals("\\n", JsonEscapeUtil.ESCAPE_CODES[0x0A]);
30+
assertEquals("\\u000B", JsonEscapeUtil.ESCAPE_CODES[0x0B]);
31+
assertEquals("\\f", JsonEscapeUtil.ESCAPE_CODES[0x0C]);
32+
assertEquals("\\r", JsonEscapeUtil.ESCAPE_CODES[0x0D]);
33+
assertEquals("\\u000E", JsonEscapeUtil.ESCAPE_CODES[0x0E]);
34+
35+
assertEquals("\\u001A", JsonEscapeUtil.ESCAPE_CODES[0x1A]);
36+
}
37+
38+
@Test
39+
public void testEscapeString() {
40+
assertEquals("abc", JsonEscapeUtil.jsonEscapeString("abc"));
41+
assertEquals("{world: \\\"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
42+
assertEquals("{world: "+'\\'+'"'+"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
43+
}
44+
}

0 commit comments

Comments
 (0)