|
1 | 1 | /* This file is inspired by https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/sdk/index.ts */
|
2 | 2 |
|
3 | 3 | import type {
|
4 |
| - BaseTransportOptions, |
5 |
| - Integration, |
| 4 | + Client, |
6 | 5 | ServerRuntimeClientOptions,
|
7 | 6 | Transport,
|
8 | 7 | } from "@sentry/core";
|
9 | 8 |
|
| 9 | +import os from "node:os"; |
| 10 | +import path from "node:path"; |
| 11 | + |
10 | 12 | import {
|
11 | 13 | createStackParser,
|
12 | 14 | functionToStringIntegration,
|
13 |
| - getIntegrationsToSetup, |
14 | 15 | initAndBind,
|
15 | 16 | linkedErrorsIntegration,
|
16 | 17 | nodeStackLineParser,
|
17 |
| - requestDataIntegration, |
18 | 18 | ServerRuntimeClient,
|
19 | 19 | stackParserFromStackParserOptions,
|
20 | 20 | } from "@sentry/core";
|
| 21 | +import debug from "debug"; |
21 | 22 |
|
22 |
| -import { getHardhatVersion } from "../../../utils/package.js"; |
23 |
| - |
24 |
| -import { onUncaughtExceptionIntegration } from "./integrations/onuncaughtexception.js"; |
25 |
| -import { onUnhandledRejectionIntegration } from "./integrations/onunhandledrejection.js"; |
| 23 | +import { GENERIC_SERVER_NAME } from "./constants.js"; |
26 | 24 | import { nodeContextIntegration } from "./vendor/integrations/context.js";
|
27 | 25 | import { contextLinesIntegration } from "./vendor/integrations/contextlines.js";
|
28 | 26 | import { createGetModuleFromFilename } from "./vendor/utils/module.js";
|
29 | 27 |
|
30 |
| -interface InitOptions { |
| 28 | +const log = debug("hardhat:core:sentry:init"); |
| 29 | + |
| 30 | +interface GlobalCustomSentryReporterOptions { |
| 31 | + /** |
| 32 | + * Sentry's DSN |
| 33 | + */ |
31 | 34 | dsn: string;
|
32 |
| - transport: (transportOptions: BaseTransportOptions) => Transport; |
33 |
| - release?: string; |
34 |
| - serverName?: string; |
35 |
| - integrations?: (integrations: Integration[]) => Integration[]; |
| 35 | + |
| 36 | + /** |
| 37 | + * The environment used to report the events |
| 38 | + */ |
| 39 | + environment: string; |
| 40 | + |
| 41 | + /** |
| 42 | + * The release of Hardhat |
| 43 | + */ |
| 44 | + release: string; |
| 45 | + |
| 46 | + /** |
| 47 | + * A transport that customizes how we send envelopes to Sentry's server. |
| 48 | + * |
| 49 | + * See the transport module for the different options. |
| 50 | + */ |
| 51 | + transport: Transport; |
| 52 | + |
| 53 | + /** |
| 54 | + * If `true`, the global unhandled rejection and uncaught exception handlers |
| 55 | + * will be installed. |
| 56 | + */ |
| 57 | + installGlobalHandlers?: boolean; |
36 | 58 | }
|
37 | 59 |
|
38 | 60 | /**
|
39 |
| - * Initialize Sentry for Node, without performance instrumentation. |
| 61 | + * This function initializes a custom global sentry reporter/client. |
| 62 | + * |
| 63 | + * There are two reasons why we customize it, instead of using the default one |
| 64 | + * provided by @sentry/node: |
| 65 | + * - @sentry/node has an astronomical amount of dependencies -- See https://github.com/getsentry/sentry-javascript/discussions/13846 |
| 66 | + * - We customize the transport to avoid blocking the main Hardhat process |
| 67 | + * while reporting errors. |
| 68 | + * |
| 69 | + * Once you initialize the custom global sentry reporter, you can use the usual |
| 70 | + * `captureException` and `captureMessage` functions exposed by @sentry/core. |
| 71 | + * |
| 72 | + * The reason that this uses the global instance of sentry (by calling |
| 73 | + * initAndBind), is that using the client directly doesn't work with the linked |
| 74 | + * errors integration. |
| 75 | + * |
| 76 | + * Calling `init` also has an option to set global unhandled rejection and |
| 77 | + * uncaught exception handlers. |
40 | 78 | */
|
41 |
| -export async function init(options: InitOptions): Promise<void> { |
42 |
| - const stackParser = stackParserFromStackParserOptions( |
43 |
| - createStackParser(nodeStackLineParser(createGetModuleFromFilename())), |
| 79 | +export function init(options: GlobalCustomSentryReporterOptions): void { |
| 80 | + const client = initAndBind<ServerRuntimeClient, ServerRuntimeClientOptions>( |
| 81 | + ServerRuntimeClient, |
| 82 | + { |
| 83 | + dsn: options.dsn, |
| 84 | + environment: options.environment, |
| 85 | + serverName: GENERIC_SERVER_NAME, |
| 86 | + release: options.release, |
| 87 | + initialScope: { |
| 88 | + contexts: { |
| 89 | + os: { |
| 90 | + name: os.type(), |
| 91 | + build: os.release(), |
| 92 | + version: os.version(), |
| 93 | + }, |
| 94 | + device: { |
| 95 | + arch: os.arch(), |
| 96 | + }, |
| 97 | + runtime: { |
| 98 | + name: path.basename(process.title), |
| 99 | + version: process.version, |
| 100 | + }, |
| 101 | + }, |
| 102 | + }, |
| 103 | + transport: () => options.transport, |
| 104 | + integrations: [ |
| 105 | + functionToStringIntegration(), |
| 106 | + contextLinesIntegration(), |
| 107 | + linkedErrorsIntegration(), |
| 108 | + nodeContextIntegration(), |
| 109 | + ], |
| 110 | + platform: process.platform, |
| 111 | + stackParser: stackParserFromStackParserOptions( |
| 112 | + createStackParser(nodeStackLineParser(createGetModuleFromFilename())), |
| 113 | + ), |
| 114 | + }, |
44 | 115 | );
|
45 | 116 |
|
46 |
| - // NOTE: We do not include most of the default integrations @sentry/node does |
47 |
| - // because in the main hardhat process, we don't use the default integrations |
48 |
| - // at all, and they're of limited use in the context of a reporter subprocess. |
49 |
| - const integrationOptions = { |
50 |
| - defaultIntegrations: [ |
51 |
| - // Inbound filters integration filters out events (errors and transactions) mainly based on init inputs we never use |
52 |
| - // Import from @sentry/core if needed |
53 |
| - // inboundFiltersIntegration(), |
54 |
| - |
55 |
| - functionToStringIntegration(), |
56 |
| - linkedErrorsIntegration(), |
57 |
| - requestDataIntegration(), |
58 |
| - |
59 |
| - // Native Wrappers |
60 |
| - // Console integration captures console logs as breadcrumbs |
61 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/console.ts if needed |
62 |
| - // consoleIntegration(), |
63 |
| - |
64 |
| - // HTTP integration instruments the http(s) modules to capture outgoing requests and attach them as breadcrumbs/spans |
65 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/http/index.ts if needed |
66 |
| - // httpIntegration(), |
67 |
| - |
68 |
| - // Native Node Fetch integration instruments the native node fetch module to capture outgoing requests and attach them as breadcrumbs/spans |
69 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/node-fetch/index.ts if needed |
70 |
| - // nativeNodeFetchIntegration(), |
71 |
| - |
72 |
| - // Global Handlers |
73 |
| - onUncaughtExceptionIntegration(), |
74 |
| - onUnhandledRejectionIntegration(), |
75 |
| - |
76 |
| - // Event Info |
77 |
| - contextLinesIntegration(), |
78 |
| - nodeContextIntegration(), |
79 |
| - |
80 |
| - // Local variables integrations adds local variables to exception frames |
81 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/local-variables/local-variables-async.ts if needed |
82 |
| - // localVariablesIntegration(), |
83 |
| - |
84 |
| - // Child process integration captures child process/worker thread events as breadcrumbs |
85 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/childProcess.ts if needed |
86 |
| - // childProcessIntegration(), |
87 |
| - |
88 |
| - // Records a session for the current process to track release health |
89 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/processSession.ts if needed |
90 |
| - // processSessionIntegration(), |
91 |
| - |
92 |
| - // CommonJS Only |
93 |
| - // Vendor https://github.com/getsentry/sentry-javascript/blob/9.4.0/packages/node/src/integrations/modules.ts if needed |
94 |
| - // modulesIntegration(), |
95 |
| - ], |
96 |
| - integrations: options.integrations, |
97 |
| - }; |
98 |
| - |
99 |
| - const clientOptions: ServerRuntimeClientOptions = { |
100 |
| - sendClientReports: true, |
101 |
| - ...options, |
102 |
| - platform: "node", |
103 |
| - runtime: { |
104 |
| - name: "node", |
105 |
| - version: process.version, |
106 |
| - }, |
107 |
| - stackParser, |
108 |
| - integrations: getIntegrationsToSetup(integrationOptions), |
109 |
| - _metadata: { |
110 |
| - sdk: { |
111 |
| - name: "hardhat", |
112 |
| - version: await getHardhatVersion(), |
| 117 | + setupGlobalUnhandledErrorHandlers(client); |
| 118 | +} |
| 119 | + |
| 120 | +function setupGlobalUnhandledErrorHandlers(client: Client) { |
| 121 | + log("Setting up global unhandled error handlers"); |
| 122 | + |
| 123 | + async function listener(error: Error | unknown) { |
| 124 | + log("Uncaught exception", error); |
| 125 | + |
| 126 | + client.captureException(error, { |
| 127 | + captureContext: { |
| 128 | + level: "fatal", |
113 | 129 | },
|
114 |
| - }, |
115 |
| - }; |
| 130 | + mechanism: { |
| 131 | + handled: false, |
| 132 | + type: "onuncaughtexception", |
| 133 | + }, |
| 134 | + }); |
| 135 | + |
| 136 | + await client.flush(100); |
| 137 | + await client.close(100); |
| 138 | + |
| 139 | + console.error("Unexpected error encountered:\n"); |
| 140 | + console.error(error); |
| 141 | + process.exit(1); |
| 142 | + } |
116 | 143 |
|
117 |
| - initAndBind(ServerRuntimeClient, clientOptions); |
| 144 | + process.on("uncaughtException", listener); |
| 145 | + process.on("unhandledRejection", listener); |
118 | 146 | }
|
0 commit comments