Skip to content

Commit dadebfe

Browse files
committed
ongoing work on JsonEscapeUtil
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
1 parent 3d2c108 commit dadebfe

File tree

6 files changed

+302
-1
lines changed

6 files changed

+302
-1
lines changed
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy