The Art of Logging
medium.com
medium.com
We actually did something slightly different: the correlation ID is generated at the start of a request, where it enters "our stuff" as a whole. But downstream "microservice" style requests get a different correlation ID, but with the original as a prefix. So if you have lots of microservice calls being made this means you can select any subtree with some prefix. The original correlation ID is the entire tree, but you might have,
b8a0b848-92e6-49e7-bde2-283e165b02e4 # the original request
b8a0b848-92e6-49e7-bde2-283e165b02e4/authz # request to authz service
b8a0b848-92e6-49e7-bde2-283e165b02e4/foo-db/0 # first subrequest to foo-db
b8a0b848-92e6-49e7-bde2-283e165b02e4/foo-db/1 # second subrequest to foo-db
b8a0b848-92e6-49e7-bde2-283e165b02e4/foo-db/2 # third subrequest to foo-db
b8a0b848-92e6-49e7-bde2-283e165b02e4/bar-db/0 # first subrequest to bar-db
etc. So you can pull just the original request with "= 'b8a0b848-92e6-49e7-bde2-283e165b02e4'", a whole subsystem with something like "starts_with 'b8a0b848-92e6-49e7-bde2-283e165b02e4/foo-db/'", so on. Very convenient. (Our graphs weren't even that deep; we really just have "backends for your frontends" and "backends" and that was mostly it, so it was mostly to correlate between the BE and the BE-for-FE.)You need structured logs first, so if you've not got that, start with that first.
There's this thing called "OpenTracing", which I hear is a sort-of standardization of this, but I've not had time to investigate it.
I always liked structured logs because it frees my mind from coming up with how I want to format arguments. "New HTTP request for /foobar from 1.2.3.4 with request-id ac31a9e0-5d57-44de-9e98-60fa94d3e866" is a pain to read and maintain. `log.Debug("incoming http request", logger.IP("remote-ip", req.Address), logger.String("request-id", req.ID), logger.String("route", req.Route))` is easy to write, and the resulting logs are easy to analyze!
I do like myself some pretty colorful logs, but prefer JSON as the intermediary. It's pretty easy to post-process the logs into something understandable. I wrote https://github.com/jrockway/json-logs for this task. Give it a stream of JSON logs, get colorful text out. Add/drop fields or select/highlight interesting lines with JQ or regular expressions (with -A -B -C of course). Installable on Linux or Mac with Homebrew ;) It's one of the few pieces of software I wrote for myself that passes the "toothbrush test". I use it twice a day every day. And that's a good day when I'm not pulling my hair out over an obscure issue ;)
For example, I wrote opinionated-server which lets you pick text or JSON logging. Even though it has text logging, when I'm developing a server locally, I just pipe the JSON through jlog because I have super-refined that format to my personal tastes, whereas zap's builtin text format isn't as nice.
But I will say that when I create loggers for unit tests, I use the text formatter if one is available. I have never been interested in parsing JSON logs interspersed among plain text (though it is totally very possible to do).
If you want to go log-crazy with a big elaborate deal and the whole ELK thing and so forth and so on, by all means, if you can afford it - which means afford to see it all the way through. Too many times I've had to deal with teams where everybody says, "Yeah... It would be nice to have logs...." because someone built the Big Elaborate Thing and nobody can figure it out, find it, etc. etc. It becomes a long-term "would be" instead of "is".
Metrics tell you there's a problem. They're an application's way of reporting metadata or state. They are designed to be small and frequent and machine readable/writeable, with small bits of useful information that are of themselves quite useless, but are very useful in aggregate.
Logs describe the problem in detail to a human. They're a developer's way of diagnosing problems in an application. They can have machine-readable components to them, but being machine-readable is not the point. The point is for a human to quickly diagnose and fix an arbitrary problem, however you decide to do that. It's extremely annoying if you can't read a log because it was machine-formatted.
From the article:
Invest time in designing your log structure
Don't. Eventually you will have to deal with logs with a random structure. Invest in telemetry management and distributed tracing. Log as much as possible.
Don't. It's about quality, not quantity. Too many logs with too little information leaves you buried and unable to diagnose quickly. And too much information is bad for security; always mask sensitive information. I can't tell you how many giant companies have been hacked because of this. And cost is a factor: I have seen people stop logging because it was costing them too much money, when they should have been logging smarter (fewer messages with more useful info) and utilizing metrics. Keeping consistency is everone’s priority
It is guaranteed that you will have to deal with some metrics and logs in arbitrary formats. Do not worry about making them perfect. Invest in telemetry management and distributed tracing.I used to work at a company that invested too much time and effort into an ELK stack that handled specialist log formats sent over a custom UDP service. It was brittle and fell over in all sorts of strange ways. This was all for monitoring a handful of servers.
The only good that came of it was when we realized that the support staff could search the logs and diagnose 95% of customer complaints without bothering developers. ("Your transaction failed because you entered a bad card number.")
But that was because the text logs were more useful than the metrics.
Also stripe's canonical log lines[1] solve this problem nicely
It's quite nice at first glance. My main qualm with it is that there is no formal spec and I got bitten a few times by incompatibilities between emitters and parsers especially around quoted values.
(In particular we had a rust binary emitting badly escaped values such that fluentbit emitted structured logs with thousands of fields which in turn caused graylog to explode which was particularly gnarly because at the same time we had a production incident we had to troubleshoot without historical logs)
logfmt might be fine for people at Stripe who've built infra around it, but for mortals, stick with JSON so you can send data around.
When you have a million log lines, the question "Did it parse correctly?" shouldn't need to be asked because checking correctness is a huge problem.
Canonical log lines don't solve the exact same problem (they provide all the information for some api call in a single place), but they need to be structured to work.
Logfmt: https://www.cloudbees.com/blog/logfmt-a-log-format-thats-eas...
In basically every case you should store/log a dense binary dump of your varying data and a ID that uniquely identifies the data format. Then, when a human wants to read the log, you do a post-processing step in your debug environment that formats the data in a human readable way.
This moves string formatting off of the target and prevents the frankly outrageous amount of redundant string duplication when storing human readable logs. It also avoids a roundtrip through stringly-typed logs and allows for efficient binary parsing of your logged data in their natural data format instead of doing brittle string parsing.
It is frankly challenging to come up with even a single case where you should not do it this way as long as your tooling is mature enough to manage the separated encoding/decoding pipeline. Even if it is not, you could always just run the post-processing step on all your new raw logs and store that afterwards as your “official logs” which still gives you the performance advantages on your production systems while being no worse than any existing scheme.
One use case for human readable format: your service/whatever that output logs doesn't output TBs of output every month and you can easily handle the amount on commodity hardware. Why complicate something that doesn't need it?
Both methods have their place.
Is there a library for it?
This sounds like something I can't justify DIYing in the kinds of software that I work on.
There's an index on our trace/correlation column, so we can instantly pull all log entries for a specific user interaction with a query like:
SELECT *
FROM LogEntries
WHERE TraceId = @TraceId
We also use a single connection and WAL, so we can write many thousands of entries per second if necessary.For example if you're on version X and in the process of deploying version Y, when service A sends a request to service B you have 4 distinct possibilities regarding the versions running on these two instances (X→X, X→Y, Y→X, Y→Y). If in version Y you've made changes to the request in its protocol/format/payload, you'll need to deal with all 4 cases to avoid encountering errors during the deployment. If the instances log all messages with their version, it'll help track down these transient incompatibility bugs, e.g.
{
"service": "B",
"build": "X",
"request_service": "A",
"message": "unknown field in request: 'new_field'"
}JSON makes a lot of sense to me in logging if it's specifically something complex that you want a user to grab with copy-paste and dump into an interpreter for further inspection so having some supplemental detail fields be compacted in the JSON isn't a bad idea - but for the majority of the fields I'd prefer either fixed position or a more simplistic key=value (or key="value") format that can be more trivially read and is just as easy to parse.
Of course in a perfect world you want to ingest these logs into some better searching tool that lets you run SQL or some other standard query language over the set to prune them by date and pull out specific fields but there are times when you'll be hanging on by your nails with nothing to help diagnose the issue except an SSH connection to the server and the log file open in vi.
That tool is the Logfile Navigator (https://lnav.org). It's a TUI that reads plaintext logs, JSON-lines logs, and others. It will collate messages from all the files into a single log view that you can filter and search. The logs are also plumbed through SQLite virtual tables, so you can do fancy queries.
When setting up ELK or paying for Splunk/Sumologic is too much, lnav is a pretty good alternative.
For simple enough logs (like the nginx/apache example shown) having so simple format enables a quick and dirty search and information extraction with common unix command line tools (like grep, cut, sort, uniq, tail and sometimes rev or awk). It is not an approach that works for all logs and all situations, but sometimes is enough. And simplicity is a good thing to have.
For example, a great structured logging logging library for Python:
This story is not a push for a standard but rather an attempt to create a logically organized log format optimized for log parsing systems like newRelic or ELK etc. It will help you generate useful dashboards, metrics and event notifications (example: trigger an alert if the percentage of errors exceeds 5%).
Implementing a standardized log system in your applications will cost you time and money. Debugging will costs you too, especially in edge cases where information is key. Your decision should consider both sides of the equation.
Logging is a very opinionated and sometimes divisive conversation. However, no matter which format you will use, it is always better than no logs at all.
Otherwise, it's hard to convert a stack trace into a metric ;) But you can process the logs to generate a metric about the frequency of stack traces.
Ditto for logs telling you "user Foo doesn't have permissions for that operation". Again, you can make a metric about "user permissions failure" but you still need the log to know that it was user Foo.
Being able to do "where query_time_ms > 5000" and then seeing the query has made some of my systems incredibly debuggable¹.
Or just finding a failing request with "where response_code = 500". Yeah, I can "grep 500", but you would be shocked at how often those characters occur by happenstance in logs "grep ' 500 '" is a bit closer but … you're just dancing around a solution engineered to do the job: structured logs.
¹and then finding out someone drew a rather large bounding box that managed to be centered precisely between and encapsulate both Philly & NYC. This kills the DB.
Unfortunately with most SaaS metric services, your credit card does not scale to support infinite dimensions of data correlation.
Tagging a high cardinality value like a customer ID for example would quickly blow out your bill.
Usually only events that alert of a severe error are logged.
Once we know something's not working, the settings for level are modified so, without even stopping the service, much more detailed information is written and we have a more precise idea of what exactly is failing and why.
It works nicely.
feels bad, but getting a 55 hour job down to 12 hours by removing logging was worth it.
yes, we log asynchronously and without blocking. but it is a constant tax on everything that happens and it adds up.
I've seen some time jumps with logging removal but I don't think I've ever seen anything exceed about 20% of execution time (at least on the scale you're talking about, there were some scripts I revised logging on that were constructing huge logging infrastructures for an otherwise sub-milisecond job) 40 hours is a lot of hours.
it was far, far worse than that.