UNPKG

6.11 kBJavaScriptView Raw
1import { resolve, posix } from 'path'
2import consola from 'consola'
3import deepMerge from 'deepmerge'
4import * as Sentry from '@sentry/node'
5import * as Integrations from '@sentry/integrations'
6import WebpackPlugin from '@sentry/webpack-plugin'
7
8const logger = consola.withScope('nuxt:sentry')
9
10const filterDisabledIntegration = integrations => Object.keys(integrations)
11 .filter(key => integrations[key])
12
13export default function SentryModule (moduleOptions) {
14 const defaults = {
15 dsn: process.env.SENTRY_DSN || false,
16 disabled: process.env.SENTRY_DISABLED || false,
17 initialize: process.env.SENTRY_INITIALIZE || true,
18 disableClientSide: process.env.SENTRY_DISABLE_CLIENT_SIDE || false,
19 disableServerSide: process.env.SENTRY_DISABLE_SERVER_SIDE || false,
20 publishRelease: process.env.SENTRY_PUBLISH_RELEASE || false,
21 disableServerRelease: process.env.SENTRY_DISABLE_SERVER_RELEASE || false,
22 disableClientRelease: process.env.SENTRY_DISABLE_CLIENT_RELEASE || false,
23 attachCommits: process.env.SENTRY_AUTO_ATTACH_COMMITS || false,
24 sourceMapStyle: 'source-map',
25 repo: process.env.SENTRY_RELEASE_REPO || false,
26 clientIntegrations: {
27 Dedupe: {},
28 ExtraErrorData: {},
29 ReportingObserver: {},
30 RewriteFrames: {},
31 Vue: { attachProps: true }
32 },
33 serverIntegrations: {
34 Dedupe: {},
35 ExtraErrorData: {},
36 RewriteFrames: {},
37 Transaction: {}
38 },
39 config: {
40 environment: this.options.dev ? 'development' : 'production'
41 },
42 serverConfig: {},
43 clientConfig: {},
44 webpackConfig: {
45 include: [],
46 ignore: [
47 'node_modules',
48 '.nuxt/dist/client/img'
49 ],
50 configFile: '.sentryclirc'
51 }
52 }
53
54 const topLevelOptions = this.options.sentry || {}
55 const options = deepMerge.all([defaults, topLevelOptions, moduleOptions])
56
57 options.serverConfig = deepMerge.all([options.config, options.serverConfig])
58 options.clientConfig = deepMerge.all([options.config, options.clientConfig])
59
60 if (options.publishRelease) {
61 if (!options.webpackConfig.urlPrefix) {
62 // Set urlPrefix to match resources on the client. That's not technically correct for the server
63 // source maps, but it is what it is for now.
64 const publicPath = posix.join(this.options.router.base, this.options.build.publicPath)
65 options.webpackConfig.urlPrefix = publicPath.startsWith('/') ? `~${publicPath}` : publicPath
66 }
67
68 if (typeof options.webpackConfig.include === 'string') {
69 options.webpackConfig.include = [options.webpackConfig.include]
70 }
71
72 const { buildDir } = this.options
73
74 if (!options.disableServerRelease) {
75 options.webpackConfig.include.push(`${buildDir}/dist/server`)
76 }
77 if (!options.disableClientRelease) {
78 options.webpackConfig.include.push(`${buildDir}/dist/client`)
79 }
80
81 if (options.config.release && !options.webpackConfig.release) {
82 options.webpackConfig.release = options.config.release
83 }
84
85 if (options.attachCommits) {
86 options.webpackConfig.setCommits = {
87 auto: true
88 }
89
90 if (options.repo) {
91 options.webpackConfig.setCommits.repo = options.repo
92 }
93 }
94 }
95
96 const initializationRequired = options.initialize && options.dsn
97
98 if (!options.dsn) {
99 logger.info('Errors will not be logged because no DSN has been provided')
100 }
101
102 // Register the client plugin
103 if (!options.disabled && !options.disableClientSide) {
104 this.addPlugin({
105 src: resolve(__dirname, 'sentry.client.js'),
106 fileName: 'sentry.client.js',
107 mode: 'client',
108 options: {
109 config: {
110 dsn: options.dsn,
111 ...options.clientConfig
112 },
113 initialize: initializationRequired,
114 integrations: filterDisabledIntegration(options.clientIntegrations)
115 .reduce((res, key) => {
116 res[key] = options.clientIntegrations[key]
117 return res
118 }, {})
119 }
120 })
121 } else {
122 logger.info('Sentry client side errors will not be logged because the disable option has been set')
123 this.addPlugin({
124 src: resolve(__dirname, 'sentry.mocked.js'),
125 fileName: 'sentry.client.js',
126 mode: 'client'
127 })
128 }
129
130 // Register the server plugin
131 if (!options.disabled && !options.disableServerSide) {
132 // Initialize Sentry
133 if (initializationRequired) {
134 Sentry.init({
135 dsn: options.dsn,
136 ...options.serverConfig,
137 integrations: filterDisabledIntegration(options.serverIntegrations)
138 .map(name => new Integrations[name](options.serverIntegrations[name]))
139 })
140 }
141
142 process.sentry = Sentry
143 logger.success('Started logging errors to Sentry')
144
145 this.addPlugin({
146 src: resolve(__dirname, 'sentry.server.js'),
147 fileName: 'sentry.server.js',
148 mode: 'server'
149 })
150 this.nuxt.hook('render:setupMiddleware', app => app.use(Sentry.Handlers.requestHandler()))
151 this.nuxt.hook('render:errorMiddleware', app => app.use(Sentry.Handlers.errorHandler()))
152 this.nuxt.hook('generate:routeFailed', ({ route, errors }) => {
153 errors.forEach(({ error }) => Sentry.withScope((scope) => {
154 scope.setExtra('route', route)
155 Sentry.captureException(error)
156 }))
157 })
158 } else {
159 logger.info('Sentry server side errors will not be logged because the disable option has been set')
160 this.addPlugin({
161 src: resolve(__dirname, 'sentry.mocked.js'),
162 fileName: 'sentry.server.js',
163 mode: 'server'
164 })
165 }
166
167 // Enable publishing of sourcemaps
168 if (options.publishRelease && !options.disabled && !this.options.dev) {
169 this.nuxt.hook('webpack:config', (webpackConfigs) => {
170 for (const config of webpackConfigs) {
171 config.devtool = options.sourceMapStyle
172 }
173
174 // Add WebpackPlugin to last build config
175
176 const config = webpackConfigs[webpackConfigs.length - 1]
177
178 config.plugins = config.plugins || []
179 config.plugins.push(new WebpackPlugin(options.webpackConfig))
180
181 logger.info('Enabling uploading of release sourcemaps to Sentry')
182 })
183 }
184}