The support ticket had been open for nine days when the operations manager finally said the sentence out loud: "It works every time I try it. It only breaks for her."
It was a shipping approval. The user would submit a form, a script would stamp a batch number, an automation would push the record to a partner system, and a notification would tell the warehouse to move. Four moving parts, three of them invisible, and one user out of a hundred and forty for whom the whole chain produced nothing at all. No error on screen. No failed record. Just silence where a batch number should have been.
Nine days is not a long time for a bug like that. It is a long time when nobody can see anything.
The promise is already in the documentation
If you read our platform's own documentation, this is a solved problem. Under Easy Maintenance it says, plainly, that the platform "provides detailed logs and monitoring functions to help quickly locate and solve problems."
I wrote a version of that sentence. I still think it was true when we wrote it. What it hides is a distinction that took us years of customer escalations to feel properly: a log is a record of what happened. An answer is a statement about what should have happened and where reality diverged. Platforms are generous with the first and stingy with the second.
Here is what the logs actually look like on the ground.
The log tells you what you printed, not what happened
Server-side scripts here run on an embedded engine with a fixed entry file, and they can print. There is exactly one way to do it. The documentation is unusually blunt: printing goes through a single function, the other JavaScript logging functions are not supported, and long objects sometimes come out with an ellipsis that hides the part you needed. The official workaround is to stringify the object yourself before printing it.
That is a real constraint, and I understand why it exists — you cannot let arbitrary script code write to an unbounded console. But notice what it means. The platform does not log what the script did. It logs what the developer thought to say. If the person who wrote the script never imagined that a record could arrive with an empty batch field, the log will never mention the batch field. The log is a transcript of the author's imagination, and the bug is usually hiding exactly where the imagination ran out.
Levels are a setting, and production is the wrong setting
Then there is the level question. Log level is an application-wide setting, not a per-module one. Development is where you set it talkative. Production is where the same documentation recommends the opposite — quieter, for performance reasons, which is a fair reason.
Roll those two facts together and you get the failure mode our customer lived through. The verbose lines exist in development, where nothing goes wrong, and they are filtered out in production, where everything does. The one environment that generates the bug is the one configured not to describe it. We shipped a platform that keeps its notes in a language that is only spoken on Tuesdays.
Three more ways to lose the evidence
The real-time log window keeps only the most recent thousand entries, and truncates any single entry past a fixed size. A thousand entries sounds like plenty until you remember that automations fire in loops, that one import can generate thousands of event lines, and that the entry you want is by definition the one that scrolled off first. The window is a rear-view mirror with a hundred meters of visible road.
History goes to files instead — but the files are organized per application, a couple per day, with a bounded retention window. Read that layout out loud and its assumption becomes obvious: an application is the unit of troubleshooting. Our platform's entire pitch is that a business action crosses applications. A form in one app, a script in another, an automation in a third. When that chain stumbles, its evidence is scattered across three directories with three different clocks. Every support engineer on my team has, at some point, opened two log files side by side and tried to line up timestamps by eye. That is not a workflow. That is archaeology with a browser tab.
The platform knows what is running. It does not know why
To be fair, we do better than most on the live view. There is a runtime page where you can see scripts, scheduled jobs, automations and API calls that are currently executing, and you can terminate one by hand. It is genuinely useful.
But look at what it answers. It tells you what is running. It does not tell you who or what started it, what it was started for, or what it passed downstream. When a user calls in and says her approval did not move, the runtime view cannot be searched by her action. There is no handle that ties the form submission, the script run, the automation step and the notification attempt into one thing with one name. Each layer knows its own name and nothing else.
What one name would have bought us
I want to be concrete here, because "better observability" is the kind of phrase that means nothing.
A single identifier, minted at the business event and carried through every hop, would have ended that nine-day ticket on day one. You would search the identifier, and instead of a thousand log lines you would get four statements: form accepted; script ran and produced this number; automation attempted delivery and got this response; notification queued for this person. Four statements. One page. The operations manager would have known before lunch that the break was in the delivery attempt and not in the user's account.
Everything else follows from that handle. Structured events instead of printed sentences, so a filter can find every failed delivery rather than every line containing the word "error." Per-execution retention: talkative while a record is young and cheap to delete later, quiet once it is old. And a trace a customer-success person can read without a developer standing behind them translating.
None of this is exotic. It is the ordinary kit of a system that expects to be operated. We built the platform first and the operations tooling second, and the order shows.
The uncomfortable conclusion
The hard part of debugging a low-code platform is not that the internals are complicated. It is that the platform was designed by people who also build things, and builders debug with an editor and a rerun. So we optimized the logs for a developer who is already holding the whole mental model in their head — and then handed the platform to customers who are holding nothing, who have one phone call, no reproduction, and a business process that is stuck.
Every log line we kept was chosen by someone who assumed the reader would be us. That assumption was correct for about two years and wrong forever after.
A platform's logs are not for the people who built it. They are for the people who have to keep it running on a Tuesday afternoon when the batch number does not appear. If you cannot answer their question in one search, you have not logged enough. You have just written a lot down.
Top comments (0)