koa.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
  2. import {
  3. Docs,
  4. DocsParams,
  5. OnboardingConfig,
  6. } from 'sentry/components/onboarding/gettingStartedDoc/types';
  7. import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils';
  8. import {getJSServerMetricsOnboarding} from 'sentry/components/onboarding/gettingStartedDoc/utils/metricsOnboarding';
  9. import {ProductSolution} from 'sentry/components/onboarding/productSelection';
  10. import {t, tct} from 'sentry/locale';
  11. import {
  12. getDefaultNodeImports,
  13. getInstallConfig,
  14. ProductSelectionMap,
  15. } from 'sentry/utils/gettingStartedDocs/node';
  16. type Params = DocsParams;
  17. const productSelection = (params: Params): ProductSelectionMap => {
  18. return {
  19. [ProductSolution.ERROR_MONITORING]: true,
  20. [ProductSolution.PROFILING]: params.isProfilingSelected,
  21. [ProductSolution.PERFORMANCE_MONITORING]: params.isPerformanceSelected,
  22. [ProductSolution.SESSION_REPLAY]: params.isReplaySelected,
  23. };
  24. };
  25. const getSdkSetupSnippet = (params: Params) => `
  26. ${getDefaultNodeImports({productSelection: productSelection(params)}).join('\n')}
  27. import { stripUrlQueryAndFragment } from "@sentry/utils";
  28. import Koa from "koa";
  29. const app = new Koa();
  30. Sentry.init({
  31. dsn: "${params.dsn}",
  32. integrations: [${
  33. params.isPerformanceSelected
  34. ? `
  35. // Automatically instrument Node.js libraries and frameworks
  36. ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),`
  37. : ''
  38. }${
  39. params.isProfilingSelected
  40. ? `
  41. new ProfilingIntegration(),`
  42. : ''
  43. }
  44. ],${
  45. params.isPerformanceSelected
  46. ? `
  47. // Performance Monitoring
  48. tracesSampleRate: 1.0, // Capture 100% of the transactions`
  49. : ''
  50. }${
  51. params.isProfilingSelected
  52. ? `
  53. // Set sampling rate for profiling - this is relative to tracesSampleRate
  54. profilesSampleRate: 1.0,`
  55. : ''
  56. }
  57. });${
  58. params.isPerformanceSelected
  59. ? `
  60. const requestHandler = (ctx, next) => {
  61. return new Promise((resolve, reject) => {
  62. Sentry.runWithAsyncContext(async () => {
  63. const hub = Sentry.getCurrentHub();
  64. hub.configureScope((scope) =>
  65. scope.addEventProcessor((event) =>
  66. Sentry.addRequestDataToEvent(event, ctx.request, {
  67. include: {
  68. user: false,
  69. },
  70. })
  71. )
  72. );
  73. try {
  74. await next();
  75. } catch (err) {
  76. reject(err);
  77. }
  78. resolve();
  79. });
  80. });
  81. };
  82. // This tracing middleware creates a transaction per request
  83. const tracingMiddleWare = async (ctx, next) => {
  84. const reqMethod = (ctx.method || "").toUpperCase();
  85. const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);
  86. // Connect to trace of upstream app
  87. let traceparentData;
  88. if (ctx.request.get("sentry-trace")) {
  89. traceparentData = Sentry.extractTraceparentData(
  90. ctx.request.get("sentry-trace")
  91. );
  92. }
  93. const transaction = Sentry.startTransaction({
  94. name: \`\${reqMethod} \${reqUrl}\`,
  95. op: "http.server",
  96. ...traceparentData,
  97. });
  98. ctx.__sentry_transaction = transaction;
  99. // We put the transaction on the scope so users can attach children to it
  100. Sentry.getCurrentHub().configureScope((scope) => {
  101. scope.setSpan(transaction);
  102. });
  103. ctx.res.on("finish", () => {
  104. // Push \`transaction.finish\` to the next event loop so open spans have a chance to finish before the transaction closes
  105. setImmediate(() => {
  106. // If you're using koa router, set the matched route as transaction name
  107. if (ctx._matchedRoute) {
  108. const mountPath = ctx.mountPath || "";
  109. transaction.setName(\`\${reqMethod} \${mountPath}\${ctx._matchedRoute}\`);
  110. }
  111. transaction.setHttpStatus(ctx.status);
  112. transaction.finish();
  113. });
  114. });
  115. await next();
  116. };
  117. app.use(requestHandler);
  118. app.use(tracingMiddleWare);`
  119. : ''
  120. }
  121. // Send errors to Sentry
  122. app.on("error", (err, ctx) => {
  123. Sentry.withScope((scope) => {
  124. scope.addEventProcessor((event) => {
  125. return Sentry.addRequestDataToEvent(event, ctx.request);
  126. });
  127. Sentry.captureException(err);
  128. });
  129. });
  130. app.listen(3000);`;
  131. const getVerifySnippet = () => `
  132. app.use(async function () {
  133. throw new Error("My first Sentry error!");
  134. });
  135. `;
  136. const onboarding: OnboardingConfig = {
  137. install: params => [
  138. {
  139. type: StepType.INSTALL,
  140. description: t('Add the Sentry Node SDK as a dependency:'),
  141. configurations: getInstallConfig(params, {
  142. additionalPackages: params.isPerformanceSelected ? ['@sentry/utils'] : [],
  143. }),
  144. },
  145. ],
  146. configure: params => [
  147. {
  148. type: StepType.CONFIGURE,
  149. description: tct(
  150. "Initialize Sentry as early as possible in your application's lifecycle, for example in your [code:index.ts/js] entry point:",
  151. {code: <code />}
  152. ),
  153. configurations: [
  154. {
  155. language: 'javascript',
  156. code: getSdkSetupSnippet(params),
  157. },
  158. ],
  159. },
  160. getUploadSourceMapsStep({
  161. guideLink: 'https://docs.sentry.io/platforms/node/guides/koa/sourcemaps/',
  162. ...params,
  163. }),
  164. ],
  165. verify: () => [
  166. {
  167. type: StepType.VERIFY,
  168. description: t(
  169. "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected."
  170. ),
  171. configurations: [
  172. {
  173. language: 'javascript',
  174. code: getVerifySnippet(),
  175. },
  176. ],
  177. },
  178. ],
  179. };
  180. const docs: Docs = {
  181. onboarding,
  182. customMetricsOnboarding: getJSServerMetricsOnboarding(),
  183. };
  184. export default docs;