Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "LoggingFormats"
uuid = "98105f81-4425-4516-93fd-1664fb551ab6"
version = "1.1.0"
version = "1.2.0"

[deps]
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ julia> with_logger(FormatLogger(LoggingFormats.JSON(; recursive=true), stderr))
```

If it encounters something which does not have a defined `StructTypes.StructType` to use
for serializing to JSON, it will fallback to converting the objects to strings, like the default `recursive=false` option does.
for serializing to JSON, it will fallback to converting the objects to strings, like the default `recursive=false` option does. Handles the key `exception` specially, by printing errors and stacktraces using `Base.showerror`.

```julia
julia> f() = try
throw(ArgumentError("Bad input"))
catch e
@error "Input error" exception=(e, catch_backtrace())
end

julia> with_logger(f, FormatLogger(LoggingFormats.JSON(; recursive=true), stderr))
{"level":"error","msg":"Input error","module":"Main","file":"REPL[2]","line":4,"group":"REPL[2]","id":"Main_a226875f","kwargs":{"exception":"ERROR: ArgumentError: Bad input\nStacktrace:\n [1] f()\n @ Main ./REPL[2]:2\n [2] with_logstate(f::Function, logstate::Any)\n @ Base.CoreLogging ./logging.jl:511\n [3] with_logger(f::Function, logger::FormatLogger)\n @ Base.CoreLogging ./logging.jl:623\n [4] top-level scope\n @ REPL[3]:1\n"}}
```

## `LogFmt`: Format log events as logfmt

Expand Down
15 changes: 13 additions & 2 deletions src/LoggingFormats.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ end
transform(::Type{String}, v) = string(v)
transform(::Type{Any}, v) = v

# Use key information, then lower to 2-arg transform
function transform(::Type{T}, key, v) where {T}
key == :exception || return transform(T, v)
if v isa Tuple && length(v) == 2 && v[1] isa Exception
e, bt = v
msg = sprint(Base.display_error, e, bt)
return transform(T, msg)
end
return transform(T, sprint(showerror, v))
end

function JSONLogMessage{T}(args) where {T}
JSONLogMessage{T}(
lvlstr(args.level),
Expand All @@ -79,7 +90,7 @@ function JSONLogMessage{T}(args) where {T}
args.line,
args.group === nothing ? nothing : string(args.group),
args.id === nothing ? nothing : string(args.id),
Dict{String,T}(string(k) => transform(T, v) for (k, v) in args.kwargs)
Dict{String,T}(string(k) => transform(T, k, v) for (k, v) in args.kwargs)
)
end
StructTypes.StructType(::Type{<:JSONLogMessage}) = StructTypes.OrderedStruct()
Expand All @@ -98,7 +109,7 @@ function (j::JSON)(io, args)
JSON3.write(io, logmsg)
catch e
fallback_msg = JSONLogMessage{String}(args)
fallback_msg.kwargs["LoggingFormats.FormatError"] = sprint(Base.showerror, e)
fallback_msg.kwargs["LoggingFormats.FormatError"] = sprint(showerror, e)
JSON3.write(io, fallback_msg)
end
else
Expand Down
44 changes: 38 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ end
@test json.kwargs.y == Dict(:hi => Dict(:hi2 => [1,2]))

# Fallback to strings
io = IOBuffer()
with_logger(FormatLogger(JSON(; recursive=true), io)) do
y = (1, 2)
@info "info msg" x = [1, 2, 3] y = Dict("hi" => NoStructTypeDefined(1))
end
json = JSON3.read(seekstart(io))
io = IOBuffer()
with_logger(FormatLogger(JSON(; recursive=true), io)) do
y = (1, 2)
@info "info msg" x = [1, 2, 3] y = Dict("hi" => NoStructTypeDefined(1))
end
json = JSON3.read(seekstart(io))
@test json.level == "info"
@test json.msg == "info msg"
@test json.module == "Main"
Expand All @@ -129,6 +129,38 @@ end
@test all(h -> occursin(h, y), must_have) # avoid issues with printing changing with versions
@test json.kwargs[Symbol("LoggingFormats.FormatError")] == "ArgumentError: NoStructTypeDefined doesn't have a defined `StructTypes.StructType`"

# Test logging exceptions
for recursive in (false, true)
# no stacktrace
io = IOBuffer()
with_logger(FormatLogger(JSON(; recursive=recursive), io)) do
try
throw(ArgumentError("no"))
catch e
@error "Oh no" exception = e
end
end
logs = JSON3.read(seekstart(io))
@test logs["msg"] == "Oh no"
@test logs["kwargs"]["exception"] == "ArgumentError: no"

# stacktrace
io = IOBuffer()
with_logger(FormatLogger(JSON(; recursive=recursive), io)) do
try
throw(ArgumentError("no"))
catch e
@error "Oh no" exception = (e, catch_backtrace())
end
end
logs = JSON3.read(seekstart(io))
@test logs["msg"] == "Oh no"

@test occursin("ArgumentError: no", logs["kwargs"]["exception"])
# Make sure we get a stacktrace out:
@test occursin(r"ArgumentError: no\nStacktrace:\s* \[1\]",
logs["kwargs"]["exception"])
end
end

@testset "logfmt" begin
Expand Down