The only two log levels you need are INFO and ERROR
ntietz.com
ntietz.com
That's why you want DEBUG and TRACE. I'd rather have useful logs as well as a debugger when running locally.
While debugging and testing locally, though? They're extremely useful.
I think they're also useful in production code, although much more rarely. I've had a few times when a customer is having a strange problem that higher log levels helped to quickly resolve.
Being able to have them enable the higher log levels, reproduce the issue, reduce the log level to normal, and send you the logs can occasionally find an issue that would have taken forever to track down otherwise.
And that's not even to mention special processing. In the unices, anyway, the log daemon can take different actions depending on what level the log message was issued at. CRITICAL errors can result in automatic notification to a dev, for instance.
As for the articles thesis you own need two debug levels: seems wrong to me. If you look at it from the perspective of target uses, there seem to be at least three levels. There is "I've died for this reason". That's CRITICAL. Then there is the user of your code, who is trying to debug it's interactions with other systems. That's INFO. Finally there is the person trying to debug the internals. That's DEBUG. In reality there seems to be a fourth useful level: I've got bad inputs or in a bad state, but I'm continuing anyway. Examples are API calls with bad parameters or a storage system running low of space. That's WARN.
Any tips on rolling your own? My first whack would probably be treating it like a packed message, e.g.
<start_char> <n_packed_bytes> <time> <file> <line> <n_args> <arg1_type><arg1_raw_dump> ... <format_str>
with everything aside from the format string having agreed upon byte-counts, and probably have the file names getting dumped to a lookup chart. There's probably something more elegant, but I've never been good at straying too far eclectic.
E.g. a slice needs a length.
defmt::error!("Data: {=[u8]}!", [0, 1, 2]);
// on the wire: [1, 3, 0, 1, 2]
// string index ^ ^ ^^^^^^^ the slice data
// LEB128(length) ^
LEB128[2] is the same compressed integer encoding scheme used by DWARF & WASM.The entire "Data: {=[u8]}!" format string is just one byte on the wire (assuming it's in the first 255 or fewer log statements, the index is also LEB128 compressed.)
And while, yes, you can have your debugger stop where exceptions are thrown, that's often not very useful because the exception happens after the point where you want to start seeing what is going on.
Log channels allow you to route certain messages to certain systems. CRITICAL, for example, can be forwarded to your "you have to deal with this RIGHT NOW" alert system (CRITICAL: Stock trading algo lost 75% of its portfolio value in the last hour). That's very different from a regular (ERROR: Endpoint /report responded with code 503).
DEBUG and TRACE can be combined with dynamic filters to keep the volume of messages down while still being able to get useful information from a production system. There are also dev and staging servers for running general DEBUG and TRACE in a lower volume environment.
Any others are only really useful for debug situations.
Logs are standard and easy, which is why people use them.
Metrics show you that something is wrong / alerting.
Logs show you what is wrong and where to start debugging.
Anything else will quickly get turned into a metric stream anyways.
Semi-structured streams with basically unbounded cardinality are still useful tho, and I think that's arguably "a log" regardless of how it's represented. And then levels come back into the system.
No, you cannot. At least for most of the practical cases. Some upstream dependencies can and do throw critical/fault-level log messages which you do not control and cannot catch.
This scenario is frequent enough that some cloud providers even provide services that generate metrics streams by filtering logs.
Also important: the operational side of running a service. When the shit hits the fan, you do not have the luxury of redeploying production services just because you need to add a log call or emit a metric. For that, you work with what you already have in place. I was involved in a couple of SEVs where I personally created metrics filters and alarms on the fly to help troubleshoot issues and monitor relevant failure modes.
Sure, if you want to setup such rules. Do you want to go through your service and define on the notification level that not every ERROR is CRITICAL?
I agree, this is a very clear fact that's readily understood by anyone with any experience developing and operating a service.
It's weird that we see claims arguing one of the core tools of troubleshooting isn't needed, without providing any sound argument.
Among the long list of arguments that can be presented, I'm only going to point out one which might be obscure: cost. Cloud providers charge a premium for data generated through log ingestion, and the more logs you generate the more you pay. Most of the time needlessly so.
One strategy to work around that is only logging error/critical in production. That avoids the invoice caused by all other log levels, which dominate the whole event dump. However, when the shit hits the fan you will want as much info as you can to help you quickly troubleshoot problems. With the standard log level hierarchy that's as easy as tweaking an environment variable in prod to output additional log levels. If you want to, you can drive the level down to dumping tracing info. Modern production-ready logging frameworks even support filtering log events based on the namespace/package/class level, for this very reason. You pepper your code paths with the relevant log messages corresponding to adequate log levels, and once you have your infrastructure sorted out you have all the tools you need to adapt your logs to the operational needs you have.
You absolutely cannot do that with simplistic info/error logs.
When I don't have LogLevel.Warning or the like, I end up doing things like adding a "WARNING:" prefix to the message so it stands out and can be grepped, but it's much better when I can just use the LogLevel to filter, color the message, show it in the UI, etc.
This is already an error.
Warning is supposed to be before anything weird takes place.
WARN - something is off a happy path but it's either out of you control (external system sent a broken request) or not urgent (e. g. use of a deprecated API) or should be handled gracefully (timeout for a sub-request with multiple retries, a request may still succeed but with high response time)
ERROR - you need to take action or at least may need - e. g. indication of a bug or a problem in the infrastructure (cannot connect to a database or disk is full, or a process has run out of file descriptors, an expired certificate, invalid auth token e. t. c.).
Of course one can lamp two these levels together but it makes Ops work harder.
Oh boy, where to start....
1. Staging/pre-prod environments do exist.
2. Debuggers are nice, but they're not everything. I have a loop and want to see the distribution of execution times. Should I really use a debugger, writing down the info from each iteration? Or maybe something a bit more automated (like a log...)?
3. Being able to enable debug logs (for a specific part of the system) is extremely helpful. Datadog makes this super easy to enable finer logging at runtime.*
* More precisely, the application always logs at a fine level. I have Datadog drop debug logs, but at any time I can enable them to help with a problem. https://docs.datadoghq.com/logs/guide/getting-started-lwl/
There is a big difference between logging “a HTTP request was made to x/y” and a stream of super-verbose, protocol-level messages.
Both are useful for diagnosing issues. Both don’t belong together.
Let’s not forget, not all production environments are Google-scale! I’ve worked on mission critical applications with a few hundred internal users, tops. Turning on the debug logging firehouse can be feasible for short periods of time. More so if you can limit it to a targeted subset of users.
For a team I was technical lead on a number of years back we decided to only log errors, and only if there was an identified known error path. We also documented the error and what action, if any was expected to be taken.
The team was relatively junior and had up to that point had a habit of scattering debug and info statements through any code touched by the feature under development.
This forced developers to diagnose development issues using better test coverage and to resolve and deal with error paths early.
When we went to production we had alerts that informed us anytime the log size > 0 kB and we investigated and resolved the issue as soon as we could.
It wasn't the most complicated application (online government form for applying for a pension), but did integrate with a number of other systems and deal with a fairly varied number of scenarios. We also had less lines of code and better codebase readability.
No idea if that policy is still in place but it did have a number of benefits.
I've seldom added anything but error or observability logging to anything I've worked on since.
[0] Not the trace log level, but events and spans, i.e. opentracing
These days, something like 90% of the issues I to have to deal with observability are cost related. "You can't have that amount of cardinalities.", "Do you really need this metric?", "We've decided to turn off all logs in dev envs, they are too expensive.", ...
Especially when the deployment is not under your control. That is when you ask to enable DEBUG so system can spew more information and you can trace the execution better.
OTOH, this whole idea of attaching trace-id (Android) and where the log came from (modules and functions) is nothing new. on the whole, I am not sure what the takeaway is.
Plus three levels makes more mental model sense to me. Debug noise, key info and somethings on fire
DEBUG/TRACE is better handled by tracing. INFO is noise. WARN is INFO with emphasis.