UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

134 lines (131 loc) • 5.65 kB
import * as os from 'node:os'; import { trace } from '@opentelemetry/api'; import { applySdkMetadata, debug, _INTERNAL_flushLogsBuffer, _INTERNAL_flushMetricsBuffer, _INTERNAL_setDeferSegmentSpanCapture, SDK_VERSION, _INTERNAL_clearAiProviderSkips } from '@sentry/core'; import { ServerRuntimeClient } from '@sentry/core/server'; import { registerPrepareSpanScope, setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import { registerDiagnosticsChannelInjection } from '@sentry/server-runtime-injection/register'; import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; import { threadId, isMainThread } from 'worker_threads'; import { DEBUG_BUILD } from '../debug-build.js'; const DEFAULT_CLIENT_REPORT_FLUSH_INTERVAL_MS = 6e4; class NodeClient extends ServerRuntimeClient { constructor(options) { const serverName = options.includeServerName === false ? void 0 : options.serverName || global.process.env.SENTRY_NAME || os.hostname(); const clientOptions = { ...options, platform: "node", // Use provided runtime or default to 'node' with current process version runtime: options.runtime || { name: "node", version: global.process.version }, serverName }; applySdkMetadata(clientOptions, "node"); debug.log(`Initializing Sentry: process: ${process.pid}, thread: ${isMainThread ? "main" : `worker-${threadId}`}.`); super(clientOptions); this._logOnExitFlushListener = () => { _INTERNAL_flushLogsBuffer(this); }; this._metricsOnExitFlushListener = () => { _INTERNAL_flushMetricsBuffer(this); }; if (serverName) { this.on("beforeCaptureLog", (log) => { log.attributes = { ...log.attributes, "server.address": serverName }; }); } process.on("beforeExit", this._logOnExitFlushListener); process.on("beforeExit", this._metricsOnExitFlushListener); _INTERNAL_setDeferSegmentSpanCapture(this); registerPrepareSpanScope(this); if ((typeof __SENTRY_CHANNEL_INJECTION__ === "undefined" || __SENTRY_CHANNEL_INJECTION__) && options.enableRuntimeChannelInjection !== false) { registerDiagnosticsChannelInjection(); } } /** @inheritDoc */ init() { if (this.getOptions().enableOpenTelemetrySetup) { this.asyncLocalStorageLookup = setOpenTelemetryContextAsyncContextStrategy(); } else { this.asyncLocalStorageLookup = { asyncLocalStorage: setAsyncLocalStorageAsyncContextStrategy() }; } super.init(); } /** Get the OTEL tracer. */ get tracer() { if (this._tracer) { return this._tracer; } const name = "@sentry/node"; const version = SDK_VERSION; const tracer = trace.getTracer(name, version); this._tracer = tracer; return tracer; } /** @inheritDoc */ // @ts-expect-error - PromiseLike is a subset of Promise async flush(timeout) { await this.traceProvider?.forceFlush(); if (this.getOptions().sendClientReports) { this._flushOutcomes(); } return super.flush(timeout); } /** @inheritDoc */ // @ts-expect-error - PromiseLike is a subset of Promise async close(timeout) { if (this._clientReportInterval) { clearInterval(this._clientReportInterval); } if (this._clientReportOnExitFlushListener) { process.off("beforeExit", this._clientReportOnExitFlushListener); } if (this._logOnExitFlushListener) { process.off("beforeExit", this._logOnExitFlushListener); } if (this._metricsOnExitFlushListener) { process.off("beforeExit", this._metricsOnExitFlushListener); } const allEventsSent = await super.close(timeout); if (this.traceProvider) { await this.traceProvider.shutdown(); } return allEventsSent; } /** * Will start tracking client reports for this client. * * NOTICE: This method will create an interval that is periodically called and attach a `process.on('beforeExit')` * hook. To clean up these resources, call `.close()` when you no longer intend to use the client. Not doing so will * result in a memory leak. */ // The reason client reports need to be manually activated with this method instead of just enabling them in a // constructor, is that if users periodically and unboundedly create new clients, we will create more and more // intervals and beforeExit listeners, thus leaking memory. In these situations, users are required to call // `client.close()` in order to dispose of the acquired resources. // We assume that calling this method in Sentry.init() is a sensible default, because calling Sentry.init() over and // over again would also result in memory leaks. // Note: We have experimented with using `FinalizationRegisty` to clear the interval when the client is garbage // collected, but it did not work, because the cleanup function never got called. startClientReportTracking() { const clientOptions = this.getOptions(); if (clientOptions.sendClientReports) { this._clientReportOnExitFlushListener = () => { this._flushOutcomes(); }; this._clientReportInterval = setInterval(() => { DEBUG_BUILD && debug.log("Flushing client reports based on interval."); this._flushOutcomes(); }, clientOptions.clientReportFlushInterval ?? DEFAULT_CLIENT_REPORT_FLUSH_INTERVAL_MS).unref(); process.on("beforeExit", this._clientReportOnExitFlushListener); } } /** @inheritDoc */ _setupIntegrations() { _INTERNAL_clearAiProviderSkips(); super._setupIntegrations(); } } export { NodeClient }; //# sourceMappingURL=client.js.map