Skip to content

Commit 11c9220

Browse files
authored
Merge pull request #58 from nuxt-community/request-tracking
fix: properly track ssr request and errors with nitro
2 parents d65656b + 899d935 commit 11c9220

File tree

5 files changed

+94
-16
lines changed

5 files changed

+94
-16
lines changed

lib/module.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ const { fileURLToPath } = require('url')
33
const AppInsights = require('applicationinsights')
44
const deepMerge = require('deepmerge')
55

6-
const { defineNuxtModule, getNuxtVersion, logger, addTemplate, addPluginTemplate, hasNuxtCompatibility } = require('@nuxt/kit')
6+
const {
7+
defineNuxtModule, logger, addTemplate, addPluginTemplate, hasNuxtCompatibility,
8+
addServerHandler, createResolver
9+
} = require('@nuxt/kit')
710
const { requestHandler, errorHandler } = require('./serverHandlers')
811

912
const DEFAULTS = {
@@ -62,6 +65,8 @@ module.exports = defineNuxtModule({
6265
// Register the server plugin
6366
if (!options.disableServerSide) {
6467
if (await hasNuxtCompatibility({ bridge: true })) {
68+
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
69+
const { resolve } = createResolver(runtimeDir)
6570
addTemplate({
6671
src: path.resolve(__dirname, 'runtime/appinsights.init.js'),
6772
fileName: 'appinsights.init.js'
@@ -72,6 +77,10 @@ module.exports = defineNuxtModule({
7277
mode: 'server',
7378
options
7479
})
80+
addServerHandler({
81+
middleware: true,
82+
handler: resolve('./middleware.js')
83+
})
7584
nuxt.hook('nitro:config', (config) => {
7685
// Add a nitro plugin that will run the validator for us on each request
7786
config.plugins = config.plugins || []

lib/runtime/appinsights.init.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,50 @@
11
import * as AppInsights from 'applicationinsights'
22

33
let initialized = false
4+
const appInsights = AppInsights.default ? AppInsights.default : AppInsights
45

56
const getClient = (options, runtimeConfig) => {
67
if (!initialized) {
78
const runtimeConfigLocal = { ...(runtimeConfig.public.appInsights || {}), ...(runtimeConfig.appInsights || {}) }
89
const mergedConfig = { ...options, ...runtimeConfigLocal }
9-
const appInsightsServer = AppInsights.setup(mergedConfig.serverConnectionString)
10+
const appInsightsServer = appInsights.setup(mergedConfig.serverConnectionString)
1011
for (const [key, value] of Object.entries(mergedConfig.serverConfig)) {
11-
AppInsights.defaultClient.config[key] = value
12+
appInsights.defaultClient.config[key] = value
1213
}
1314
if (mergedConfig.initialize) {
1415
appInsightsServer.start()
1516
}
1617
initialized = true
1718
}
18-
return AppInsights.default ? AppInsights.default.defaultClient : AppInsights.defaultClient
19+
return appInsights.defaultClient
1920
}
21+
22+
const addContextTelemetry = () => {
23+
appInsights.defaultClient.addTelemetryProcessor(
24+
(envelope, context) => {
25+
const setContextKey = (key, value) => {
26+
envelope.tags[key] = value
27+
}
28+
const getContextKey = (key) => {
29+
return appInsights.defaultClient.context.keys[key]
30+
}
31+
32+
// custom context that I set on per-request basis
33+
const requestContext = context.authData || {}
34+
35+
for (const [key, value] of Object.entries(requestContext)) {
36+
if (value) {
37+
setContextKey(
38+
getContextKey(key),
39+
value
40+
)
41+
}
42+
}
43+
44+
return true
45+
}
46+
)
47+
}
48+
2049
export default getClient
50+
export { appInsights, addContextTelemetry }

lib/runtime/errorHandler.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
import getClient from './appinsights.init'
1+
import getClient, { appInsights } from './appinsights.init'
22
import defaultErrorHandler from '#existing-error-handler'
3-
import { useRuntimeConfig } from '#imports'
3+
import { useRuntimeConfig, useCookies } from '#imports'
44
import options from '#appinsights-config'
55

66
export default function (error, event) {
77
const client = getClient(options, useRuntimeConfig())
8-
client.trackException({
9-
exception: error,
10-
properties: {
11-
headers: event.req.headers
12-
}
13-
})
8+
const correlationContext = event.req.context.correlationContext
9+
const cookies = useCookies(event)
10+
appInsights.wrapWithCorrelationContext(() => {
11+
client.trackException({
12+
exception: error,
13+
properties: {
14+
headers: event.req.headers,
15+
url: event.req.headers.host + event.req.url
16+
},
17+
contextObjects: {
18+
authData: {
19+
userId: cookies.ai_user ? cookies.ai_user.split('|')[0] : undefined,
20+
sessionId: cookies.ai_session,
21+
userAuthUserId: event.req.context.userAuthUserId
22+
}
23+
}
24+
})
25+
}, correlationContext)()
1426
defaultErrorHandler(error, event)
1527
}

lib/runtime/middleware.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineEventHandler } from 'h3'
2+
import { appInsights } from './appinsights.init'
3+
4+
export default defineEventHandler((event) => {
5+
const correlationContext = appInsights.startOperation(event.req)
6+
event.req.context.startTime = Date.now()
7+
event.req.context.correlationContext = correlationContext
8+
})

lib/runtime/nitro.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1-
import getClient from './appinsights.init'
2-
import { useRuntimeConfig } from '#imports'
1+
import getClient, { appInsights, addContextTelemetry } from './appinsights.init'
2+
import { useRuntimeConfig, useCookies } from '#imports'
33
import options from '#appinsights-config'
44

55
export default function (nitro) {
66
const client = getClient(options, useRuntimeConfig())
7+
addContextTelemetry()
78
nitro.hooks.hook('render:response', (response, { event }) => {
89
if (typeof response.body === 'string' && (response.headers['Content-Type'] || response.headers['content-type'])?.includes('html')) {
910
// We deliberately do not await so as not to block the response
10-
// appInsightsClient.trackRequest({ name: `${event.req.method} ${event.req.url}`, url: event.req.url, resultCode: response.statusCode, success: true, duration: 0 })
11-
client.trackNodeHttpRequest({ request: event.req, response })
11+
const cookies = useCookies(event)
12+
const correlationContext = event.req.context.correlationContext
13+
appInsights.wrapWithCorrelationContext(() => {
14+
// console.log('ssss', appInsights.getCorrelationContext())
15+
client.trackRequest({
16+
name: `${event.req.method} ${event.req.url}`,
17+
url: event.req.url,
18+
resultCode: response.statusCode,
19+
success: response.statusCode === 200,
20+
duration: Date.now() - event.req.context.startTime,
21+
id: correlationContext.operation.parentId,
22+
contextObjects: {
23+
authData: {
24+
userId: cookies.ai_user ? cookies.ai_user.split('|')[0] : undefined,
25+
userAuthUserId: event.req.context.userAuthUserId,
26+
sessionId: cookies.ai_session
27+
}
28+
}
29+
})
30+
}, correlationContext)()
1231
}
1332
})
1433
}

0 commit comments

Comments
 (0)