mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
feat(core): Add profiling support via sentry (#25049)
This commit is contained in:
parent
779a04f471
commit
659de949cb
22 changed files with 298 additions and 45 deletions
|
|
@ -94,7 +94,7 @@
|
|||
"@azure/identity": "4.13.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@mistralai/mistralai": "^1.10.0",
|
||||
"@n8n/typeorm>@sentry/node": "catalog:",
|
||||
"@n8n/typeorm>@sentry/node": "catalog:sentry",
|
||||
"@types/node": "^20.17.50",
|
||||
"axios": "1.12.0",
|
||||
"chokidar": "4.0.3",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
/** Schema for sample rates (0.0 to 1.0). */
|
||||
export const sampleRateSchema = z.number({ coerce: true }).min(0).max(1);
|
||||
|
||||
@Config
|
||||
export class SentryConfig {
|
||||
/** Sentry DSN (data source name) for the backend. */
|
||||
|
|
@ -10,6 +15,26 @@ export class SentryConfig {
|
|||
@Env('N8N_FRONTEND_SENTRY_DSN')
|
||||
frontendDsn: string = '';
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry traces (0.0 to 1.0).
|
||||
* This determines whether tracing is enabled and what percentage of
|
||||
* transactions are traced.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_TRACES_SAMPLE_RATE', sampleRateSchema)
|
||||
tracesSampleRate: number = 0;
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry profiling (0.0 to 1.0).
|
||||
* This determines whether profiling is enabled and what percentage of
|
||||
* transactions are profiled.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_PROFILES_SAMPLE_RATE', sampleRateSchema)
|
||||
profilesSampleRate: number = 0;
|
||||
|
||||
/**
|
||||
* Environment of the n8n instance.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export { Config, Env, Nested } from './decorators';
|
|||
export { AiConfig } from './configs/ai.config';
|
||||
export { DatabaseConfig, SqliteConfig } from './configs/database.config';
|
||||
export { InstanceSettingsConfig } from './configs/instance-settings-config';
|
||||
export { sampleRateSchema } from './configs/sentry.config';
|
||||
export type { TaskRunnerMode } from './configs/runners.config';
|
||||
export { TaskRunnersConfig } from './configs/runners.config';
|
||||
export { SecurityConfig } from './configs/security.config';
|
||||
|
|
|
|||
|
|
@ -281,6 +281,8 @@ describe('GlobalConfig', () => {
|
|||
frontendDsn: '',
|
||||
environment: '',
|
||||
deploymentName: '',
|
||||
profilesSampleRate: 0,
|
||||
tracesSampleRate: 0,
|
||||
},
|
||||
logging: {
|
||||
level: 'info',
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
],
|
||||
"devDependencies": {
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@sentry/node": "catalog:"
|
||||
"@sentry/node": "catalog:sentry"
|
||||
},
|
||||
"dependencies": {
|
||||
"callsites": "catalog:"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from src.env import read_str_env
|
||||
from src.env import read_str_env, read_float_env
|
||||
from src.constants import (
|
||||
ENV_DEPLOYMENT_NAME,
|
||||
ENV_ENVIRONMENT,
|
||||
ENV_N8N_VERSION,
|
||||
ENV_SENTRY_DSN,
|
||||
ENV_SENTRY_PROFILES_SAMPLE_RATE,
|
||||
ENV_SENTRY_TRACES_SAMPLE_RATE,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -15,6 +17,8 @@ class SentryConfig:
|
|||
n8n_version: str
|
||||
environment: str
|
||||
deployment_name: str
|
||||
profiles_sample_rate: float
|
||||
traces_sample_rate: float
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
|
|
@ -27,4 +31,6 @@ class SentryConfig:
|
|||
n8n_version=read_str_env(ENV_N8N_VERSION, ""),
|
||||
environment=read_str_env(ENV_ENVIRONMENT, ""),
|
||||
deployment_name=read_str_env(ENV_DEPLOYMENT_NAME, ""),
|
||||
profiles_sample_rate=read_float_env(ENV_SENTRY_PROFILES_SAMPLE_RATE, 0),
|
||||
traces_sample_rate=read_float_env(ENV_SENTRY_TRACES_SAMPLE_RATE, 0),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ ENV_SENTRY_DSN = "N8N_SENTRY_DSN"
|
|||
ENV_N8N_VERSION = "N8N_VERSION"
|
||||
ENV_ENVIRONMENT = "ENVIRONMENT"
|
||||
ENV_DEPLOYMENT_NAME = "DEPLOYMENT_NAME"
|
||||
ENV_SENTRY_PROFILES_SAMPLE_RATE = "N8N_SENTRY_PROFILES_SAMPLE_RATE"
|
||||
ENV_SENTRY_TRACES_SAMPLE_RATE = "N8N_SENTRY_TRACES_SAMPLE_RATE"
|
||||
|
||||
# Sentry
|
||||
SENTRY_TAG_SERVER_TYPE_KEY = "server_type"
|
||||
|
|
|
|||
|
|
@ -43,3 +43,15 @@ def read_bool_env(env_name: str, default: bool) -> bool:
|
|||
if value is None:
|
||||
return default
|
||||
return value.strip().lower() == "true"
|
||||
|
||||
|
||||
def read_float_env(env_name: str, default: float) -> float:
|
||||
value = read_env(env_name)
|
||||
if value is None:
|
||||
return default
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Environment variable {env_name} must be a float, got '{value}'"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,18 +20,48 @@ class TaskRunnerSentry:
|
|||
import sentry_sdk
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn=self.config.dsn,
|
||||
release=f"n8n@{self.config.n8n_version}",
|
||||
environment=self.config.environment,
|
||||
server_name=self.config.deployment_name,
|
||||
before_send=self._filter_out_ignored_errors,
|
||||
attach_stacktrace=True,
|
||||
send_default_pii=False,
|
||||
auto_enabling_integrations=False,
|
||||
default_integrations=True,
|
||||
integrations=[LoggingIntegration(level=logging.ERROR)],
|
||||
)
|
||||
integrations = [LoggingIntegration(level=logging.ERROR)]
|
||||
|
||||
is_profiling_enabled = self.config.profiles_sample_rate > 0
|
||||
|
||||
if is_profiling_enabled:
|
||||
try:
|
||||
# Import profiling integration lazily to avoid hard dependency
|
||||
import sentry_sdk.profiler # noqa: F401
|
||||
|
||||
self.logger.info("Sentry profiling integration loaded")
|
||||
except ImportError:
|
||||
self.logger.warning(
|
||||
"Sentry profiling is enabled but sentry-sdk profiling is not available. "
|
||||
"Install with: uv sync --all-extras"
|
||||
)
|
||||
|
||||
is_tracing_enabled = self.config.traces_sample_rate > 0
|
||||
if is_profiling_enabled and not is_tracing_enabled:
|
||||
self.logger.warning(
|
||||
"Profiling is enabled but tracing is disabled. Profiling will not work."
|
||||
)
|
||||
|
||||
init_options = {
|
||||
"dsn": self.config.dsn,
|
||||
"release": f"n8n@{self.config.n8n_version}",
|
||||
"environment": self.config.environment,
|
||||
"server_name": self.config.deployment_name,
|
||||
"before_send": self._filter_out_ignored_errors,
|
||||
"attach_stacktrace": True,
|
||||
"send_default_pii": False,
|
||||
"auto_enabling_integrations": False,
|
||||
"default_integrations": True,
|
||||
"integrations": integrations,
|
||||
}
|
||||
|
||||
if self.config.traces_sample_rate > 0:
|
||||
init_options["traces_sample_rate"] = self.config.traces_sample_rate
|
||||
|
||||
if is_profiling_enabled:
|
||||
init_options["profiles_sample_rate"] = self.config.profiles_sample_rate
|
||||
|
||||
sentry_sdk.init(**init_options)
|
||||
sentry_sdk.set_tag(SENTRY_TAG_SERVER_TYPE_KEY, SENTRY_TAG_SERVER_TYPE_VALUE)
|
||||
self.logger.info("Sentry ready")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
from src.env import read_env, read_int_env, read_bool_env, read_str_env
|
||||
from src.env import read_env, read_int_env, read_bool_env, read_str_env, read_float_env
|
||||
|
||||
|
||||
class TestReadEnv:
|
||||
|
|
@ -179,3 +179,45 @@ class TestReadBoolEnv:
|
|||
with patch.dict(os.environ, clear=True):
|
||||
result = read_bool_env("TEST_BOOL", default=True)
|
||||
assert result is True
|
||||
|
||||
|
||||
class TestReadFloatEnv:
|
||||
def test_returns_float_from_direct_env(self):
|
||||
with patch.dict(os.environ, {"TEST_FLOAT": "3.14"}):
|
||||
result = read_float_env("TEST_FLOAT", default=0.0)
|
||||
assert result == 3.14
|
||||
|
||||
def test_returns_float_from_file(self):
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=True) as f:
|
||||
f.write("2.718")
|
||||
f.flush()
|
||||
|
||||
with patch.dict(os.environ, {"TEST_FLOAT_FILE": f.name}):
|
||||
result = read_float_env("TEST_FLOAT", default=0.0)
|
||||
assert result == 2.718
|
||||
|
||||
def test_returns_default_when_not_set(self):
|
||||
with patch.dict(os.environ, clear=True):
|
||||
result = read_float_env("TEST_FLOAT", default=9.99)
|
||||
assert result == 9.99
|
||||
|
||||
def test_raises_error_for_invalid_float(self):
|
||||
with patch.dict(os.environ, {"TEST_FLOAT": "not_a_number"}):
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
read_float_env("TEST_FLOAT", default=0.0)
|
||||
assert "must be a float" in str(exc_info.value)
|
||||
|
||||
def test_handles_negative_numbers(self):
|
||||
with patch.dict(os.environ, {"TEST_FLOAT": "-42.5"}):
|
||||
result = read_float_env("TEST_FLOAT", default=0.0)
|
||||
assert result == -42.5
|
||||
|
||||
def test_handles_integer_values(self):
|
||||
with patch.dict(os.environ, {"TEST_FLOAT": "42"}):
|
||||
result = read_float_env("TEST_FLOAT", default=0.0)
|
||||
assert result == 42.0
|
||||
|
||||
def test_handles_zero(self):
|
||||
with patch.dict(os.environ, {"TEST_FLOAT": "0"}):
|
||||
result = read_float_env("TEST_FLOAT", default=1.0)
|
||||
assert result == 0.0
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ def sentry_config():
|
|||
n8n_version="1.0.0",
|
||||
environment="test",
|
||||
deployment_name="test-deployment",
|
||||
profiles_sample_rate=0,
|
||||
traces_sample_rate=0,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -34,6 +36,8 @@ def disabled_sentry_config():
|
|||
n8n_version="1.0.0",
|
||||
environment="test",
|
||||
deployment_name="test-deployment",
|
||||
profiles_sample_rate=0,
|
||||
traces_sample_rate=0,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -236,6 +240,8 @@ class TestSentryConfig:
|
|||
"N8N_VERSION": "2.0.0",
|
||||
"ENVIRONMENT": "production",
|
||||
"DEPLOYMENT_NAME": "prod-deployment",
|
||||
"N8N_SENTRY_PROFILES_SAMPLE_RATE": "0.5",
|
||||
"N8N_SENTRY_TRACES_SAMPLE_RATE": "0.1",
|
||||
},
|
||||
)
|
||||
def test_from_env_creates_config_from_environment(self):
|
||||
|
|
@ -245,6 +251,8 @@ class TestSentryConfig:
|
|||
assert config.n8n_version == "2.0.0"
|
||||
assert config.environment == "production"
|
||||
assert config.deployment_name == "prod-deployment"
|
||||
assert config.profiles_sample_rate == 0.5
|
||||
assert config.traces_sample_rate == 0.1
|
||||
|
||||
@patch.dict("os.environ", {}, clear=True)
|
||||
def test_from_env_uses_defaults_when_missing(self):
|
||||
|
|
@ -254,3 +262,5 @@ class TestSentryConfig:
|
|||
assert config.n8n_version == ""
|
||||
assert config.environment == ""
|
||||
assert config.deployment_name == ""
|
||||
assert config.profiles_sample_rate == 0
|
||||
assert config.traces_sample_rate == 0
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
"@n8n/config": "workspace:*",
|
||||
"@n8n/di": "workspace:*",
|
||||
"@n8n/errors": "workspace:*",
|
||||
"@sentry/node": "catalog:",
|
||||
"@sentry/node": "catalog:sentry",
|
||||
"acorn": "8.14.0",
|
||||
"acorn-walk": "8.3.4",
|
||||
"lodash": "catalog:",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ describe('TaskRunnerSentry', () => {
|
|||
n8nVersion: '1.0.0',
|
||||
environment: 'local',
|
||||
deploymentName: 'test',
|
||||
profilesSampleRate: 0,
|
||||
tracesSampleRate: 0,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -258,6 +260,11 @@ describe('TaskRunnerSentry', () => {
|
|||
serverName: 'test',
|
||||
serverType: 'task_runner',
|
||||
withEventLoopBlockDetection: false,
|
||||
profilesSampleRate: 0,
|
||||
tracesSampleRate: 0,
|
||||
eligibleIntegrations: {
|
||||
Http: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Config, Env } from '@n8n/config';
|
||||
import { Config, Env, sampleRateSchema } from '@n8n/config';
|
||||
|
||||
@Config
|
||||
export class SentryConfig {
|
||||
|
|
@ -6,6 +6,24 @@ export class SentryConfig {
|
|||
@Env('N8N_SENTRY_DSN')
|
||||
dsn: string = '';
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry profiling (0.0 to 1.0).
|
||||
* This determines what percentage of transactions are profiled.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_PROFILES_SAMPLE_RATE', sampleRateSchema)
|
||||
profilesSampleRate: number = 0;
|
||||
|
||||
/**
|
||||
* Sample rate for Sentry traces (0.0 to 1.0).
|
||||
* This determines what percentage of transactions are profiled.
|
||||
*
|
||||
* @default 0 (disabled)
|
||||
*/
|
||||
@Env('N8N_SENTRY_TRACES_SAMPLE_RATE', sampleRateSchema)
|
||||
tracesSampleRate: number = 0;
|
||||
|
||||
//#region Metadata about the environment
|
||||
|
||||
@Env('N8N_VERSION')
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ describe('JsTaskRunner', () => {
|
|||
deploymentName: '',
|
||||
environment: '',
|
||||
n8nVersion: '',
|
||||
profilesSampleRate: 0,
|
||||
tracesSampleRate: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ export class TaskRunnerSentry {
|
|||
) {}
|
||||
|
||||
async initIfEnabled() {
|
||||
const { dsn, n8nVersion, environment, deploymentName } = this.config;
|
||||
const { dsn, n8nVersion, environment, deploymentName, profilesSampleRate, tracesSampleRate } =
|
||||
this.config;
|
||||
|
||||
if (!dsn) return;
|
||||
|
||||
|
|
@ -27,6 +28,11 @@ export class TaskRunnerSentry {
|
|||
serverName: deploymentName,
|
||||
beforeSendFilter: this.filterOutUserCodeErrors,
|
||||
withEventLoopBlockDetection: false,
|
||||
tracesSampleRate,
|
||||
profilesSampleRate,
|
||||
eligibleIntegrations: {
|
||||
Http: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@
|
|||
"@n8n_io/license-sdk": "2.25.0",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@rudderstack/rudder-sdk-node": "3.0.0",
|
||||
"@sentry/node": "catalog:",
|
||||
"@sentry/node": "catalog:sentry",
|
||||
"aws4": "1.11.0",
|
||||
"axios": "catalog:",
|
||||
"bcryptjs": "2.4.3",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ export abstract class BaseCommand<F = never> {
|
|||
this.dbConnection = Container.get(DbConnection);
|
||||
this.errorReporter = Container.get(ErrorReporter);
|
||||
|
||||
const { backendDsn, environment, deploymentName } = this.globalConfig.sentry;
|
||||
const { backendDsn, environment, deploymentName, profilesSampleRate, tracesSampleRate } =
|
||||
this.globalConfig.sentry;
|
||||
await this.errorReporter.init({
|
||||
serverType: this.instanceSettings.instanceType,
|
||||
dsn: backendDsn,
|
||||
|
|
@ -93,6 +94,16 @@ export abstract class BaseCommand<F = never> {
|
|||
serverName: deploymentName,
|
||||
releaseDate: N8N_RELEASE_DATE,
|
||||
withEventLoopBlockDetection: true,
|
||||
tracesSampleRate,
|
||||
profilesSampleRate,
|
||||
eligibleIntegrations: {
|
||||
Express: true,
|
||||
Http: true,
|
||||
Postgres: this.globalConfig.database.type === 'postgresdb',
|
||||
Redis:
|
||||
this.globalConfig.executions.mode === 'queue' ||
|
||||
this.globalConfig.cache.backend === 'redis',
|
||||
},
|
||||
});
|
||||
|
||||
process.once('SIGTERM', this.onTerminationSignal('SIGTERM'));
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@
|
|||
"@n8n/constants": "workspace:*",
|
||||
"@n8n/decorators": "workspace:*",
|
||||
"@n8n/di": "workspace:*",
|
||||
"@sentry/node": "catalog:",
|
||||
"@sentry/node-native": "^10.36.0",
|
||||
"@sentry/node": "catalog:sentry",
|
||||
"@sentry/node-native": "catalog:sentry",
|
||||
"@sentry/profiling-node": "catalog:sentry",
|
||||
"axios": "catalog:",
|
||||
"callsites": "catalog:",
|
||||
"chardet": "2.0.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { inProduction, inTest, Logger } from '@n8n/backend-common';
|
||||
import { inTest, Logger } from '@n8n/backend-common';
|
||||
import { type InstanceType } from '@n8n/constants';
|
||||
import { Service } from '@n8n/di';
|
||||
import type { ReportingOptions } from '@n8n/errors';
|
||||
|
|
@ -8,6 +8,8 @@ import { AxiosError } from 'axios';
|
|||
import { ApplicationError, ExecutionCancelledError, BaseError } from 'n8n-workflow';
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
type SentryIntegration = 'Redis' | 'Postgres' | 'Http' | 'Express';
|
||||
|
||||
type ErrorReporterInitOptions = {
|
||||
serverType: InstanceType | 'task_runner';
|
||||
dsn: string;
|
||||
|
|
@ -19,11 +21,23 @@ type ErrorReporterInitOptions = {
|
|||
/** Whether to enable event loop block detection, if Sentry is enabled. */
|
||||
withEventLoopBlockDetection: boolean;
|
||||
|
||||
/** Sample rate for Sentry traces (0.0 to 1.0). 0 means disabled */
|
||||
tracesSampleRate: number;
|
||||
|
||||
/** Sample rate for Sentry profiling (0.0 to 1.0). 0 means disabled */
|
||||
profilesSampleRate: number;
|
||||
|
||||
/**
|
||||
* Function to allow filtering out errors before they are sent to Sentry.
|
||||
* Return true if the error should be filtered out.
|
||||
*/
|
||||
beforeSendFilter?: (event: ErrorEvent, hint: EventHint) => boolean;
|
||||
|
||||
/**
|
||||
* Integrations eligible for enablement. `tracesSampleRate` still determines
|
||||
* whether they are actually enabled or not.
|
||||
*/
|
||||
eligibleIntegrations?: Partial<Record<SentryIntegration, boolean>>;
|
||||
};
|
||||
|
||||
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||
|
|
@ -88,6 +102,9 @@ export class ErrorReporter {
|
|||
serverName,
|
||||
releaseDate,
|
||||
withEventLoopBlockDetection,
|
||||
profilesSampleRate,
|
||||
tracesSampleRate,
|
||||
eligibleIntegrations = {},
|
||||
}: ErrorReporterInitOptions) {
|
||||
if (inTest) return;
|
||||
|
||||
|
|
@ -123,13 +140,28 @@ export class ErrorReporter {
|
|||
const { init, captureException, setTag } = await import('@sentry/node');
|
||||
const { requestDataIntegration, rewriteFramesIntegration } = await import('@sentry/node');
|
||||
|
||||
const enabledIntegrations = [
|
||||
// Most of the integrations are listed here:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/
|
||||
const enabledIntegrations = new Set([
|
||||
'InboundFilters',
|
||||
'FunctionToString',
|
||||
'LinkedErrors',
|
||||
'OnUnhandledRejection',
|
||||
'ContextLines',
|
||||
];
|
||||
]);
|
||||
|
||||
const isTracingEnabled = tracesSampleRate > 0;
|
||||
if (isTracingEnabled) {
|
||||
const tracingIntegrations: SentryIntegration[] = ['Http', 'Postgres', 'Redis', 'Express'];
|
||||
tracingIntegrations
|
||||
.filter((integrationName) => !!eligibleIntegrations[integrationName])
|
||||
.forEach((integrationName) => enabledIntegrations.add(integrationName));
|
||||
}
|
||||
|
||||
const isProfilingEnabled = profilesSampleRate > 0;
|
||||
if (isProfilingEnabled && !isTracingEnabled) {
|
||||
this.logger.warn('Profiling is enabled but tracing is disabled. Profiling will not work.');
|
||||
}
|
||||
|
||||
const eventLoopBlockIntegration = withEventLoopBlockDetection
|
||||
? // The EventLoopBlockIntegration doesn't automatically include the
|
||||
|
|
@ -140,16 +172,18 @@ export class ErrorReporter {
|
|||
})
|
||||
: [];
|
||||
|
||||
const profilingIntegration = isProfilingEnabled ? await this.getProfilingIntegration() : [];
|
||||
|
||||
init({
|
||||
dsn,
|
||||
release,
|
||||
environment,
|
||||
tracesSampleRate: inProduction ? 0.01 : 0,
|
||||
serverName,
|
||||
beforeBreadcrumb: () => null,
|
||||
...(isTracingEnabled ? { tracesSampleRate } : {}),
|
||||
...(isProfilingEnabled ? { profilesSampleRate, profileLifecycle: 'trace' } : {}),
|
||||
beforeSend: this.beforeSend.bind(this) as NodeOptions['beforeSend'],
|
||||
integrations: (integrations) => [
|
||||
...integrations.filter(({ name }) => enabledIntegrations.includes(name)),
|
||||
...integrations.filter(({ name }) => enabledIntegrations.has(name)),
|
||||
rewriteFramesIntegration({ root: '/' }),
|
||||
requestDataIntegration({
|
||||
include: {
|
||||
|
|
@ -161,6 +195,7 @@ export class ErrorReporter {
|
|||
},
|
||||
}),
|
||||
...eventLoopBlockIntegration,
|
||||
...profilingIntegration,
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -276,10 +311,22 @@ export class ErrorReporter {
|
|||
}),
|
||||
];
|
||||
} catch {
|
||||
this.logger.debug(
|
||||
this.logger.warn(
|
||||
"Sentry's event loop block integration is disabled, because the native binary for `@sentry/node-native` was not found",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async getProfilingIntegration() {
|
||||
try {
|
||||
const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
|
||||
return [nodeProfilingIntegration()];
|
||||
} catch {
|
||||
this.logger.warn(
|
||||
'Sentry profiling is disabled, because the `@sentry/profiling-node` package was not found',
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,6 @@ catalogs:
|
|||
'@n8n_io/ai-assistant-sdk':
|
||||
specifier: 1.20.0
|
||||
version: 1.20.0
|
||||
'@sentry/node':
|
||||
specifier: ^10.36.0
|
||||
version: 10.36.0
|
||||
'@types/basic-auth':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
|
|
@ -251,6 +248,16 @@ catalogs:
|
|||
vue-router:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
sentry:
|
||||
'@sentry/node':
|
||||
specifier: ^10.36.0
|
||||
version: 10.36.0
|
||||
'@sentry/node-native':
|
||||
specifier: ^10.36.0
|
||||
version: 10.36.0
|
||||
'@sentry/profiling-node':
|
||||
specifier: ^10.36.0
|
||||
version: 10.36.0
|
||||
storybook:
|
||||
'@chromatic-com/storybook':
|
||||
specifier: ^4.1.3
|
||||
|
|
@ -958,7 +965,7 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../typescript-config
|
||||
'@sentry/node':
|
||||
specifier: 'catalog:'
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
|
||||
packages/@n8n/eslint-config:
|
||||
|
|
@ -1579,7 +1586,7 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../errors
|
||||
'@sentry/node':
|
||||
specifier: 'catalog:'
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
acorn:
|
||||
specifier: 8.14.0
|
||||
|
|
@ -1739,7 +1746,7 @@ importers:
|
|||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
'@sentry/node':
|
||||
specifier: 'catalog:'
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
aws4:
|
||||
specifier: 1.11.0
|
||||
|
|
@ -2091,10 +2098,13 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../@n8n/di
|
||||
'@sentry/node':
|
||||
specifier: 'catalog:'
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
'@sentry/node-native':
|
||||
specifier: ^10.36.0
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
'@sentry/profiling-node':
|
||||
specifier: catalog:sentry
|
||||
version: 10.36.0
|
||||
axios:
|
||||
specifier: 1.12.0
|
||||
|
|
@ -7462,6 +7472,10 @@ packages:
|
|||
resolution: {integrity: sha512-zPjz7AbcxEyx8AHj8xvp28fYtPTPWU1XcNtymhAHJLS9CXOblqSC7W02Jxz6eo3eR1/pLyOo6kJBUjvLe9EoFA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry-internal/node-cpu-profiler@2.2.0':
|
||||
resolution: {integrity: sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry-internal/node-native-stacktrace@0.3.0':
|
||||
resolution: {integrity: sha512-ef0M2y2JDrC/H0AxMJJQInGTdZTlnwa6AAVWR4fMOpJRubkfdH2IZXE/nWU0Nj74oeJLQgdPtS6DeijLJtqq8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -7572,6 +7586,11 @@ packages:
|
|||
'@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0
|
||||
'@opentelemetry/semantic-conventions': ^1.37.0
|
||||
|
||||
'@sentry/profiling-node@10.36.0':
|
||||
resolution: {integrity: sha512-lKaFPFN/XgX2LjUstSPWhp0xyifMC71MG8OFbgpy23n1Nnl5TExZehm7Ppaoad/Rhc2cfF3WJByOKgsHasX1Rg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@sentry/vite-plugin@4.3.0':
|
||||
resolution: {integrity: sha512-MeTAHMmTOgBPMAjeW7/ONyXwgScZdaFFtNiALKcAODnVqC7eoHdSRIWeH5mkLr2Dvs7nqtBaDpKxRjUBgfm9LQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
|
@ -14655,9 +14674,6 @@ packages:
|
|||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
module-details-from-path@1.0.3:
|
||||
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
||||
|
||||
module-details-from-path@1.0.4:
|
||||
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
|
||||
|
||||
|
|
@ -23983,6 +23999,11 @@ snapshots:
|
|||
dependencies:
|
||||
'@sentry/core': 10.36.0
|
||||
|
||||
'@sentry-internal/node-cpu-profiler@2.2.0':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
node-abi: 3.75.0
|
||||
|
||||
'@sentry-internal/node-native-stacktrace@0.3.0':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
|
|
@ -24141,6 +24162,14 @@ snapshots:
|
|||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
'@sentry/core': 10.36.0
|
||||
|
||||
'@sentry/profiling-node@10.36.0':
|
||||
dependencies:
|
||||
'@sentry-internal/node-cpu-profiler': 2.2.0
|
||||
'@sentry/core': 10.36.0
|
||||
'@sentry/node': 10.36.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sentry/vite-plugin@4.3.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 4.3.0(encoding@0.1.13)
|
||||
|
|
@ -25451,7 +25480,7 @@ snapshots:
|
|||
'@ts-morph/common@0.27.0':
|
||||
dependencies:
|
||||
fast-glob: 3.3.3
|
||||
minimatch: 10.0.3
|
||||
minimatch: 10.1.1
|
||||
path-browserify: 1.0.1
|
||||
|
||||
'@tsconfig/node10@1.0.11':
|
||||
|
|
@ -33234,8 +33263,6 @@ snapshots:
|
|||
ast-module-types: 6.0.1
|
||||
node-source-walk: 7.0.1
|
||||
|
||||
module-details-from-path@1.0.3: {}
|
||||
|
||||
module-details-from-path@1.0.4: {}
|
||||
|
||||
module-lookup-amd@9.0.5:
|
||||
|
|
@ -35046,7 +35073,7 @@ snapshots:
|
|||
require-in-the-middle@8.0.1:
|
||||
dependencies:
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
module-details-from-path: 1.0.3
|
||||
module-details-from-path: 1.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ catalogs:
|
|||
eslint-plugin-playwright: 2.2.2
|
||||
playwright: 1.57.0
|
||||
playwright-core: 1.57.0
|
||||
sentry:
|
||||
'@sentry/node': '^10.36.0'
|
||||
'@sentry/node-native': '^10.36.0'
|
||||
'@sentry/profiling-node': '^10.36.0'
|
||||
|
||||
minimumReleaseAge: 4320
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue