Logs are for the machine, not the human
Structured logging best practice is not about formatting JSON correctly, but about ensuring that the data emitted is actually visible when it matters. If the output stays buried in a rotating file on disk, the structure is irrelevant to your ability to ship or debug. The core problem is that logs are written for the machine to consume, yet they are read by a tired human at 3 a.m. who needs one specific answer, not a stream of context.
The setup here is a single VPS with 12GB of RAM. One Next.js process serves four distinct sites, including the film publication at
flix.pracandy.com, while Instalaz runs as a separate Node process on port 3050. Caddy terminates TLS for all of them. Because everything shares the same kernel and memory space, a leak in one service affects the others. If a log line does not tell you which process is holding the resource, you are just looking at noise.
What to alert on
Alerting on every error is a trap. It creates a background hum that you stop hearing. Instead, alert on state changes that break the product contract. For a static build like GamingCap or Invoiceful, there is no server process to monitor, so alerts focus on the Caddy configuration and disk space. For the dynamic services, the signal is different.
The Next.js process serves multiple hosts. If memory usage climbs steadily over hours, it is not an error; it is a leak. The log should capture the memory footprint alongside the request ID. If the PostgreSQL 16 database connection pool saturates, the app does not crash immediately; it starts timing out. That timeout is the alert. Do not alert on the 500 status code alone. Alert on the specific failure mode: "connection pool exhausted" or "TLS handshake failed." These are distinct states that require distinct fixes. A generic "error" alert gives you nothing. It forces you to open the logs to find out what actually happened.
Sampling and retention
High-traffic endpoints generate thousands of lines per minute. Logging every single request at full fidelity is a waste of disk I/O. On a 193GB disk, you do not have infinite space for verbose output. Sample the success paths. If a request completes in under a second and returns a 200, log it at a reduced frequency. Log the failures at 100%.
The goal is to keep the signal-to-noise ratio high. If you log every request, the interesting failure is buried under ten thousand successful ones. When you do need to read the logs, you want to scroll past the noise quickly. Sampling is not about saving space; it is about preserving your attention. If the log file is too large, you will not read it. You will grep for a specific ID, and if that ID is not indexed or searchable, you are stuck.
Writing for the future
A log line must be understandable by the person who wrote it six months ago. That person does not remember the context. They do not remember why a specific variable was chosen. The log line must contain enough context to be self-explanatory.
Include the request ID, the host header, and the specific operation. Do not assume the reader knows which site a request belongs to. The Next.js process serves four sites by host-based routing. A log line that says "fetch failed" is useless. A log line that says "flix.pracandy.com: fetch failed for article ID 123" is actionable. The host tells you the product. The ID tells you the resource. The error tells you the state.
Avoid abbreviations that feel clear in the moment but opaque later. "DB conn" might mean database connection, but it could also mean dependency connection. Write "database connection." The disk space is there. The cost is not the bytes; it is the time spent deciphering. If you cannot explain the log line to a colleague in one sentence, rewrite it. The log is not a place for cleverness. It is a place for clarity.