1. At the start of a request, generate a globally unique traceId
2. Pass this traceId through the whole call stack.
3. Whenever logging, log the traceId as a parameter
Now you have a log with many of the plusses of a trace. The only additional cost to the log is the storage of the traceId on every message.
If you want to read a trace, search through your logs for "traceId: xyz123". If you use plain text storage you can grep. If you use some indexed storage, search for the key-value pair.
This way, you can retrieve something that looks like a trace from a log.
This does not solve all the issues named in the article. However, it is a decent tradeoff that I've used successfully in the past. Call it "poor man's tracing".