D: Connecting to DB
D: Reading config file
D: Connecting to API server
D: Sending updated record
I: Updated user record for $USER
Or, if it fails, the last line is replaced by: E: Failed to connect to API server; could not update user record. [IP=..., ErrNo=..]syslog() and setlogmask() being the obvious examples.
Many (if not all) companies I have worked out filtered out everything DEBUG at compile time to improve performance in production as well.
The best option I've seen is putting everything in DEBUG or lower into a memory ring buffer per process that's dumped and collated with other logs at each ERROR/FATAL log.
Does anyone know of an implementation for this in any of the Java logging frameworks?
In my web app if nothing unexpected happens, only INFO/LOG level stuff is pushed to logs. If, however, I get an exception, I will instead print out all the possible traces. I.e. I always store all the logs in-memory and choose what should be printed out based on the success/failure of the request.
Now, of course, this is just a web API running in AWS Lambda, and I don't have to care overly much about the machine just silently dying mid-request, so this might not work for some of you, but this works great for my use-case and I'm sure it will be enough for a lot of people out there.
Or, to flip that around, if you take a program that produces a manageable amount of INFO logs, and change some of those INFOs to DEBUGs, how does that suddenly become unmanageable?
My experience has been that 1 customer-facing byte tends to generate something like ~10 DEBUG-level telemetry bytes. This level of request amplification can't be feasibly sustained at nontrivial request volumes, your logging infrastructure would dwarf your production infrastructure.
In a case where there are plenty of resources and the thing you are logging is very heavy then even verbose logging is trivial.
If you are beset by failures it is much cheaper than making your developers guess.
I've spent way too many hours adjusting log levels on a client environment and trying to replicate the issue without breaking or losing data while doing it.
Connecting to DB
Connected to DB → OK
Reading config file
Read config file → OK
Even better, but hardly doable because of the nature of logging Connecting to DB ... OK
Reading config file ... OKIf your program disappears into a black hole doing some operation that doesn't have (and needs) a timeout specified, you want to know what it was about to do when you last heard from it.
Imagine this success output, at a low (not tracing) log level:
Opened DB connection
Error output at the same logging level: Opening DB connection
Opening TCP connection to 127.0.0.1:5678
Error: No more file descriptors
Error: Failed opening TCP connection
Error: Failed opening DB connection
Crash output at the same logging level: Opening DB connection
Opening TCP connection to 127.0.0.1:5678
Starting TLS negotiation
Fatal: Segmentation fault
<stacktrace...>
Success output with tracing turned up and no error: Opening DB connection
Opened TCP connection to 127.0.0.1:5678
TLS negotiation completed in 0.1ms
DB version 5.1 features A, B, C
Opened DB connection
The key feature here is that there's a scoped before/after message pair ("Opening DB connection" and "Opened DB connection"), and the before part of the pair is output if and only if any messages are output in between.The contour around messages in between is visible (here with indentation), and the before message is not shown if there are no intermediate messages. Sometimes it's useful to show a different after-message if the before/after pair is not output, to include a small amount of information in the single message, or just to improve the language in the quieter case. Sometimes it's useful to suppress the after-message just like the before-message, if there is nothing in between.
Anything that might terminate the program, such as the Segmentation fault with stacktrace, counts as an intermediate message, and so triggers the output of all the before messages as part of the termination message logging. For this to work, of course you must have some way for the before messages to be queued that is available to the crash handler.
This is a poorly known technique, which is a shame as in my opinion it's a very helpful technique.
Its chief benefit isn't saving a few messages, it's that it scales to complex, deeply called systems with heavy amounts of tracepoints encouraged throughout the code. When studying issues, you may turn on tracing for some components and actions. As you turn on tracing for a particular component, before-messages from caller components become activated dynamically, ensuring there's always context about what's happening.
In case you're wondering, it works fine with multiple threads, async contexts, and even across network services, if you have the luxury of being able to implement your choice of logging across services, but of course there needs to be some context id on each line to disambiguate.
Exceptions should log their call stack, making both the before and after logs redundant.
Save the logs for significant events.
My problem with that is what if the program crashes or produces an error. It's good to know what the program was attempting to do.
Imagine the output
vs.