From 34add1a7290bd16fbb6aee4086b6c56fad7c9287 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Sat, 4 Jan 2025 23:01:17 -0600 Subject: [PATCH] Add the ability to filter by log level and namespace. --- slfmt.nimble | 4 +-- src/slfmt.nim | 89 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/slfmt.nimble b/slfmt.nimble index eca3386..11dc860 100644 --- a/slfmt.nimble +++ b/slfmt.nimble @@ -1,6 +1,6 @@ # Package -version = "0.1.1" +version = "0.1.2" author = "Jonathan Bernard" description = "Small utility to pretty-print strucutured logs." license = "MIT" @@ -10,5 +10,5 @@ bin = @["slfmt"] # Dependencies -requires "nim >= 2.2.0" +requires @["nim >= 2.2.0", "docopt"] requires "timeutils" diff --git a/src/slfmt.nim b/src/slfmt.nim index 3c111f9..1a3abca 100644 --- a/src/slfmt.nim +++ b/src/slfmt.nim @@ -1,19 +1,34 @@ -import std/[json, sequtils, streams, strutils, terminal, times] +import std/[json, options, sequtils, streams, strutils, terminal, times] import timeutils -#import docopt +import docopt -const VERSION = "0.1.0" +from std/logging import Level -# const USAGE = """Usage: -# slfmt -# -# Options: -# -# -h, --help Print this usage and help information +const VERSION = "0.1.2" + +const USAGE = """Usage: + slfmt [options] + +Options: + + -h, --help Print this usage and help information + -l, --log-level Only show log events at or above this level + -n, --namespace Only show log events from this namespace +""" const fieldDisplayOrder = @[ "scope", "level", "ts", "code", "sid", "sub", "msg", "err", "stack", "method", "args"] +func parseLogLevel(s: string): Level = + case s.toUpper + of "DEBUG": result = Level.lvlDebug + of "INFO": result = Level.lvlInfo + of "NOTICE": result = Level.lvlNotice + of "WARN": result = Level.lvlWarn + of "ERROR": result = Level.lvlError + of "FATAL": result = Level.lvlFatal + else: result = Level.lvlAll + func decorate( s: string, fg = fgDefault, @@ -42,38 +57,60 @@ proc formatField(name: string, value: JsonNode): string = of "err": strVal = decorate(value.getStr, fgRed) of "msg": strVal = decorate(value.getStr, fgYellow) of "stack": strVal = decorate(value.getStr, fgBlack, {styleBright}) - else: strVal = pretty(value) + else: + if value.kind == JString: strVal = decorate(value.getStr) + else: strVal = pretty(value) let valLines = splitLines(strVal) if name.len > 10 or strVal.len + 16 > terminalWidth() or valLines.len > 1: result &= "\n" & valLines.mapIt(" " & it).join("\n") & "\n" else: result &= strVal & "\n" -proc prettyPrintFormat(logLine: string): string = - try: - var logJson = parseJson(logLine) +proc prettyPrintFormat(logJson: JsonNode): string = + result = '-'.repeat(terminalWidth()) & "\n" - result = '-'.repeat(terminalWidth()) & "\n" + # Print the known fields in order first + for f in fieldDisplayOrder: + if logJson.hasKey(f): + result &= formatField(f, logJson[f]) + logJson.delete(f) - # Print the known fields in order first - for f in fieldDisplayOrder: - if logJson.hasKey(f): - result &= formatField(f, logJson[f]) - logJson.delete(f) + # Print the rest of the fields + for (key, val) in pairs(logJson): result &= formatField(key, val) - # Print the rest of the fields - for (key, val) in pairs(logJson): result &= formatField(key, val) + result &= "\n" - result &= "\n" - - except ValueError, JsonParsingError: - result = logLine +proc parseLogLine(logLine: string): JsonNode = + result = parseJson(logLine) when isMainModule: try: + let args = docopt(USAGE, version = VERSION) + + let logLevel = + if args["--log-level"]: some(parseLogLevel($args["--log-level"])) + else: none[Level]() + + let namespace = + if args["--namespace"]: some($args["--namespace"]) + else: none[string]() + var line: string = "" let sin = newFileStream(stdin) - while(sin.readLine(line)): stdout.writeLine(prettyPrintFormat(line)) + while(sin.readLine(line)): + try: + let logJson = parseLogLine(line) + + if logLevel.isSome and logJson.hasKey("level"): + let lvl = parseLogLevel(logJson["level"].getStr) + if lvl < logLevel.get: continue + + if namespace.isSome and logJson.hasKey("scope"): + if not logJson["scope"].getStr.startsWith(namespace.get): continue + + stdout.writeLine(prettyPrintFormat(logJson)) + except ValueError, JsonParsingError: + stdout.writeLine(line) except: stderr.writeLine("slfmt - FATAL: " & getCurrentExceptionMsg()) stderr.writeLine(getCurrentException().getStackTrace())