Skip to content

Commit

Permalink
fix(tracing): autoSessionTracking not working on the server-side (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl authored Dec 13, 2022
1 parent b81b2cf commit 67851ba
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 36 deletions.
5 changes: 4 additions & 1 deletion lib/core/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,11 @@ export async function webpackConfigHook (moduleContainer, webpackConfigs, option
*
* @param {ThisParameterType<import('@nuxt/types').Module>} moduleContainer
* @param {import('../../types/sentry').ResolvedModuleConfiguration} moduleOptions
* @param {import('../../types/sentry').SentryHandlerProxy} sentryHandlerProxy
* @param {import('consola').Consola} logger
* @return {Promise<void>}
*/
export async function initializeServerSentry (moduleContainer, moduleOptions, logger) {
export async function initializeServerSentry (moduleContainer, moduleOptions, sentryHandlerProxy, logger) {
if (process.sentry) {
return
}
Expand All @@ -154,6 +155,8 @@ export async function initializeServerSentry (moduleContainer, moduleOptions, lo

if (canInitialize(moduleOptions)) {
Sentry.init(config)
sentryHandlerProxy.errorHandler = Sentry.Handlers.errorHandler()
sentryHandlerProxy.requestHandler = Sentry.Handlers.requestHandler(moduleOptions.requestHandlerConfig)
}

process.sentry = Sentry
Expand Down
14 changes: 14 additions & 0 deletions lib/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ export const clientSentryEnabled = options => !options.disabled && !options.disa
* @return {boolean} True if node Sentry is enabled.
*/
export const serverSentryEnabled = options => !options.disabled && !options.disableServerSide

/**
* @param {(...args: any[]) => any} fn
* @return {(...args: any[]) => any}
*/
export function callOnce (fn) {
let called = false
return function callOnceWrapper () {
if (!called) {
called = true
return fn(arguments)
}
}
}
69 changes: 39 additions & 30 deletions lib/module.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import consola from 'consola'
import merge from 'lodash.mergewith'
import { Handlers as SentryHandlers, captureException, withScope } from '@sentry/node'
import { captureException, withScope } from '@sentry/node'
import { buildHook, initializeServerSentry, shutdownServerSentry, webpackConfigHook } from './core/hooks'
import { boolToText, canInitialize, clientSentryEnabled, envToBool, serverSentryEnabled } from './core/utils'
import { boolToText, callOnce, canInitialize, clientSentryEnabled, envToBool, serverSentryEnabled } from './core/utils'

const logger = consola.withScope('nuxt:sentry')

Expand Down Expand Up @@ -73,21 +73,6 @@ export default function SentryModule (moduleOptions) {
options.publishRelease = merged
}

if (serverSentryEnabled(options)) {
// @ts-ignore
this.nuxt.hook('render:setupMiddleware', app => app.use(SentryHandlers.requestHandler(options.requestHandlerConfig)))
// @ts-ignore
this.nuxt.hook('render:errorMiddleware', app => app.use(SentryHandlers.errorHandler()))
// @ts-ignore
this.nuxt.hook('generate:routeFailed', ({ route, errors }) => {
// @ts-ignore
errors.forEach(({ error }) => withScope((scope) => {
scope.setExtra('route', route)
captureException(error)
}))
})
}

if (canInitialize(options) && (clientSentryEnabled(options) || serverSentryEnabled(options))) {
const status = `(client side: ${boolToText(clientSentryEnabled(options))}, server side: ${boolToText(serverSentryEnabled(options))})`
logger.success(`Sentry reporting is enabled ${status}`)
Expand All @@ -105,21 +90,45 @@ export default function SentryModule (moduleOptions) {
logger.info(`Sentry reporting is disabled (${why})`)
}

this.nuxt.hook('build:before', () => buildHook(this, options, logger))
this.nuxt.hook('build:before', callOnce(() => buildHook(this, options, logger)))

// This is messy but Nuxt provides many modes that it can be started with like:
// - nuxt dev
// - nuxt build
// - nuxt start
// - nuxt generate
// but it doesn't really provide great way to differentiate those or enough hooks to
// pick from. This should ensure that server Sentry will only be initialized **after**
// the release version has been determined and the options template created but before
// the build is started (if building).
const initHook = this.options._build ? 'build:compile' : 'ready'
if (serverSentryEnabled(options)) {
this.nuxt.hook(initHook, () => initializeServerSentry(this, options, logger))
this.nuxt.hook('generate:done', () => shutdownServerSentry())
/**
* Proxy that provides a dummy request handler before Sentry is initialized and gets replaced with Sentry's own
* handler after initialization. Otherwise server-side request tracing would not work as it depends on Sentry being
* initialized already during handler creation.
* @type {import('../types/sentry').SentryHandlerProxy}
*/
const sentryHandlerProxy = {
errorHandler: (error, req, res, next) => { next(error) },
requestHandler: (req, res, next) => { next() },
}
// @ts-ignore
this.nuxt.hook('render:setupMiddleware', app => app.use((req, res, next) => { sentryHandlerProxy.requestHandler(req, res, next) }))
// @ts-ignore
this.nuxt.hook('render:errorMiddleware', app => app.use((error, req, res, next) => { sentryHandlerProxy.errorHandler(error, req, res, next) }))
// @ts-ignore
this.nuxt.hook('generate:routeFailed', ({ route, errors }) => {
// @ts-ignore
errors.forEach(({ error }) => withScope((scope) => {
scope.setExtra('route', route)
captureException(error)
}))
})
// This is messy but Nuxt provides many modes that it can be started with like:
// - nuxt dev
// - nuxt build
// - nuxt start
// - nuxt generate
// but it doesn't really provide great way to differentiate those or enough hooks to
// pick from. This should ensure that server Sentry will only be initialized **after**
// the release version has been determined and the options template created but before
// the build is started (if building).
const initHook = this.options._build ? 'build:compile' : 'ready'
this.nuxt.hook(initHook, callOnce(() => initializeServerSentry(this, options, sentryHandlerProxy, logger)))
const shutdownServerSentryOnce = callOnce(() => shutdownServerSentry())
this.nuxt.hook('generate:done', shutdownServerSentryOnce)
this.nuxt.hook('close', shutdownServerSentryOnce)
}

// Enable publishing of sourcemaps
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@
"playwright-chromium": "^1.28.1",
"release-it": "^15.5.1",
"sentry-testkit": "^4.1.0",
"typescript": "^4.9.4"
"typescript": "4.8.4"
}
}
7 changes: 7 additions & 0 deletions types/sentry.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { IncomingMessage, ServerResponse } from 'http'
import { Options as WebpackOptions } from 'webpack'
import { BrowserTracing } from '@sentry/tracing'
import { Options as SentryOptions } from '@sentry/types'
import { BrowserOptions } from '@sentry/browser'
import { SentryCliPluginOptions } from '@sentry/webpack-plugin'
import { Handlers } from '@sentry/node'

export interface SentryHandlerProxy {
errorHandler: (error: any, req: IncomingMessage, res: ServerResponse, next: (error: any) => void) => void
requestHandler: (req: IncomingMessage, res: ServerResponse, next: (error?: any) => void) => void
}

export type IntegrationsConfiguration = Record<string, unknown>

export interface LazyConfiguration {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15075,10 +15075,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=

typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
typescript@4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==

ua-parser-js@^0.7.28:
version "0.7.31"
Expand Down

0 comments on commit 67851ba

Please sign in to comment.