Skip to content

Commit

Permalink
ongoing work on JsonEnconder
Browse files Browse the repository at this point in the history
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
  • Loading branch information
ceki committed May 3, 2023
1 parent f7ae802 commit 97e5175
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.encoder.EncoderBase;
import ch.qos.logback.core.util.DirectJson;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
import static ch.qos.logback.core.model.ModelConstants.NULL_STR;

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

public static final String CONTEXT_ATTR_NAME = "context";
public static final String TIMESTAMP_ATTR_NAME = "timestamp";

public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";

public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumbers";


public static final String LEVEL_ATTR_NAME = "level";
public static final String MARKERS_ATTR_NAME = "markers";
public static final String THREAD_ATTR_NAME = "thread";
public static final String MDC_ATTR_NAME = "mdc";
public static final String LOGGER_ATTR_NAME = "logger";
public static final String MESSAGE_ATTR_NAME = "raw-message";
public static final String MESSAGE_ATTR_NAME = "rawMessage";

public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
public static final String KEY_VALUE_PAIRS_ATTR_NAME = "keyValuePairs";

public static final String THROWABLE_ATTR_NAME = "throwable";

private static final char OPEN_OBJ = '{';
private static final char CLOSE_OBJ = '}';
private static final char OPEN_ARR = '[';
private static final char CLOSE_ARR = ']';
private static final char OPEN_ARRAY = '[';
private static final char CLOSE_ARRAY = ']';

private static final char QUOTE = DOUBLE_QUOTE_CHAR;
private static final char SP = ' ';
private static final char ENTRY_SEPARATOR = COLON_CHAR;

private static final char COL_SP = COLON_CHAR+SP;

private static final char VALUE_SEPARATOR = COMMA_CHAR;


Expand All @@ -72,26 +88,130 @@ public byte[] headerBytes() {
public byte[] encode(ILoggingEvent event) {
final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE: DEFAULT_SIZE_WITH_THROWABLE;
StringBuilder sb = new StringBuilder(initialCapacity);
sb.append(OPEN_OBJ);


sb.append(SEQUENCE_NUMBER_ATTR_NAME).append(COL_SP).append(event.getSequenceNumber());
sb.append(VALUE_SEPARATOR);


sb.append(TIMESTAMP_ATTR_NAME).append(COL_SP).append(event.getTimeStamp());
sb.append(VALUE_SEPARATOR);

sb.append(NANOSECONDS_ATTR_NAME).append(COL_SP).append(event.getNanoseconds());
sb.append(VALUE_SEPARATOR);


String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
sb.append(LEVEL_ATTR_NAME).append(COL_SP).append(QUOTE).append(levelStr).append(QUOTE);
sb.append(VALUE_SEPARATOR);

sb.append(THREAD_ATTR_NAME).append(COL_SP).append(QUOTE).append(jsonSafeStr(event.getThreadName())).append(QUOTE);
sb.append(VALUE_SEPARATOR);

sb.append(LOGGER_ATTR_NAME).append(COL_SP).append(QUOTE).append(event.getLoggerName()).append(QUOTE);
sb.append(VALUE_SEPARATOR);

appendMarkers(sb, event);
appendMDC(sb, event);
appendKeyValuePairs(sb, event);

sb.append(MESSAGE_ATTR_NAME).append(COL_SP).append(QUOTE).append(jsonSafeStr(event.getMessage())).append(QUOTE);
sb.append(VALUE_SEPARATOR);

appendArgumentArray(sb, event);

sb.append(CLOSE_OBJ);
return sb.toString().getBytes(UTF_8_CHARSET);
}

private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
List<KeyValuePair> kvpList = event.getKeyValuePairs();
if(kvpList == null || kvpList.isEmpty())
return;

sb.append(KEY_VALUE_PAIRS_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
final int len = kvpList.size();
for(int i = 0; i < len; i++) {
KeyValuePair kvp = kvpList.get(i);
sb.append(QUOTE).append(jsonSafeToString(kvp.key)).append(QUOTE);
sb.append(COL_SP);
sb.append(QUOTE).append(jsonSafeToString(kvp.value)).append(QUOTE);

if(i != len)
sb.append(VALUE_SEPARATOR);
}
sb.append(CLOSE_ARRAY);
}

private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
Object[] argumentArray = event.getArgumentArray();
if(argumentArray == null)
return;

sb.append(ARGUMENT_ARRAY_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
final int len = argumentArray.length;
for(int i = 0; i < len; i++) {
sb.append(QUOTE).append(jsonSafeToString(argumentArray[i])).append(QUOTE);
if(i != len)
sb.append(VALUE_SEPARATOR);
}
sb.append(CLOSE_ARRAY);
}

private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
List<Marker> markerList = event.getMarkerList();
if(markerList == null)
return;

sb.append(MARKERS_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_ARRAY);
final int len = markerList.size();
for(int i = 0; i < len; i++) {
sb.append(QUOTE).append(jsonSafeToString(markerList.get(i))).append(QUOTE);
if(i != len)
sb.append(VALUE_SEPARATOR);
}
sb.append(CLOSE_ARRAY);
}

private String jsonSafeToString(Object o) {
if(o == null)
return NULL_STR;
return jsonEscapeString(o.toString());
}

private String jsonSafeStr(String s) {
if(s == null)
return NULL_STR;
return jsonEscapeString(s);
}


private void appendMDC(StringBuilder sb, ILoggingEvent event) {
Map<String, String> map = event.getMDCPropertyMap();

sb.append(MDC_ATTR_NAME).append(ENTRY_SEPARATOR).append(SP).append(OPEN_OBJ);
if(isNotEmptyMap(map)) {
map.entrySet().stream().forEach(e -> appendMapEntry(sb, e));
}
sb.append(CLOSE_OBJ);

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

void writeStringValue(StringBuilder sb, String attrName, String value) {
sb.append(attrName).append(ENTRY_SEPARATOR).append(SP).append(QUOTE).append(value);
Character c = ' ';
private void appendMapEntry(StringBuilder sb, Map.Entry<String, String> entry) {
if(entry == null)
return;

sb.append(QUOTE).append(jsonSafeToString(entry.getKey())).append(QUOTE).append(COL_SP).append(QUOTE)
.append(jsonSafeToString(entry.getValue())).append(QUOTE);
}

public void writeSep(StringBuilder sb) {
sb.append(',');
boolean isNotEmptyMap(Map map) {
if(map == null)
return false;
return !map.isEmpty();
}

@Override
public byte[] footerBytes() {
return EMPTY_BYTES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ public class JsonEscapeUtil {

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


static final int ESCAPE_CODES_COUNT = 32;

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


// From RFC-8259 page 5

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

static {
for(char c = 0; c < ESCAPE_CODES_COUNT; c++) {
for (char c = 0; c < ESCAPE_CODES_COUNT; c++) {

switch(c) {
case 0x08: ESCAPE_CODES[c] = "\\b";
break;
case 0x09: ESCAPE_CODES[c] = "\\t";
switch (c) {
case 0x08:
ESCAPE_CODES[c] = "\\b";
break;
case 0x09:
ESCAPE_CODES[c] = "\\t";
break;
case 0x0A: ESCAPE_CODES[c] = "\\n";
case 0x0A:
ESCAPE_CODES[c] = "\\n";
break;
case 0x0C: ESCAPE_CODES[c] = "\\f";
case 0x0C:
ESCAPE_CODES[c] = "\\f";
break;
case 0x0D: ESCAPE_CODES[c] = "\\r";
case 0x0D:
ESCAPE_CODES[c] = "\\r";
break;
default:
ESCAPE_CODES[c] = getEscapeCodeBelowASCII32(c);
ESCAPE_CODES[c] = _computeEscapeCodeBelowASCII32(c);
}
}
}
static String getEscapeCodeBelowASCII32(char c) {
if(c > 32) {

// this method should not be called by methods except the static initializer
private static String _computeEscapeCodeBelowASCII32(char c) {
if (c > 32) {
throw new IllegalArgumentException("input must be less than 32");
}

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


return sb.toString();
}

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

static String getObligatoryEscapeCode(char c) {
if(c < 32)
return getEscapeCodeBelowASCII32(c);
if(c == 0x22)
if (c < 32)
return ESCAPE_CODES[c];
if (c == 0x22)
return "\\\"";
if(c == 0x5C)
if (c == 0x5C)
return "\\/";

return null;
}

static String jsonEscapeString(String input) {
static public String jsonEscapeString(String input) {
int length = input.length();
int lenthWithLeeway = (int) (length*1.1);
int lenthWithLeeway = (int) (length * 1.1);

StringBuilder sb = new StringBuilder(lenthWithLeeway);
for(int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
final char c = input.charAt(i);
String escaped = getObligatoryEscapeCode(c);
if(escaped == null)
if (escaped == null)
sb.append(c);
else
else {
sb.append(escaped);
}
}

return sb.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class JsonEscapeUtilTest {

@Test
public void testEscapeCodes() {
public void smokeTestEscapeCodes() {
assertEquals("\\u0001", JsonEscapeUtil.ESCAPE_CODES[1]);
assertEquals("\\u0005", JsonEscapeUtil.ESCAPE_CODES[5]);
assertEquals("\\b", JsonEscapeUtil.ESCAPE_CODES[8]);
Expand All @@ -36,9 +36,23 @@ public void testEscapeCodes() {
}

@Test
public void testEscapeString() {
public void smokeTestEscapeString() {
assertEquals("abc", JsonEscapeUtil.jsonEscapeString("abc"));
assertEquals("{world: \\\"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
assertEquals("{world: "+'\\'+'"'+"world\\\"}", JsonEscapeUtil.jsonEscapeString("{world: \"world\"}"));
}

@Test
public void testEscapingLF() {
String input = "{\nhello: \"wo\nrld\"}";
System.out.println(input);
assertEquals("{\\nhello: "+'\\'+'"'+"wo\\nrld\\\"}", JsonEscapeUtil.jsonEscapeString(input));
}

@Test
public void testEscapingTab() {
String input = "{hello: \"\tworld\"}";
System.out.println(input);
assertEquals("{hello: "+'\\'+'"'+"\\tworld\\\"}", JsonEscapeUtil.jsonEscapeString(input));
}
}

0 comments on commit 97e5175

Please sign in to comment.