Skip to content

Commit

Permalink
+ timestamp goes before message in json element
Browse files Browse the repository at this point in the history
+ internal rename
  • Loading branch information
q3769 committed Oct 8, 2023
1 parent 36adf2e commit e40c69a
Show file tree
Hide file tree
Showing 23 changed files with 194 additions and 224 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ A stand-alone log engine in the meantime, it is designed to be adaptable for ser
The output becomes:

```
{"message":"Hello, world!","timestamp":"2023-10-04T09:06:08.2921769-05:00","level":"INFO","callerClass":"elf4j.engine.Main"}
{"message":"It's a beautiful day","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"TRACE","callerClass":"elf4j.engine.Main"}
{"message":"... no matter on what level you say it","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"INFO","callerClass":"elf4j.engine.Main"}
{"message":"Houston, we do not have a problem but let's do a drill","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"WARN","callerClass":"elf4j.engine.Main"}
{"message":"","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"ERROR","callerClass":"elf4j.engine.Main","exception":"java.lang.Exception: This is a drill\r\n\tat elf4j.engine.Main.main(Main.java:43)\r\n"}
{"message":"i.e. Throwable always comes first, then the following optional message and arguments work as usual","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"INFO","callerClass":"elf4j.engine.Main","exception":"java.lang.Exception: This is a drill\r\n\tat elf4j.engine.Main.main(Main.java:43)\r\n"}
{"message":"Not a practical example but now the severity level is DEBUG","timestamp":"2023-10-04T09:06:08.2951796-05:00","level":"DEBUG","callerClass":"elf4j.engine.Main"}
{"timestamp":"2023-10-07T20:11:44.0345848-05:00","message":"Hello, world!","level":"INFO","callerClass":"elf4j.engine.Main"}
{"timestamp":"2023-10-07T20:11:44.0375856-05:00","message":"It's a beautiful day","level":"TRACE","callerClass":"elf4j.engine.Main"}
{"timestamp":"2023-10-07T20:11:44.038585-05:00","message":"... no matter on what level you say it","level":"INFO","callerClass":"elf4j.engine.Main"}
{"timestamp":"2023-10-07T20:11:44.038585-05:00","message":"Houston, we do not have a problem but let's do a drill","level":"WARN","callerClass":"elf4j.engine.Main"}
{"timestamp":"2023-10-07T20:11:44.038585-05:00","message":"","level":"ERROR","callerClass":"elf4j.engine.Main","exception":"java.lang.Exception: This is a drill\r\n\tat elf4j.engine.Main.main(Main.java:43)\r\n"}
{"timestamp":"2023-10-07T20:11:44.038585-05:00","message":"i.e. Throwable always comes first, then the following optional message and arguments work as usual","level":"INFO","callerClass":"elf4j.engine.Main","exception":"java.lang.Exception: This is a drill\r\n\tat elf4j.engine.Main.main(Main.java:43)\r\n"}
{"timestamp":"2023-10-07T20:11:44.038585-05:00","message":"Not a practical example but now the severity level is DEBUG","level":"DEBUG","callerClass":"elf4j.engine.Main"}
```

The JSON pattern can be configured to pretty-print format, and/or mixed with other patterns.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

<groupId>io.github.elf4j</groupId>
<artifactId>elf4j-engine</artifactId>
<version>13.0.1</version>
<version>13.0.2</version>
<packaging>jar</packaging>
<name>elf4j-engine</name>
<description>A stand-alone Java log engine implementing the ELF4J (Easy Logging Facade for Java) API</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
*
*/
@Value
class ClassPattern implements LogPattern {
class ClassElement implements PatternElement {
private static final DisplayOption DEFAULT_DISPLAY_OPTION = DisplayOption.SIMPLE;
@NonNull DisplayOption classDisplayOption;

Expand All @@ -45,18 +45,15 @@ class ClassPattern implements LogPattern {
* @return converted patternSegment object
*/
@Nonnull
public static ClassPattern from(@NonNull String patternSegment) {
if (!PatternType.CLASS.isTargetTypeOf(patternSegment)) {
throw new IllegalArgumentException("patternSegment: " + patternSegment);
}
return new ClassPattern(PatternType.getPatternDisplayOption(patternSegment)
public static ClassElement from(@NonNull String patternSegment) {
return new ClassElement(ElementType.getPatternDisplayOption(patternSegment)
.map(displayOption -> DisplayOption.valueOf(displayOption.toUpperCase()))
.orElse(DEFAULT_DISPLAY_OPTION));
}

/**
* @return <code>false</code> assuming the logger's owner class is the same as the caller class. Therefore, unlike
* the {@link MethodPattern}, it does not take a stack trace walk to locate the caller class - the owner
* the {@link MethodElement}, it does not take a stack trace walk to locate the caller class - the owner
* class is taken instead.
*/
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,62 +35,62 @@
/**
*
*/
enum PatternType {
enum ElementType {
/**
*
*/
TIMESTAMP {
@Override
LogPattern translate(String patternSegment) {
return TimestampPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return TimestampElement.from(patternElement);
}
},
/**
*
*/
LEVEL {
@Override
LogPattern translate(String patternSegment) {
return LevelPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return LevelElement.from(patternElement);
}
},
/**
*
*/
THREAD {
@Override
LogPattern translate(String patternSegment) {
return ThreadPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return ThreadElement.from(patternElement);
}
},
/**
*
*/
CLASS {
@Override
LogPattern translate(String patternSegment) {
return ClassPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return ClassElement.from(patternElement);
}
},
/**
*
*/
METHOD {
@Override
LogPattern translate(String patternSegment) {
return MethodPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return MethodElement.from(patternElement);
}
},
FILENAME {
@Override
LogPattern translate(String patternSegment) {
return FileNamePattern.from(patternSegment);
PatternElement parse(String patternElement) {
return FileNameElement.from(patternElement);
}
},
LINENUMBER {
@Override
LogPattern translate(String patternSegment) {
return LineNumberPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return LineNumberElement.from(patternElement);
}
},

Expand All @@ -99,119 +99,124 @@ LogPattern translate(String patternSegment) {
*/
MESSAGE {
@Override
LogPattern translate(String patternSegment) {
return MessageAndExceptionPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return MessageAndExceptionElement.from(patternElement);
}
},
/**
*
*/
JSON {
@Override
LogPattern translate(String patternSegment) {
return JsonPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return JsonElement.from(patternElement);
}
},
SYSPROP {
@Override
LogPattern translate(String patternSegment) {
return SystemPropertyPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return SystemPropertyElement.from(patternElement);
}
},
SYSENV {
@Override
LogPattern translate(String patternSegment) {
return SystemEnvironmentPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return SystemEnvironmentElement.from(patternElement);
}
},
/**
*
*/
VERBATIM {
@Override
LogPattern translate(String patternSegment) {
return VerbatimPattern.from(patternSegment);
PatternElement parse(String patternElement) {
return VerbatimElement.from(patternElement);
}
};
private static final EnumSet<PatternType> PATTERN_TYPES = EnumSet.allOf(PatternType.class);
private static final EnumSet<PatternType> PREDEFINED_PATTERN_TYPES = EnumSet.complementOf(EnumSet.of(VERBATIM));
private static final EnumSet<ElementType> PREDEFINED_TYPES = EnumSet.complementOf(EnumSet.of(VERBATIM));

/**
* @param patternSegment
* entire text of an individual pattern segment, including pattern segment name and possibly options
* @return the option portion of the pattern segment text if present; otherwise, empty Optional
* @param patternElement
* entire text of an individual pattern element, including pattern element name and possibly options
* @return the option portion of the pattern element text if present; otherwise, empty Optional
*/
static Optional<String> getPatternDisplayOption(@NonNull String patternSegment) {
String[] elements = patternSegment.split(":", 2);
static Optional<String> getPatternDisplayOption(@NonNull String patternElement) {
String[] elements = patternElement.split(":", 2);
return elements.length == 1 ? Optional.empty() : Optional.of(elements[1].trim());
}

/**
* @param pattern
* entire layout pattern text of a writer, including one or more individual pattern segments. Predefined
* pattern segment texts in curly braces - e.g. {timestamp}, {level}, or {json} - will be parsed into
* pattern segment objects who extract and render specific log data to form the final log message. Undefined
* entire layout pattern text of a writer, including one or more individual pattern elements. Predefined
* pattern element texts in curly braces - e.g. {timestamp}, {level}, or {json} - will be parsed into
* pattern element objects who extract and render specific log data to form the final log message. Undefined
* pattern texts, in or outside curly braces, are to be rendered verbatim in the final log message.
* @return ordered list of individual patterns forming the entire layout pattern of the writer
*/
static @NonNull List<LogPattern> parsePatterns(@NonNull String pattern) {
static @NonNull List<PatternElement> parsePattern(@NonNull String pattern) {
if (pattern.trim().isEmpty()) {
throw new IllegalArgumentException("Unexpected blank pattern");
}
List<LogPattern> logPatterns = new ArrayList<>();
List<PatternElement> patternElements = new ArrayList<>();
final int length = pattern.length();
int i = 0;
while (i < length) {
String segment;
String element;
int j;
if (pattern.charAt(i) == '{') {
j = pattern.indexOf('}', i);
if (j != -1) {
segment = pattern.substring(i + 1, j);
element = pattern.substring(i + 1, j);
i = j + 1;
} else {
segment = pattern.substring(i);
element = pattern.substring(i);
i = length;
}
patternElements.add(parsePredefinedPatternELement(element));
} else {
j = pattern.indexOf('{', i);
if (j != -1) {
segment = pattern.substring(i, j);
element = pattern.substring(i, j);
i = j;
} else {
segment = pattern.substring(i);
element = pattern.substring(i);
i = length;
}
patternElements.add(VERBATIM.parse(element));
}
logPatterns.add(parsePattern(segment));
}
return logPatterns;
return patternElements;
}

private static LogPattern parsePattern(String patternSegment) {
return PATTERN_TYPES.stream()
.filter(type -> type.isTargetTypeOf(patternSegment))
private static String getPatternElementName(String patternElement) {
return patternElement.split(":", 2)[0].trim();
}

private static PatternElement parsePredefinedPatternELement(String predefinedPatternElement) {
return PREDEFINED_TYPES.stream()
.filter(type -> type.isTargetTypeOf(predefinedPatternElement))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("pattern segment: '" + patternSegment + "'"))
.translate(patternSegment);
.orElseThrow(() -> new IllegalArgumentException(
"Predefined pattern element: '" + predefinedPatternElement + "'"))
.parse(predefinedPatternElement);
}

/**
* @param patternSegment
* @param patternElement
* text to translate
* @return pattern segment object of the specified text
* @return pattern element object of the specified text
*/
abstract LogPattern translate(String patternSegment);
abstract PatternElement parse(String patternElement);

/**
* @param patternSegment
* text configuration of an individual pattern segment
* @return true if this pattern segment type is the target type of the specified pattern segment text
* @param patternElement
* text configuration of an individual pattern element
* @return true if this pattern element type is the target type of the specified pattern element text
*/
boolean isTargetTypeOf(String patternSegment) {
private boolean isTargetTypeOf(String patternElement) {
if (this == VERBATIM) {
return PREDEFINED_PATTERN_TYPES.stream().noneMatch(type -> type.isTargetTypeOf(patternSegment));
return PREDEFINED_TYPES.stream().noneMatch(type -> type.isTargetTypeOf(patternElement));
}
return name().equalsIgnoreCase(patternSegment.split(":", 2)[0].trim());
return name().equalsIgnoreCase(getPatternElementName(patternElement));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,15 @@
*
*/
@Value
class FileNamePattern implements LogPattern {
class FileNameElement implements PatternElement {
/**
* @param patternSegment
* text segment to convert
* @return converted patternSegment object
*/
@Nonnull
public static FileNamePattern from(String patternSegment) {
if (!PatternType.FILENAME.isTargetTypeOf(patternSegment)) {
throw new IllegalArgumentException("patternSegment: " + patternSegment);
}
return new FileNamePattern();
public static FileNameElement from(String patternSegment) {
return new FileNameElement();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
*/
@Value
@Builder
class JsonPattern implements LogPattern {
class JsonElement implements PatternElement {
private static final String UTF_8 = StandardCharsets.UTF_8.toString();
private static final int JSON_BYTES_INIT_SIZE = 1024;
private static final String CALLER_DETAIL = "caller-detail";
Expand All @@ -73,20 +73,17 @@ class JsonPattern implements LogPattern {
* to convert
* @return converted patternSegment object
*/
public static JsonPattern from(@NonNull String patternSegment) {
if (!PatternType.JSON.isTargetTypeOf(patternSegment)) {
throw new IllegalArgumentException("patternSegment: " + patternSegment);
}
Optional<String> displayOption = PatternType.getPatternDisplayOption(patternSegment);
public static JsonElement from(@NonNull String patternSegment) {
Optional<String> displayOption = ElementType.getPatternDisplayOption(patternSegment);
if (!displayOption.isPresent()) {
return JsonPattern.builder().build();
return JsonElement.builder().build();
}
Set<String> options =
Arrays.stream(displayOption.get().split(",")).map(String::trim).collect(Collectors.toSet());
if (!DISPLAY_OPTIONS.containsAll(options)) {
throw new IllegalArgumentException("Invalid JSON display option inside: " + options);
}
return JsonPattern.builder()
return JsonElement.builder()
.includeCallerThread(options.contains(CALLER_THREAD))
.includeCallerDetail(options.contains(CALLER_DETAIL))
.prettyPrint(options.contains(PRETTY))
Expand Down Expand Up @@ -114,15 +111,15 @@ public void render(LogEvent logEvent, @NonNull StringBuilder target) {
@Builder
@CompiledJson
static class JsonLogEntry {
CharSequence message;
OffsetDateTime timestamp;
CharSequence message;
String level;
String callerClass;
LogEvent.ThreadValue callerThread;
LogEvent.StackFrameValue callerDetail;
CharSequence exception;

static JsonLogEntry from(@NonNull LogEvent logEvent, @NonNull JsonPattern jsonPattern) {
static JsonLogEntry from(@NonNull LogEvent logEvent, @NonNull JsonElement jsonPattern) {
return JsonLogEntry.builder()
.timestamp(OffsetDateTime.ofInstant(logEvent.getTimestamp(), ZoneId.systemDefault()))
.callerClass(jsonPattern.includeCallerDetail ? null : logEvent.getCallerClassName())
Expand Down
Loading

0 comments on commit e40c69a

Please sign in to comment.