Skip to content

Commit afc9469

Browse files
committed
Move process label to a global context
1 parent 360c080 commit afc9469

File tree

12 files changed

+59
-53
lines changed

12 files changed

+59
-53
lines changed

docs/node-renderer/error-reporting-and-tracing.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@ Import these functions from `@shakacode-tools/react-on-rails-pro-node-renderer/i
4747
### Error reporting services
4848
- `addErrorNotifier` and `addMessageNotifier` tell React on Rails Pro how to report errors to your chosen service.
4949
- Use `addNotifier` if the service uses the same reporting function for both JavaScript `Error`s and string messages.
50+
- `globalContext` contains the current process's label.
5051

5152
For example, integrating with BugSnag can be as simple as
5253
```js
5354
const Bugsnag = require('@bugsnag/js');
54-
const { addNotifier } = require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/api');
55+
const { addNotifier, globalContext } = require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/api');
5556

56-
Bugsnag.start({ /* your options */ });
57+
Bugsnag.start({
58+
metadata: globalContext,
59+
/* your apiKey and other options */
60+
});
5761

5862
addNotifier((msg) => { Bugsnag.notify(msg); });
5963
```

packages/node-renderer/src/integrations/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
* @module
2424
*/
2525

26-
export { addErrorNotifier, addMessageNotifier, addNotifier } from '../shared/errorReporter';
26+
export { globalContext } from '../shared/log';
27+
export { addErrorNotifier, addMessageNotifier, addNotifier, message, error } from '../shared/errorReporter';
2728
export {
2829
setupTracing,
2930
TracingContext,
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import { notify } from '@honeybadger-io/js';
1+
import * as HoneybadgerModule from '@honeybadger-io/js';
22
import { addNotifier } from '../shared/errorReporter';
3+
import { globalContext } from '../shared/log';
34

4-
export function init() {
5-
addNotifier((msg) => notify(msg));
5+
// When used in spec/dummy (or otherwise linked with Yalc), global Honeybadger here is from this package's
6+
// node_modules, and the initialization script loads the one from spec/dummy/node_modules instead.
7+
// There is probably a similar issue with Sentry, but at least it doesn't throw on initialization.
8+
// Switching to Yarn workspaces should fix the problem.
9+
/**
10+
* Initializes Honeybadger integration.
11+
* @param Honeybadger only necessary when Node renderer is linked with Yalc.
12+
*/
13+
export function init({ Honeybadger = HoneybadgerModule } = {}) {
14+
Honeybadger.setContext(globalContext);
15+
16+
addNotifier((msg) => Honeybadger.notify(msg));
617
}

packages/node-renderer/src/integrations/sentry.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { captureException, captureMessage, startSpan } from '@sentry/node';
1+
import { captureException, captureMessage, getGlobalScope, startSpan } from '@sentry/node';
22
import { StartSpanOptions } from '@sentry/types';
33
import { addErrorNotifier, addMessageNotifier } from '../shared/errorReporter';
44
import { setupTracing } from '../shared/tracing';
5+
import { globalContext } from '../shared/log';
56

67
declare module '../shared/tracing' {
78
interface UnitOfWorkOptions {
@@ -10,6 +11,8 @@ declare module '../shared/tracing' {
1011
}
1112

1213
export function init({ tracing = false } = {}) {
14+
getGlobalScope().setExtras(globalContext);
15+
1316
addMessageNotifier((msg) => {
1417
captureMessage(msg);
1518
});

packages/node-renderer/src/integrations/sentry6.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { captureException, captureMessage, startTransaction } from '@sentry/node';
1+
import { captureException, captureMessage, getGlobalScope, startTransaction } from '@sentry/node';
22
import { CaptureContext, TransactionContext } from '@sentry/types';
33
import { addErrorNotifier, addMessageNotifier, message } from '../shared/errorReporter';
44
import { setupTracing } from '../shared/tracing';
5+
import { globalContext } from '../shared/log';
56

67
declare module '../shared/tracing' {
78
interface TracingContext {
@@ -14,6 +15,8 @@ declare module '../shared/tracing' {
1415
}
1516

1617
export function init({ tracing = false } = {}) {
18+
getGlobalScope().setExtras(globalContext);
19+
1720
addMessageNotifier((msg, tracingContext) => {
1821
captureMessage(msg, tracingContext?.sentry6);
1922
});

packages/node-renderer/src/shared/locks.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import lockfile, { Options } from 'lockfile';
22
import { promisify } from 'util';
33

4+
import cluster from 'cluster';
45
import debug from './debug';
56
import log from './log';
6-
import { delay, workerIdLabel } from './utils';
7+
import { delay } from './utils';
78

89
const lockfileLockAsync = promisify<string, Options>(lockfile.lock);
910
const lockfileUnlockAsync = promisify(lockfile.unlock);
@@ -48,8 +49,7 @@ const lockfileOptions = {
4849
};
4950

5051
export async function unlock(lockfileName: string) {
51-
debug('Worker %s: About to unlock %s', workerIdLabel(), lockfileName);
52-
log.info('Worker %s: About to unlock %s', workerIdLabel(), lockfileName);
52+
log.info('About to unlock %s', lockfileName);
5353

5454
await lockfileUnlockAsync(lockfileName);
5555
}
@@ -69,22 +69,21 @@ type LockResult = {
6969

7070
export async function lock(filename: string): Promise<LockResult> {
7171
const lockfileName = `${filename}.lock`;
72-
const workerId = workerIdLabel();
7372

7473
try {
75-
debug('Worker %s: About to request lock %s', workerId, lockfileName);
76-
log.info('Worker %s: About to request lock %s', workerId, lockfileName);
74+
log.info('About to request lock %s', lockfileName);
7775
await lockfileLockAsync(lockfileName, lockfileOptions);
7876

77+
const workerId = cluster.worker?.id ?? 'NO WORKER ID';
7978
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- the const may be changed to test threading
8079
if (TEST_LOCKFILE_THREADING) {
8180
debug('Worker %i: handleNewBundleProvided sleeping 5s', workerId);
8281
await delay(5000);
8382
debug('Worker %i: handleNewBundleProvided done sleeping 5s', workerId);
8483
}
85-
debug('After acquired lock in pid', lockfileName);
84+
debug('Worker %i: After acquired lock %s', workerId, lockfileName);
8685
} catch (error) {
87-
log.info('Worker %s: Failed to acquire lock %s, error %s', workerId, lockfileName, error);
86+
log.info(error, 'Failed to acquire lock %s', lockfileName);
8887
return { lockfileName, wasLockAcquired: false, errorMessage: error as Error };
8988
}
9089
return { lockfileName, wasLockAcquired: true, errorMessage: null };

packages/node-renderer/src/shared/log.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pino from 'pino';
22
import type { PrettyOptions } from 'pino-pretty';
3+
import cluster from 'cluster';
34

45
let pretty = false;
56

@@ -13,7 +14,15 @@ if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
1314
}
1415
}
1516

17+
/**
18+
* Context that should be included in every log message (including integrations).
19+
*/
20+
export const globalContext = {
21+
process: cluster.isPrimary ? 'primary' : `worker #${cluster.worker?.id}`,
22+
};
23+
1624
export const sharedLoggerOptions: pino.LoggerOptions = {
25+
base: globalContext,
1726
formatters: {
1827
level: (label) => ({ level: label }),
1928
},
@@ -40,8 +49,6 @@ export const sharedLoggerOptions: pino.LoggerOptions = {
4049
const log = pino(
4150
{
4251
name: 'RORP',
43-
// Omit pid and hostname
44-
base: undefined,
4552
...sharedLoggerOptions,
4653
},
4754
// https://getpino.io/#/docs/help?id=best-performance-for-logging-to-stdout doesn't recommend

packages/node-renderer/src/shared/utils.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import cluster from 'cluster';
21
import path from 'path';
32
import { MultipartFile } from '@fastify/multipart';
43
import { createWriteStream, ensureDir, move, MoveOptions } from 'fs-extra';
@@ -11,11 +10,6 @@ import type { RenderResult } from '../worker/vm';
1110

1211
export const TRUNCATION_FILLER = '\n... TRUNCATED ...\n';
1312

14-
export function workerIdLabel() {
15-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- worker is nullable in the primary process
16-
return cluster?.worker?.id || 'NO WORKER ID';
17-
}
18-
1913
// From https://stackoverflow.com/a/831583/1009332
2014
export function smartTrim(value: unknown, maxLength = getConfig().maxDebugSnippetLength) {
2115
let string;

packages/node-renderer/src/worker.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
formatExceptionMessage,
2222
moveUploadedAssets,
2323
ResponseResult,
24-
workerIdLabel,
2524
saveMultipartFile,
2625
Asset,
2726
} from './shared/utils';
@@ -231,7 +230,7 @@ export default function run(config: Partial<Config>) {
231230
const msg = formatExceptionMessage(
232231
taskDescription,
233232
errorMessage,
234-
`Failed to acquire lock ${lockfileName}. Worker: ${workerIdLabel()}.`,
233+
`Failed to acquire lock ${lockfileName}`,
235234
);
236235
await setResponse(errorResponseResult(msg), res);
237236
} else {
@@ -264,7 +263,7 @@ export default function run(config: Partial<Config>) {
264263
}
265264
} catch (error) {
266265
log.warn({
267-
msg: `Error unlocking ${lockfileName} from worker ${workerIdLabel()}`,
266+
msg: `Error unlocking ${lockfileName}`,
268267
err: error,
269268
task: taskDescription,
270269
});
@@ -312,11 +311,9 @@ export default function run(config: Partial<Config>) {
312311

313312
// In tests we will run worker in master thread, so we need to ensure server
314313
// will not listen:
315-
// we are extracting worker from cluster to avoid false TS error
316-
const { worker } = cluster;
317-
if (cluster.isWorker && worker !== undefined) {
314+
if (cluster.isWorker) {
318315
app.listen({ port }, () => {
319-
log.info(`Node renderer worker #${worker.id} listening on port ${port}!`);
316+
log.info(`Listening on port ${port}!`);
320317
});
321318
}
322319

packages/node-renderer/src/worker/handleRenderRequest.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* @module worker/handleRenderRequest
66
*/
77

8-
import cluster from 'cluster';
98
import path from 'path';
109
import { lock, unlock } from '../shared/locks';
1110
import fileExistsAsync from '../shared/fileExistsAsync';
@@ -14,7 +13,6 @@ import {
1413
Asset,
1514
formatExceptionMessage,
1615
errorResponseResult,
17-
workerIdLabel,
1816
moveUploadedAssets,
1917
ResponseResult,
2018
moveUploadedAsset,
@@ -28,7 +26,7 @@ import { buildVM, getVmBundleFilePath, runInVM } from './vm';
2826

2927
async function prepareResult(renderingRequest: string): Promise<ResponseResult> {
3028
try {
31-
const result = await runInVM(renderingRequest, cluster);
29+
const result = await runInVM(renderingRequest);
3230

3331
let exceptionMessage = null;
3432
if (!result) {
@@ -95,7 +93,7 @@ async function handleNewBundleProvided(
9593
const msg = formatExceptionMessage(
9694
renderingRequest,
9795
errorMessage,
98-
`Failed to acquire lock ${lockfileName}. Worker: ${workerIdLabel()}.`,
96+
`Failed to acquire lock ${lockfileName}.`,
9997
);
10098
return Promise.resolve(errorResponseResult(msg));
10199
}
@@ -144,17 +142,13 @@ to ${bundleFilePathPerTimestamp})`,
144142
}
145143
} finally {
146144
if (lockAcquired) {
147-
log.info('About to unlock %s from worker %i', lockfileName, workerIdLabel());
145+
log.info('About to unlock %s', lockfileName);
148146
try {
149147
if (lockfileName) {
150148
await unlock(lockfileName);
151149
}
152150
} catch (error) {
153-
const msg = formatExceptionMessage(
154-
renderingRequest,
155-
error,
156-
`Error unlocking ${lockfileName} from worker ${workerIdLabel()}.`,
157-
);
151+
const msg = formatExceptionMessage(renderingRequest, error, `Error unlocking ${lockfileName}.`);
158152
log.warn(msg);
159153
}
160154
}
@@ -208,7 +202,7 @@ export = async function handleRenderRequest({
208202

209203
// The bundle exists, but the VM has not yet been created.
210204
// Another worker must have written it or it was saved during deployment.
211-
log.info('Bundle %s exists. Building VM for worker %s.', bundleFilePathPerTimestamp, workerIdLabel());
205+
log.info('Bundle %s exists. Building VM.', bundleFilePathPerTimestamp);
212206
await buildVM(bundleFilePathPerTimestamp);
213207

214208
return prepareResult(renderingRequest);

0 commit comments

Comments
 (0)