-
Notifications
You must be signed in to change notification settings - Fork 180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve server error handling and logging. #189
Comments
WRT |
@quinnj, I think we should treat things like
Likewise the
Agreed, but we need to be careful to only downgrade error importance in the case where the client explicitly closed the connection. If connections on my server are silently failing sometimes, I need to know how often that is happening so I can investigate to see if there is a fixable root cause. e.g. perhaps we should keep a count and/or a seperate log of the times and types of IO errors. |
See JuliaLang/julia#25109 (comment), it looks like the new logging code will help us here. |
@quinnj, you may have noticed this little debug web page that pops up and displays a live view of the connection pool state while you run Lines 14 to 31 in b7b8e0f
Now, this is a nasty hack using a 1 seconds brute-force refresh. I'm not thinking of a full-blown logging system with persistent storage, hooks into CloudWatchLogs etc, just an in-memory thing that is only active if the browser is connected. Like a GUI-mode |
I see there are a few issues regarding logging. The first problem I encountered is that logging can't be turned off by passing web_server = HTTP.Servers.Server((req) -> begin
setup_http_handler(req, req.response)
end, devnull) Because of this, there's a lot of spam, making it hard to follow my own logging -- I get a dump for each css and js file and image referenced in a web page! The best thing would be to be able to choose between logging all requests, only dynamic requests (so no static files, these are a lot and usually not interesting), and no logging. An option would be to have a flag in the |
Also, we need to be able to pass a custom logger. |
Regarding the errors, it could be interesting to handle them in a more explicit way (ie any pottentially failing function returning a Result from ResultTypes.jl or equivalent. It's hard to know where to catch |
I'm not sure where you guys got to with this, but to comment on the logging side of things (and without knowing much about HTTP.jl at all):
connection = Connection(#=...=#)
connection.id = uuid4() # Or whatever
# ...
# Suppose we send a response
@info "HTTP Respose" content=message connection_id=connection.id sequence=transaction.sequence
# Suppose the connection times out. Here's a warning for that:
@warn "Connection timeout" connection_id=connection.id Then you can capture the log records, filtering and grouping them by
For the record, I think that HTTP is an ideal library to motivate the next iteration of stdlib/Logging which needs to focus on a more capable set of backend logging tools. stdlib/Logging can't be developed without motivation from people with "serious" logging needs like HTTP and Genie. See JuliaLang/julia#29567 for related discussion. I'm not sure where prototyping new features should go but MicroLogging works for me and I'm super happy to add collaborators. |
Oh and regarding logging-then-throwing vs throwing-then-logging vs doing both — my take on this FWIW (from JuliaLang/julia#19979 (comment)):
Once JuliaLang/julia#28878 is (presumably) merged this will be easy. |
(Note that in HTTP.jl, Servers.jl is intended to be a low-level minimal HTTP server implementation. Whereas Handlers.jl is intended to add middleware/request routing features.)
As far as logging goes, I think that should be done at a higher layer than Servers.jl e.g.:
There are probably a handful of log-worthy events that can occur in the Servers.jl layer before control is handed to the Hanlders.jl framework. It is probably good to have a way to pass these to the Handlers.jl framework so that they can be logged along with the other logs. e.g.:
|
Right!
Regarding swapping out loggers, disabling logging, etc, this is exactly what the julia logging system is designed for, in the sense that you can implement and install your own In terms of disabling logging, just installing the |
I must admit that I haven't had time to get into the details of the new logging system. If we only need to have one logging module in the Hanlders layer (because all the different backends can be handled by the AbstractLogger interface) that's great!
To what degree does this disable logging? Are we talking macros that evaluate to empty expression For some things the distinction isn't important, but for others is. On a related note: are there any practical limitations on the log event rate or overall log data size? |
The intention is that the cost of a disabled log statement would be a couple of loads, integer compare and branch (when disabled based on log level only). Thus it should be extremely cheap but still a nonzero cost. There are several layers of early testing in order to avoid doing any work if possible. For the full detail of the current early filtering system, see the logging documentation: https://docs.julialang.org/en/v1/stdlib/Logging/index.html#Early-filtering-and-message-handling-1 If you want verbose tracing which is compiled out completely in production, this implies compiling a version of each function with tracing on and with tracing off. There's no real way around this fact and if you do the code elision at the macro level it plays complete havoc with precompilation. Lucikly there's a trick which can be used to dynamically recompile such functions using the julia JIT if you truly need it. I'd be interested to see a use case which truly required this.
This is completely up to the |
That's the runtime cost, but I was thinking more about the cost in terms of loosing potential optimisations. Consider a loop
Right. I understand that this kind of logging in optimisation-critical loops is not the core focus of the logging system. In HTTP.jl we have a macro for fine-grained performance-killing debug logging (which implies recompiling the whole package): Lines 5 to 8 in e0fe578
We could always call the logging system from inside our debug macro. But it might be nice for the logging system to have an optional 1st class way of statically removing logging. This could be as simple as a global const STATIC_LOG_LEVEL. Or it could be a macro that says "everything in this block should be compiled without logs below level x". Something along the lines of
The pattern for this in HTTP.jl is that we have a nested stack type that includes different layers depending on the Lines 592 to 593 in e0fe578
We end up with different versions of request being compiled for different combinations of options, and hopefully, if you select a simple set of options (retry=false , redirect=false , status_exception=false ) you end up with a very simple stack that can be completely inlined.Lines 388 to 399 in e0fe578
Ok, nice. It would be cool to have an example AbstractLogger like:
|
Time to bring back FIFOBuffer? 😉 |
Here's a trick you can use at a module level to dynamically recompile the module and avoid having to edit the package source code: module A
# A function returning a constant boolean which will be redefined, causing dependent functions to be recompiled
_trace_enabled() = false
function enable_trace_logging(enable::Bool=true)
if enable != _trace_enabled()
eval(:(_trace_enabled() = $enable))
end
nothing
end
function test()
if _trace_enabled()
@info "A message"
end
end
end Thence: julia> @code_typed A.test()
CodeInfo(
14 1 ─ goto #3 if not false │
2 ─ nothing::Nothing │
15 3 ─ return │
) => Nothing
julia> A.enable_trace_logging()
_trace_logging (generic function with 1 method)
julia> @code_typed A.test()
CodeInfo(
15 1 ── %1 = Base.CoreLogging.Info::Const(Info, false) │╻ macro expansion
│ %2 = π ($(QuoteNode(Info)), Const(Info, false)) ││
│ %3 = Base.CoreLogging._min_enabled_level::Const(RefValue{LogLevel}(Debug), false) ││
│ %4 = (Base.getfield)(%3, :x)::LogLevel ││╻ getindex
│ %5 = (Base.getfield)(%4, :level)::Int32 │││╻╷╷╷ <=
│ %6 = (Base.slt_int)(%5, 0)::Bool ││││╻ <
[ ... ] etc etc
obviously this trick could be wrapped up in a macro to make it more convenient. |
Agreed. I've toyed with the idea of introducing the above trick to Base logging somehow. I think there's a need for a verbose tracing system to be part of the
Very neat, this makes complete sense. This obviously requires a library which is dedicated to thinking about these kind of performance issues, and willing to explicitly add the extra context on which to dispatch (ie, either DebugLayer or NoLayer). The Base logging macros do insert more boilerplate than I'd like in their current form and this could definitely affect inlining and subsequent optimizations. From a module composability point of view,
Yes, I'd suggest an |
Here's the logs from recent FemtoCleaner.jl:
https://gist.github.com/Keno/e3f911d71efbbd41a22446aa78268ed2
I see several issues that we should address:
W- ArgumentError("stream is closed or unusable")
are completely out of context; it's hard to follow "thread of execution" for serving a single request; ideally we could do better w/ logging to tie messages for a specific request togetherwe're still seeing: see BoundsError when closing MbedTLS.SSLContext #148E- ArgumentError("function requires !(isempty(bytes))")
| e = read: connection reset by peer (ECONNRESET)
errors and just emit a warning; just because the client closed their socket isn't really cause for alarm on the server sideE- SystemError("uv_getaddrinfocb", 0, nothing)
, but we should try to figure out what's happening hereFemtoCleaner.jl stops serving requests all together by the end of the gist, which is obviously unacceptableSee Analysis of EOF Errors with GitHub API (Seems Resolved) #214In general, I think things are just too obfuscated in our logs and w/ all the try-catch-rethrowing we're doing.
If anyone can jump in right away and help clean things up, that'd be great; I won't have time until Monday night to really dive in.
The text was updated successfully, but these errors were encountered: