跪拜 Guibai
← All articles
Frontend · JavaScript · Programmer

Deleting a 'Useless' console.log Broke Production: Lazy Getters and Hidden Side Effects

By ErpanOmer ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Any build step that strips console calls, whether esbuild drop or Terser drop_console, can silently remove side effects hidden inside the logged expression and break production in ways that only surface after release. This incident is a concrete warning to audit logs for getters, Proxies, and custom conversion functions before automating their removal.

Summary

A routine cleanup removed 38 console.log calls across 47 changed files. Twenty minutes after release, the alert group went off: core product detail pages showed undefined prices for products with tiered pricing. The culprit was a single deleted line, console.log('price config:', product.priceConfig), which had been the only expression that triggered a lazy getter on a reactive product model.

That getter parsed and cached tiered pricing rules from raw data on first access; during rendering, code bypassed the getter and read the private field _priceConfig directly via bracket notation. With the log gone, the getter never fired, the private field stayed null, and the UI displayed undefined. Even esbuild's drop: ['console'] would have failed the same way, because the side effect lives in the argument expression, not the console call itself.

The post walks through three console.log side-effect patterns (Proxy dependency collection, toString/Symbol.toPrimitive, and WeakRef strong references), then recommends build-tool stripping with awareness of side effects, explicit init() methods over lazy implicit triggers, and ESLint rules that keep getters pure. The real lesson: in JavaScript, property access is a potential function invocation, so a log line can be load-bearing.

Takeaways
— A console.log that logs a property can be the only trigger that initializes a lazy getter; deleting it leaves the underlying field null and breaks downstream code.
— esbuild's drop: ['console'] removes the entire console.log statement at the syntax level, which also deletes any side effects in its arguments.
— In JavaScript, reading a property can execute getters, Proxy traps, and implicit conversions like toString or Symbol.toPrimitive, making property access a potential state mutation.
— The incident fix: replace lazy getters with constructor-time initialization or an explicit init() method that throws when accessed before initialization.
— ESLint rules such as no-setter-return and @typescript-eslint/no-unsafe-member-access can discourage side-effectful getters and direct private property access.
Conclusions

The failure wasn't caused by console.log itself but by code that depended on an implicit side effect during property access; the log just happened to be the first accessor.

The recommended fix, explicit init or eager initialization, removes the hidden dependency and makes the initialization contract visible, which is better engineering than preserving the log.

Build-time stripping tools are not side-effect-aware; teams that rely on them must treat the argument expressions of removed statements as potentially effectful.

Concepts & terms
Lazy getter
A getter function that defers expensive initialization until the first property read, caching the result for subsequent accesses.
Proxy get trap
A handler function that intercepts property reads on a Proxy object; in reactive systems like Vue 3, it triggers dependency tracking.
Symbol.toPrimitive
A well-known symbol that lets an object define its own conversion to a primitive value, invoked by operations like string concatenation or console.log serialization.
From the discussion

The incident is read as a developer-discipline problem, not a logging problem: a single deleted console.log exposed a fragile lazy-getter design where the log was the only trigger. The consensus holds that such code should be left untouched if it runs, and the real culprit is the developer who created an implicit side-effect dependency. AI review is floated as a possible safety net, but the runtime nature of the failure makes static review insufficient.

— The deleted console.log was the sole initializer of a lazy getter, so any removal of apparently unused code can silently break production.
— The 'if it runs, don't touch it' maxim encapsulates the risk of optimistic cleanup without tracing dependencies.
— The fault lies in the fragile lazy-getter pattern authored by a colleague, not in the log statement itself; the lesson is to review for hidden side effects.
— AI code review is not a guaranteed remedy because runtime side effects from lazy initialization are outside static analysis.
Featured comments
阿懒

So if it runs, don't touch it [black face]

ErpanOmer

Incisive!

happyfire

Can AI review before committing solve this?

ErpanOmer

Not necessarily; these are runtime errors.

小咔拉迷

It's not about the log; it's about the colleague.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗