Skip to content

Commit

Permalink
Merge pull request #44 from orangain/refactor-extract
Browse files Browse the repository at this point in the history
Refactor extractions
  • Loading branch information
orangain authored Jul 18, 2024
2 parents 152169c + 2a26c8a commit e7517d6
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 140 deletions.
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
122 changes: 0 additions & 122 deletions src/main/kotlin/io/github/orangain/prettyjsonlog/Extract.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package io.github.orangain.prettyjsonlog.console

import com.fasterxml.jackson.databind.JsonNode
import com.intellij.execution.filters.ConsoleInputFilterProvider
import com.intellij.execution.filters.InputFilter
import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import io.github.orangain.prettyjsonlog.*
import io.github.orangain.prettyjsonlog.json.parseJson
import io.github.orangain.prettyjsonlog.json.prettyPrintJson
import io.github.orangain.prettyjsonlog.logentry.*
import java.time.ZoneId
import java.time.format.DateTimeFormatter

Expand All @@ -32,30 +31,23 @@ class MyConsoleInputFilter : InputFilter {

val timestamp = extractTimestamp(node)
val level = extractLevel(node)
val contentTypeOfLevel = contentTypeOf(level, contentType)
val message = extractMessage(node)
val stackTracePair = extractStackTracePair(node, contentTypeOfLevel)
val stackTrace = extractStackTrace(node)
// .trimEnd('\n') is necessary because of the following reasons:
// - When stackTrace is null or empty, we don't want to add an extra newline.
// - When stackTrace ends with a newline, trimming the last newline makes a folding marker look better.
val coloredMessage = "$level: $message\n${stackTrace ?: ""}".trimEnd('\n')

val jsonString = prettyPrintJson(node)
return mutableListOf(
Pair("[${timestamp?.format(zoneId, timestampFormatter)}] ", contentType),
Pair("$level: $message", contentTypeOfLevel),
stackTracePair,
Pair(coloredMessage, contentTypeOf(level, contentType)),
Pair(
" \n$jsonString$suffixWhitespaces",
" \n$jsonString$suffixWhitespaces", // Adding a space at the end of line makes a folding marker look better.
contentType
), // Add a space to at the end of line to make it look good when folded.
),
)
}

private fun extractStackTracePair(node: JsonNode, contentTypeOfLevel: ConsoleViewContentType): Pair<String, ConsoleViewContentType> {
val stackTrace = extractStackTrace(node)

if (stackTrace?.isNotEmpty() == true) {
return Pair("\n$stackTrace", contentTypeOfLevel)
}
return Pair("", contentTypeOfLevel)
}
}

private fun contentTypeOf(level: Level?, inputContentType: ConsoleViewContentType): ConsoleViewContentType {
Expand Down
52 changes: 52 additions & 0 deletions src/main/kotlin/io/github/orangain/prettyjsonlog/logentry/Level.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.github.orangain.prettyjsonlog.logentry

import com.fasterxml.jackson.databind.JsonNode

enum class Level {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL;

companion object {
fun fromInt(level: Int): Level {
// Use bunyan's level as a reference.
// See: https://github.com/trentm/node-bunyan?tab=readme-ov-file#levels
return when {
level < 20 -> TRACE
level < 30 -> DEBUG
level < 40 -> INFO
level < 50 -> WARN
level < 60 -> ERROR
else -> FATAL
}
}

fun fromString(level: String): Level? {
// Bunyan's levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
// https://github.com/trentm/node-bunyan?tab=readme-ov-file#levels
// Cloud Logging's levels: DEFAULT, DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
// java.util.logging's levels: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE
// https://docs.oracle.com/en/java/javase/21/docs/api/java.logging/java/util/logging/Level.html
return when (level.uppercase()) {
"TRACE", "FINEST", "FINER", "FINE" -> TRACE
"DEBUG", "CONFIG" -> DEBUG
"INFO", "NOTICE" -> INFO
"WARN", "WARNING" -> WARN
"ERROR", "CRITICAL", "SEVERE" -> ERROR
"FATAL", "ALERT", "EMERGENCY" -> FATAL
else -> null // This includes "DEFAULT"
}
}
}
}

private val levelKeys = listOf("level", "severity", "log.level")

fun extractLevel(node: JsonNode): Level? {
return levelKeys.firstNotNullOfOrNull { node.get(it) }?.let { levelNode ->
if (levelNode.isNumber) {
Level.fromInt(levelNode.asInt())
} else {
Level.fromString(levelNode.asText())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.orangain.prettyjsonlog.logentry

import com.fasterxml.jackson.databind.JsonNode

private val messageKeys = listOf("message", "msg", "error.message")

fun extractMessage(node: JsonNode): String? {
return messageKeys.firstNotNullOfOrNull { node.get(it) }?.asText()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.orangain.prettyjsonlog.logentry

import com.fasterxml.jackson.databind.JsonNode

typealias NodeExtractor = (JsonNode) -> JsonNode?
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.orangain.prettyjsonlog.logentry

import com.fasterxml.jackson.databind.JsonNode

private val stackTraceNodeExtractors: List<NodeExtractor> = listOf(
{ it.get("stack_trace") },
{ it.get("exception") },
{ it.get("error.stack_trace") },
{ it.get("err")?.get("stack") },
)

fun extractStackTrace(node: JsonNode): String? {
return stackTraceNodeExtractors.firstNotNullOfOrNull { it(node) }?.asText()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.orangain.prettyjsonlog.logentry

import com.fasterxml.jackson.databind.JsonNode
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

sealed interface Timestamp {
fun format(zoneId: ZoneId, formatter: DateTimeFormatter): String

data class Parsed(val value: Instant) : Timestamp {
override fun format(zoneId: ZoneId, formatter: DateTimeFormatter): String {
return value.atZone(zoneId).format(formatter)
}
}

data class Fallback(val value: String) : Timestamp {
override fun format(zoneId: ZoneId, formatter: DateTimeFormatter): String {
return value
}
}

companion object {
fun fromEpochMilli(value: Long): Parsed {
return Parsed(Instant.ofEpochMilli(value))
}

fun fromString(value: String): Timestamp {
return try {
// Use OffsetDateTime.parse instead of Instant.parse because Instant.parse in JDK <= 11 does not support non-UTC offset like "-05:00".
// See: https://stackoverflow.com/questions/68217689/how-to-use-instant-java-class-to-parse-a-date-time-with-offset-from-utc/68221614#68221614
Parsed(OffsetDateTime.parse(value).toInstant())
} catch (e: DateTimeParseException) {
Fallback(value)
}
}
}
}

private val timestampKeys = listOf("timestamp", "time", "@timestamp")

fun extractTimestamp(node: JsonNode): Timestamp? {

return timestampKeys.firstNotNullOfOrNull { node.get(it) }?.let { timestampNode ->
if (timestampNode.isNumber) {
// We assume that the number is a Unix timestamp in milliseconds.
Timestamp.fromEpochMilli(timestampNode.asLong())
} else {
Timestamp.fromString(timestampNode.asText())
}
}
}
31 changes: 31 additions & 0 deletions src/test/kotlin/io/github/orangain/prettyjsonlog/json/ParseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.orangain.prettyjsonlog.json

import junit.framework.TestCase

class ParseTest : TestCase() {
fun testParseJsonLine() {
val result = parseJson("""{"key": "value"}""")
assertNotNull(result)
val (node, rest) = result!!
assertEquals("""{"key":"value"}""", node.toString())
assertEquals("", rest)
}

fun testParseJsonLineWithSpaces() {
val result = parseJson(""" {"key": "value"} """)
assertNotNull(result)
val (node, rest) = result!!
assertEquals("""{"key":"value"}""", node.toString())
assertEquals(" ", rest)
}

fun testParseBrokenJsonLine() {
val result = parseJson("""{"key": "value" """)
assertNull(result)
}

fun testParseEmptyString() {
val result = parseJson("")
assertNull(result)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.orangain.prettyjsonlog
package io.github.orangain.prettyjsonlog.logentry

import io.github.orangain.prettyjsonlog.json.parseJson
import junit.framework.TestCase
Expand Down

0 comments on commit e7517d6

Please sign in to comment.