mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-21 12:51:16 +08:00
fix(core): Normalise Sentry stack frame paths to stable app:/// roots (#31421)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
n8n-cat-bot[bot]
Claude Opus 4.7
parent
d6eb844c32
commit
e620545c93
@@ -6,7 +6,7 @@ import { ApplicationError, BaseError } from 'n8n-workflow';
|
||||
import type { Mock } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { ErrorReporter } from '../error-reporter';
|
||||
import { ErrorReporter, normalizeFrameFilename } from '../error-reporter';
|
||||
|
||||
vi.mock('@sentry/node', () => ({
|
||||
init: vi.fn(),
|
||||
@@ -25,6 +25,73 @@ vi.mock('@sentry/node-native', () => ({
|
||||
|
||||
vi.spyOn(process, 'on');
|
||||
|
||||
describe('normalizeFrameFilename', () => {
|
||||
it('rewrites pnpm-nested n8n-core frames to a stable app:/// root', () => {
|
||||
const input =
|
||||
'/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+packages+core_abc123/node_modules/n8n-core/src/execution-engine/workflow-execute.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe(
|
||||
'app:///n8n-core/src/execution-engine/workflow-execute.ts',
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites pnpm-nested n8n-nodes-base frames to a stable app:/// root', () => {
|
||||
const input =
|
||||
'/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-nodes-base@1.2.3_xyz789/node_modules/n8n-nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe(
|
||||
'app:///n8n-nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts',
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites pnpm-nested @n8n scoped frames to a stable app:/// root', () => {
|
||||
const input =
|
||||
'/usr/local/lib/node_modules/n8n/node_modules/.pnpm/@n8n+n8n-nodes-langchain@1.0.0_peer+hash/node_modules/@n8n/n8n-nodes-langchain/nodes/agents/Agent.node.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe(
|
||||
'app:///@n8n/n8n-nodes-langchain/nodes/agents/Agent.node.ts',
|
||||
);
|
||||
});
|
||||
|
||||
it('rewrites cli install-prefix frames (src) to a stable app:/// root', () => {
|
||||
const input = '/usr/local/lib/node_modules/n8n/src/commands/start.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('app:///src/commands/start.ts');
|
||||
});
|
||||
|
||||
it('rewrites cli install-prefix frames (bin) to a stable app:/// root', () => {
|
||||
const input = '/usr/local/lib/node_modules/n8n/bin/n8n';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('app:///bin/n8n');
|
||||
});
|
||||
|
||||
it('prefers the pnpm replacement when both segments are present', () => {
|
||||
const input =
|
||||
'/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+packages+core_abc123/node_modules/n8n-core/src/foo.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('app:///n8n-core/src/foo.ts');
|
||||
});
|
||||
|
||||
it('leaves unrelated frames unchanged', () => {
|
||||
const input = '/some/other/path/file.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('/some/other/path/file.ts');
|
||||
});
|
||||
|
||||
it('leaves node-internal frames unchanged', () => {
|
||||
const input = 'node:internal/process/task_queues';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('node:internal/process/task_queues');
|
||||
});
|
||||
|
||||
it('handles pnpm frames not under the cli install prefix (e.g. dev installs)', () => {
|
||||
const input =
|
||||
'/home/dev/n8n/node_modules/.pnpm/n8n-core@file+packages+core_abc/node_modules/n8n-core/src/x.ts';
|
||||
|
||||
expect(normalizeFrameFilename(input)).toBe('app:///n8n-core/src/x.ts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ErrorReporter', () => {
|
||||
const errorReporter = new ErrorReporter(mock(), mock());
|
||||
const event = {} as ErrorEvent;
|
||||
|
||||
@@ -56,6 +56,21 @@ const SIX_WEEKS_IN_MS = 6 * 7 * ONE_DAY_IN_MS;
|
||||
const RELEASE_EXPIRATION_WARNING =
|
||||
'Error tracking disabled because this release is older than 6 weeks.';
|
||||
|
||||
const PNPM_NESTED_FRAME_RE = /.*\/node_modules\/\.pnpm\/[^/]+\/node_modules\//;
|
||||
const N8N_CLI_INSTALL_PREFIX = '/usr/local/lib/node_modules/n8n/';
|
||||
|
||||
/**
|
||||
* Normalises a Sentry stack-frame filename so that pnpm-nested dependency
|
||||
* paths and the n8n CLI install prefix become stable `app:///` roots. This
|
||||
* lets Sentry code mappings match `n8n-core`, `n8n-nodes-base`, and cli
|
||||
* frames without depending on the per-release pnpm peer-deps hash segment.
|
||||
*/
|
||||
export function normalizeFrameFilename(filename: string): string {
|
||||
return filename
|
||||
.replace(PNPM_NESTED_FRAME_RE, 'app:///')
|
||||
.replace(N8N_CLI_INSTALL_PREFIX, 'app:///');
|
||||
}
|
||||
|
||||
@Service()
|
||||
export class ErrorReporter {
|
||||
private expirationTimer?: NodeJS.Timeout;
|
||||
@@ -218,7 +233,15 @@ export class ErrorReporter {
|
||||
ignoreSpans: [`GET ${healthEndpoint}`, 'GET /metrics', 'SET search_path TO'],
|
||||
integrations: (integrations) => [
|
||||
...integrations.filter(({ name }) => enabledIntegrations.has(name)),
|
||||
rewriteFramesIntegration({ root: '/' }),
|
||||
rewriteFramesIntegration({
|
||||
root: '/',
|
||||
iteratee: (frame) => {
|
||||
if (frame.filename) {
|
||||
frame.filename = normalizeFrameFilename(frame.filename);
|
||||
}
|
||||
return frame;
|
||||
},
|
||||
}),
|
||||
requestDataIntegration({
|
||||
include: {
|
||||
cookies: false,
|
||||
|
||||
Reference in New Issue
Block a user