Skip to content

feat: log cluster module IPC via onelogger#120

Open
killagu wants to merge 1 commit into
1.xfrom
claude/ipc-log-1x
Open

feat: log cluster module IPC via onelogger#120
killagu wants to merge 1 commit into
1.xfrom
claude/ipc-log-1x

Conversation

@killagu
Copy link
Copy Markdown
Contributor

@killagu killagu commented Apr 15, 2026

Summary

Add structured logging for every master↔app worker IPC point under Node.js cluster mode, using onelogger as the logger facade. Same feature as its v3 (master-branch) counterpart, ported to 1.x's plain JavaScript lib/ layout.

Covers both layers of cluster IPC:

Application layer (always on)

Point Direction Where
A master → app send lib/utils/messenger.js sendToAppWorker — all routed master→app sends (every worker in the loop)
A' master → app send (w/ fd) lib/master.js sticky-session worker.send('sticky-session:connection', connection)
B master ← app recv lib/master.js cluster.on('fork')worker.on('message', (msg, handle) => ...); the log fires after forwarding to avoid adding serialization latency to the forward path
C app → master send lib/app_worker.js — the single explicit process.send({action:'realport'})
D app ← master recv lib/app_worker.js new process.on('message', (msg, handle) => ...)

Cluster internal layer (opt-in via EGG_CLUSTER_IPC_LOG=1; very verbose — newconn fires once per HTTP request)

Point Direction Observes
E master ← app internal online / listening / queryServer / accepted (fd ack) / close — via worker.process.on('internalMessage')
F app ← master internal newconn (fd dispatch, handle=<TCP>) / disconnect / suicide — via process.on('internalMessage')

Note: internalMessage is an undocumented Node.js event but has been stable across major versions. E/F hook on worker.process (ChildProcess) rather than cluster.Worker, since the latter does not forward internalMessage.

Agent worker (child_process.fork) is intentionally out of scope — not a cluster module component.

Users can replace the default console sink via onelogger.setLogger() / setCustomLogger().

formatIpcMessage has a safe replacer: net.Socket / net.Server / Buffer / circular refs are collapsed to tokens; long strings are truncated at 200 chars; whole JSON payload is capped at 1024 chars. Handles get a +handle=<Socket fd=N> / <TCP> / <Server> suffix.

Sample output

With `EGG_CLUSTER_IPC_LOG=1` and a real HTTP request against the `egg-ready` fixture:

```
[egg-cluster:ipc] [master<-app#86211] action=cluster:online
[egg-cluster:ipc] [app#86211->master] action=realport data={"port":17011,"protocol":"http"}
[egg-cluster:ipc] [master<-app#86211] action=realport data={"port":17011,"protocol":"http"}
[egg-cluster:ipc] [master<-app#86211] action=cluster:queryServer
[egg-cluster:ipc] [app#86211<-master] action=cluster:ack#1 data={"cmd":"NODE_CLUSTER","ack":1,...,"sockname":{...}}
[egg-cluster:ipc] [master<-app#86211] action=cluster:listening
[egg-cluster:ipc] [master->app#86211] action=egg-ready data={...}
[egg-cluster:ipc] [app#86211<-master] action=egg-ready data={...}
[egg-cluster:ipc] [master<-app#86211] action=cluster:ack#1 data={"cmd":"NODE_CLUSTER","ack":1,"accepted":true} ← fd ack
[egg-cluster:ipc] [app#86211<-master] action=cluster:newconn ... +handle= ← fd dispatch
```

Notes for 1.x

  • `onelogger` declares `engines: >=16.0.0` but its CJS output only uses ES5 features; functionally works on older Node too (install may print an engines warning on Node < 16).
  • Places where v3's `AppProcessWorker` wrapper class would have been instrumented are handled here at the `messenger.sendToAppWorker` level instead (1.x has no such wrapper).
  • In `lib/app_worker.js`, the `ipc_logger` require and listener registrations live after `new Application(options)` construction. This matters for fixtures whose Application constructor throws synchronously (e.g. `agent-die-on-forkapp`) — the added code never runs in those cases and does not perturb the pre-existing race between `onAppExit` and `onAgentExit`.

Test plan

  • Manual: startCluster on `egg-ready` fixture with `EGG_CLUSTER_IPC_LOG=1` + real HTTP request → all 7 points (A/A'/B/C/D/E/F) print as expected, including `cluster:newconn +handle=` (fd dispatch) and `cluster:ack#1 accepted:true` (fd ack). No Socket serialization errors.
  • `egg-bin test` full suite — 90 passing / 6 failing. Pre-existing (reproduced against stashed baseline):
    • 4 `FrameworkErrorformater` tests: brackets regex mismatch (pre-existing egg-errors URL formatting difference)
    • 1 `listen config path` test: Unix socket ENOENT (pre-existing environment issue)
    • 1 `should master exit when agent exit during app worker boot`: pre-existing flaky (4/5 pass rate both with and without this change — racy fixture that depends on `egg-core` `getResolvedFilename` not throwing, which it does on newer Node)

🤖 Generated with Claude Code

Add structured logging on every master<->app worker IPC point in Node.js
cluster mode. Covers both application-layer messages and the internal
NODE_CLUSTER fd dispatch / fd ack.

Application layer (always on):
- master->app send  (messenger.sendToAppWorker)
- master->app sticky-session Socket direct send (master.js)
- master<-app recv  (cluster.on 'fork' -> worker.on 'message')
- app->master send  (app_worker.js realport)
- app<-master recv  (process.on 'message')

Internal cluster layer (opt-in via EGG_CLUSTER_IPC_LOG=1, very verbose):
- master<-app internal (worker.process.on 'internalMessage'):
  online / listening / queryServer / accepted (fd ack) / close
- app<-master internal (process.on 'internalMessage'):
  newconn (fd dispatch, with handle) / disconnect / suicide

Users can replace the default console sink via onelogger.setLogger().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 15, 2026 05:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69578fe9-a39d-4dc1-b1ab-46d98293ef73

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/ipc-log-1x

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements IPC message logging between the master and app workers using a new utility and the onelogger library. The feedback identifies performance bottlenecks related to eager and redundant JSON stringification, especially during message broadcasts, and suggests a more robust check for the environment variable that enables internal cluster logging.

Comment thread lib/utils/messenger.js
continue;
}
// A. master -> app (send)
ipcLogger.info(formatIpcMessage(`master->app#${worker.process.pid}`, data));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Logging inside this loop is particularly expensive because formatIpcMessage (and its internal JSON.stringify) is called for every worker in the cluster. If a message is broadcast to many workers, the same data object is stringified repeatedly. This can cause significant CPU spikes in the Master process. It is recommended to pre-calculate the data string once outside the loop or use a more efficient logging strategy for broadcasts.

Comment thread lib/utils/ipc_logger.js
* These are very verbose (one `cluster:newconn` per HTTP request), so they are opt-in
* via the `EGG_CLUSTER_IPC_LOG` environment variable (any truthy value enables).
*/
const internalIpcLogEnabled = !!process.env.EGG_CLUSTER_IPC_LOG;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The environment variable check !!process.env.EGG_CLUSTER_IPC_LOG will evaluate to true even if the variable is set to the string "0" or "false". In the context of environment variables, users often expect these values to disable a feature. It is safer to explicitly check for truthy values or exclude known falsy strings.

Suggested change
const internalIpcLogEnabled = !!process.env.EGG_CLUSTER_IPC_LOG;
const internalIpcLogEnabled = !!(process.env.EGG_CLUSTER_IPC_LOG && process.env.EGG_CLUSTER_IPC_LOG !== '0' && process.env.EGG_CLUSTER_IPC_LOG !== 'false');

Comment thread lib/utils/ipc_logger.js
Comment on lines +52 to +63
function stringifyData(data) {
let out;
try {
out = JSON.stringify(data, makeReplacer());
} catch (err) {
return `<unserializable: ${err.message}>`;
}
if (out && out.length > MAX_TOTAL_LEN) {
out = `${out.slice(0, MAX_TOTAL_LEN)}...(truncated)`;
}
return out;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The stringifyData function eagerly performs JSON.stringify on the message data. Since this is called by formatIpcMessage before passing the result to ipcLogger.info, the stringification overhead is incurred for every IPC message even if the logger is configured to ignore INFO level messages. For the "always on" application layer logs (Points A, B, C, D), this could impact performance under high IPC load. Consider making these logs opt-in or using a lower log level like debug to minimize default overhead.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds structured IPC logging for Node.js cluster mode master↔app-worker communication in egg-cluster 1.x, using onelogger as the logging facade, with an opt-in path for very-verbose internal NODE_CLUSTER traffic.

Changes:

  • Add onelogger dependency and introduce lib/utils/ipc_logger.js for message formatting + safe serialization.
  • Instrument master→app sends (including sticky-session direct sends) and master←app receives with ipcLogger output.
  • Instrument app-worker side for app→master realport send, master→app receives, and optional internal NODE_CLUSTER messages via EGG_CLUSTER_IPC_LOG.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
package.json Adds onelogger dependency to support IPC logging facade.
lib/utils/ipc_logger.js New helper for IPC log formatting + capped serialization and handle descriptors.
lib/utils/messenger.js Logs master→app routed sends at the messenger layer.
lib/master.js Logs sticky-session sends, app-worker message receives, and optional internal NODE_CLUSTER messages.
lib/app_worker.js Logs master→app receives, app→master realport send, and optional internal NODE_CLUSTER messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/master.js
Comment on lines +351 to +360
if (internalIpcLogEnabled) {
worker.process.on('internalMessage', (msg, handle) => {
if (!msg || msg.cmd !== 'NODE_CLUSTER') return;
const label = msg.act ? `cluster:${msg.act}` : `cluster:ack#${msg.ack != null ? msg.ack : '?'}`;
ipcLogger.info(formatIpcMessage(
`master<-app#${worker.process.pid}`,
{ action: label, data: msg },
handle
));
});
Comment thread lib/utils/ipc_logger.js
Comment on lines +52 to +81
function stringifyData(data) {
let out;
try {
out = JSON.stringify(data, makeReplacer());
} catch (err) {
return `<unserializable: ${err.message}>`;
}
if (out && out.length > MAX_TOTAL_LEN) {
out = `${out.slice(0, MAX_TOTAL_LEN)}...(truncated)`;
}
return out;
}

/**
* Format a single IPC message into a one-line log string.
* @param {string} direction e.g. 'master->app#12345' / 'app#12345<-master'
* @param {Object} msg the message body (supports cluster internal msgs via `action: 'cluster:<act>'`)
* @param {*} [handle] optional handle (net.Socket / net.Server / TCP) attached to the IPC message
* @return {string}
*/
function formatIpcMessage(direction, msg, handle) {
const action = (msg && msg.action) || '<unknown>';
let out = `[${direction}] action=${action}`;
if (msg && msg.data !== undefined) {
out += ` data=${stringifyData(msg.data)}`;
}
if (handle) {
out += ` +handle=${describeHandle(handle)}`;
}
return out;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants