question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Stackdriver - Structured logging

See original GitHub issue

Tinylog 2.3.1

I want to define a console log to push logs from k8s to google stackdriver with structured logging json format.

https://cloud.google.com/logging/docs/structured-logging

I have defined console log format to output json to console but curly braces are not being escaped properly

#console
writer2=console
writer2.format=\\{\"timestamp\":\"{date:yyyy-MM-dd HH:mm:ss.SSS}\",\"severity\":\"{level}\",\"threadId\":\"{thread}\",\"file\":\"{file}:{line}\",\"message\":\"{message}\"\\}

Actual: \"timestamp":"2021-06-01 22:01:51.137","severity":"DEBUG","threadId":"main","file":"HikariConfig.java:1098","message":"test message"\ Expected: {"timestamp":"2021-06-01 22:01:51.137","severity":"DEBUG","threadId":"main","file":"HikariConfig.java:1098","message":"test message"}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
dbacinskicommented, Jun 2, 2021

I ended up creating console json writer based on JsonWriter:

#console
writer2=console json
writer2.level=debug
writer2.field.severity=level
writer2.field.timestamp={date:yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
writer2.field.thread=thread
writer2.field.ref={file}:{line}
writer2.field.file=file
writer2.field.class=class
writer2.field.message=message

ConsoleJsonWriter:

package com.example.logging

import org.tinylog.Level
import org.tinylog.core.LogEntry
import org.tinylog.core.LogEntryValue
import org.tinylog.pattern.FormatPatternParser
import org.tinylog.pattern.Token
import org.tinylog.writers.Writer
import java.io.IOException
import java.util.*
import kotlin.collections.HashMap

class ConsoleJsonWriter(properties: Map<String?, String?>) : Writer {
    private var builder: StringBuilder? = java.lang.StringBuilder(BUFFER_SIZE)
    private val jsonProperties: Map<String, Token> = createTokens(properties)
    private var errorLevel: Level = Level.ERROR

    override fun getRequiredLogEntryValues(): MutableCollection<LogEntryValue> {
        val values: MutableCollection<LogEntryValue> = EnumSet.noneOf(LogEntryValue::class.java)
        for (token in jsonProperties.values) {
            values.addAll(token.requiredLogEntryValues)
        }
        return values
    }

    override fun write(logEntry: LogEntry) {
        val builder: StringBuilder = if (this.builder == null) {
            StringBuilder()
        } else {
            val builder = this.builder!!
            builder.setLength(0)
            builder
        }
        addJsonObject(logEntry, builder)
        val data = builder.toString()
        if (logEntry.level.ordinal < errorLevel.ordinal) {
            print("$data$NEW_LINE")
        } else {
            System.err.print("$data$NEW_LINE")
        }
    }

    override fun flush() {
    }

    override fun close() {
    }

    /**
     * Prepares and adds a Json Object. Also escapes special characters.
     *
     * @param logEntry LogEntry with information for token
     * @param builder  Target for the created the JSON object
     */
    private fun addJsonObject(logEntry: LogEntry, builder: java.lang.StringBuilder) {
        builder.append("{")
        val tokenEntries = jsonProperties.values.toTypedArray()
        val fields = jsonProperties.keys.toTypedArray()
        for (i in tokenEntries.indices) {
            builder.append("\"").append(fields[i]).append("\":\"")
            val start = builder.length
            val token = tokenEntries[i]
            token.render(logEntry, builder)
            escapeCharacter("\\", "\\\\", builder, start)
            escapeCharacter("\"", "\\\"", builder, start)
            escapeCharacter(NEW_LINE, "\\n", builder, start)
            escapeCharacter("\t", "\\t", builder, start)
            escapeCharacter("\b", "\\b", builder, start)
            escapeCharacter("\u000C", "\\f", builder, start)
            escapeCharacter("\n", "\\n", builder, start)
            escapeCharacter("\r", "\\r", builder, start)
            builder.append("\"")
            builder.append(",")
        }
        builder.deleteCharAt(builder.lastIndexOf(","));
        builder.append("}")
    }

    private fun escapeCharacter(character: String, escapeWith: String, stringBuilder: java.lang.StringBuilder,
                                startIndex: Int) {
        var index = stringBuilder.indexOf(character, startIndex)
        while (index != -1) {
            stringBuilder.replace(index, index + character.length, escapeWith)
            index = stringBuilder.indexOf(character, index + escapeWith.length)
        }
    }

    companion object {
        private val NEW_LINE = System.getProperty("line.separator")
        private const val BUFFER_SIZE = 1024
        private const val FIELD_PREFIX = "field."

        private fun createTokens(properties: Map<String?, String?>): Map<String, Token> {
            val parser = FormatPatternParser(properties["exception"])
            val tokens: MutableMap<String, Token> = HashMap()
            for ((key, value) in properties) {
                if (key!!.toLowerCase(Locale.ROOT).startsWith(FIELD_PREFIX)) {
                    tokens[key.substring(FIELD_PREFIX.length)] = parser.parse(value)
                }
            }
            return tokens
        }
    }
}

result:

{"severity":"DEBUG","ref":"HikariConfig.java:1098","file":"HikariConfig.java","thread":"main","message":"readOnly........................false","class":"com.zaxxer.hikari.HikariConfig","timestamp":"2021-06-02T16:37:33.979+02:00"}

only annoying thing is that it does not keep attributes order from config and timestamp is going last but it doesn’t matter for Stackdriver

1reaction
pmwmediacommented, Jun 2, 2021
Read more comments on GitHub >

github_iconTop Results From Across the Web

Structured logging - Google Cloud
In Cloud Logging, structured logs refer to log entries that use the jsonPayload field to add structure to their payloads. Structured logging applies...
Read more >
Structured logging in Google Cloud | by minherz - Medium
Structured logging is the practice of implementing a consistent, predetermined message format for application logs that allows them to be treated as data...
Read more >
tracing-stackdriver - crates.io: Rust Package Registry
A tracing Subscriber for communicating Stackdriver-formatted logs. tracing is a scoped, structured logging and diagnostic system based on ...
Read more >
googleapis/nodejs-logging: Node.js client for ... - GitHub
Node.js client for Stackdriver Logging: Store, search, analyze, monitor, ... The LogSync class helps users easily write context-rich structured logs to ...
Read more >
JSON format for Stackdriver logging in Google Kubernetes ...
From the information you provided I guess fluentd is passing your whole JSON as as a jsonpayload as a logEntry and providing the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found