Skip to content

Commit

Permalink
ongoing work on JsonEscapeUtil
Browse files Browse the repository at this point in the history
Signed-off-by: Ceki Gulcu <[email protected]>
  • Loading branch information
ceki committed May 1, 2023
1 parent 3d2c108 commit dadebfe
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package ch.qos.logback.classic.encoder;public class JsonEncoder {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package ch.qos.logback.classic.encoder;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.encoder.JsonEncoderBase;
import ch.qos.logback.core.util.DirectJson;

import java.util.ArrayList;
import java.util.List;

/**
* This is a concrete JsonEncoder for {@link ILoggingEvent} that emits fields according to object's configuration.
* It is partially imported from <a href="https://github.com/hkupty/penna">penna</a>, but adapted to logback's structure.
*
* @author Henry John Kupty
*/
public class JsonEncoder extends JsonEncoderBase<ILoggingEvent> {


// Excerpt below imported from
// ch.qos.logback.contrib.json.classic.JsonLayout
public static final String TIMESTAMP_ATTR_NAME = "timestamp";
public static final String LEVEL_ATTR_NAME = "level";
public static final String MARKERS_ATTR_NAME = "tags";
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 FORMATTED_MESSAGE_ATTR_NAME = "message";
public static final String MESSAGE_ATTR_NAME = "raw-message";
public static final String EXCEPTION_ATTR_NAME = "exception";
public static final String CONTEXT_ATTR_NAME = "context";

protected boolean includeLevel;
protected boolean includeThreadName;
protected boolean includeMDC;
protected boolean includeLoggerName;
protected boolean includeFormattedMessage;
protected boolean includeMessage;
protected boolean includeException;
protected boolean includeContextName;

private final List<Emitter<ILoggingEvent>> emitters;


public JsonEncoder() {
super();

emitters = new ArrayList<>();
this.includeLevel = true;
this.includeThreadName = true;
this.includeMDC = true;
this.includeLoggerName = true;
this.includeFormattedMessage = true;
this.includeException = true;
this.includeContextName = true;
}

//protected = new DirectJson();


public void writeMessage(DirectJson jsonWriter, ILoggingEvent event) {
jsonWriter.writeStringValue(MESSAGE_ATTR_NAME, event.getMessage());
}

public void writeFormattedMessage(DirectJson jsonWriter, ILoggingEvent event) {
jsonWriter.writeStringValue(FORMATTED_MESSAGE_ATTR_NAME, event.getFormattedMessage());
}

public void writeLogger(DirectJson jsonWriter, ILoggingEvent event) {
jsonWriter.writeStringValue(LOGGER_ATTR_NAME, event.getLoggerName());
}

public void writeThreadName(DirectJson jsonWriter, ILoggingEvent event) {
jsonWriter.writeStringValue(THREAD_ATTR_NAME, event.getThreadName());
}

public void writeLevel(DirectJson jsonWriter, ILoggingEvent event) {
jsonWriter.writeStringValue(LEVEL_ATTR_NAME, event.getLevel().levelStr);
}


public void writeMarkers(DirectJson jsonWriter, ILoggingEvent event) {
var markers = event.getMarkerList();
if (!markers.isEmpty()) {
jsonWriter.openArray(MARKERS_ATTR_NAME);
for (var marker : markers) {
jsonWriter.writeString(marker.getName());
jsonWriter.writeSep();
}
// Close array will overwrite the last "," in the buffer, so we are OK
jsonWriter.closeArray();
jsonWriter.writeSep();
}
}

public void writeMdc(DirectJson jsonWriter, ILoggingEvent event) {
var mdc = event.getMDCPropertyMap();
if (!mdc.isEmpty()) {
jsonWriter.openObject(MDC_ATTR_NAME);
for (var entry : mdc.entrySet()) {
jsonWriter.writeStringValue(entry.getKey(), entry.getValue());
}
jsonWriter.closeObject();
jsonWriter.writeSep();
}
}

private void buildEmitterList() {
// This method should be re-entrant and allow for reconfiguring the emitters if something change;
emitters.clear();

// TODO figure out order
if (includeLevel) emitters.add(this::writeLevel);
if (includeMDC) emitters.add(this::writeMdc);
if (includeMessage) emitters.add(this::writeMessage);
if (includeFormattedMessage) emitters.add(this::writeFormattedMessage);
if (includeThreadName) emitters.add(this::writeThreadName);
if (includeLoggerName) emitters.add(this::writeLogger);
// TODO add fields missing:
// context
// exception
// custom data
// marker
}

@Override
public byte[] encode(ILoggingEvent event) {
if (emitters.isEmpty()) {
buildEmitterList();
}
DirectJson jsonWriter = new DirectJson();
jsonWriter.openObject();

for (var emitter: emitters) {
emitter.write(jsonWriter, event);
}

jsonWriter.closeObject();
return jsonWriter.flush();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
package ch.qos.logback.core;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class CoreConstants {

final public static String DISABLE_SERVLET_CONTAINER_INITIALIZER_KEY = "logbackDisableServletContainerInitializer";
Expand Down Expand Up @@ -107,6 +110,8 @@ public class CoreConstants {
*/
public static final String[] EMPTY_STRING_ARRAY = new String[] {};

public static final Charset UTF_8_CHARSET = StandardCharsets.UTF_8;

/**
* An empty Class array.
*/
Expand All @@ -129,6 +134,7 @@ public class CoreConstants {
public static final char DASH_CHAR = '-';
public static final String DEFAULT_VALUE_SEPARATOR = ":-";

public static final String NULL_STR = "null";
/**
* Number of rows before in an HTML table before, we close the table and create
* a new one
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/

package ch.qos.logback.core.encoder;

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
// %x5C / ; \ reverse solidus U+005C
// %x2F / ; / solidus U+002F

// %x62 / ; b backspace U+0008
// %x74 / ; t tab U+0009
// %x6E / ; n line feed U+000A
// %x66 / ; f form feed U+000C
// %x72 / ; r carriage return U+000D

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

switch(c) {
case 0x08: ESCAPE_CODES[c] = "\\b";
break;
case 0x09: ESCAPE_CODES[c] = "\\t";
break;
case 0x0A: ESCAPE_CODES[c] = "\\n";
break;
case 0x0C: ESCAPE_CODES[c] = "\\f";
break;
case 0x0D: ESCAPE_CODES[c] = "\\r";
break;
default:
ESCAPE_CODES[c] = getEscapeCodeBelowASCII32(c);
}
}
}
static String getEscapeCodeBelowASCII32(char c) {
if(c > 32) {
throw new IllegalArgumentException("input must be less than 32");
}

StringBuilder sb = new StringBuilder(6);
sb.append("\\u00");

int highPart = c >> 4;
sb.append(HEXADECIMALS_TABLE[highPart]);

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)
return "\\\"";
if(c == 0x5C)
return "\\/";

return null;
}

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

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

return sb.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
*/
package ch.qos.logback.core.model;

import ch.qos.logback.core.CoreConstants;

public class ModelConstants {


public static final String DEBUG_SYSTEM_PROPERTY_KEY = "logback.debug";
public static final String NULL_STR = "null";
public static final String NULL_STR = CoreConstants.NULL_STR;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/

package ch.qos.logback.core.encoder;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class JsonEscapeUtilTest {

@Test
public void testEscapeCodes() {
assertEquals("\\u0001", JsonEscapeUtil.ESCAPE_CODES[1]);
assertEquals("\\u0005", JsonEscapeUtil.ESCAPE_CODES[5]);
assertEquals("\\b", JsonEscapeUtil.ESCAPE_CODES[8]);
assertEquals("\\t", JsonEscapeUtil.ESCAPE_CODES[9]);
assertEquals("\\n", JsonEscapeUtil.ESCAPE_CODES[0x0A]);
assertEquals("\\u000B", JsonEscapeUtil.ESCAPE_CODES[0x0B]);
assertEquals("\\f", JsonEscapeUtil.ESCAPE_CODES[0x0C]);
assertEquals("\\r", JsonEscapeUtil.ESCAPE_CODES[0x0D]);
assertEquals("\\u000E", JsonEscapeUtil.ESCAPE_CODES[0x0E]);

assertEquals("\\u001A", JsonEscapeUtil.ESCAPE_CODES[0x1A]);
}

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

0 comments on commit dadebfe

Please sign in to comment.