fix(fe2): low level fatal server errors not being logged to RUM (#2197)
* fix(fe2): low level fatal server errors not being logged to RUM * undo nuxt config changes
This commit is contained in:
committed by
GitHub
parent
57a7d2206b
commit
e384f2d299
@@ -2,6 +2,7 @@ import { useScopedState } from '~~/lib/common/composables/scopedState'
|
||||
import { Observability } from '@speckle/shared'
|
||||
import type {
|
||||
AbstractErrorHandler,
|
||||
AbstractErrorHandlerParams,
|
||||
AbstractUnhandledErrorHandler
|
||||
} from '~/lib/core/helpers/observability'
|
||||
|
||||
@@ -57,5 +58,18 @@ export const useCreateErrorLoggingTransport = () => {
|
||||
|
||||
export const useGetErrorLoggingTransports = () => {
|
||||
const { transports } = useErrorLoggingTransportState()
|
||||
|
||||
return transports
|
||||
}
|
||||
|
||||
export const useLogToErrorLoggingTransports = () => {
|
||||
const transports = useGetErrorLoggingTransports()
|
||||
const invokeTransportsWithPayload = (payload: AbstractErrorHandlerParams) => {
|
||||
transports.forEach((handler) => handler.onError(payload))
|
||||
}
|
||||
|
||||
return {
|
||||
transports,
|
||||
invokeTransportsWithPayload
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ export type AbstractUnhandledErrorHandler = (params: {
|
||||
message: string
|
||||
}) => void
|
||||
|
||||
export type AbstractErrorHandlerParams = Parameters<AbstractErrorHandler>[0]
|
||||
|
||||
/**
|
||||
* Adds proxy that intercepts error log calls so that they can be sent to any transport
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { omit } from 'lodash-es'
|
||||
import type { Optional } from '@speckle/shared'
|
||||
import { get, omit } from 'lodash-es'
|
||||
import type { SetRequired } from 'type-fest'
|
||||
import { useReadUserId } from '~/lib/auth/composables/activeUser'
|
||||
import {
|
||||
useCreateErrorLoggingTransport,
|
||||
useGetErrorLoggingTransports
|
||||
useGetErrorLoggingTransports,
|
||||
useLogToErrorLoggingTransports
|
||||
} from '~/lib/core/composables/error'
|
||||
import {
|
||||
useRequestId,
|
||||
@@ -16,9 +18,20 @@ import {
|
||||
buildFakePinoLogger,
|
||||
enableCustomErrorHandling,
|
||||
type AbstractErrorHandler,
|
||||
type AbstractErrorHandlerParams,
|
||||
type AbstractUnhandledErrorHandler
|
||||
} from '~~/lib/core/helpers/observability'
|
||||
|
||||
class CustomDeserializedError extends Error {
|
||||
code: string
|
||||
|
||||
constructor(params: { code: string; message: string; stack: string }) {
|
||||
super(params.message)
|
||||
this.code = params.code
|
||||
this.stack = params.stack
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - Setting up Pino logger in SSR, basic console.log fallback in CSR
|
||||
* - Also sets up ability to add extra transport for other observability tools
|
||||
@@ -43,6 +56,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
const getUserId = useReadUserId()
|
||||
const country = useUserCountry()
|
||||
const registerErrorTransport = useCreateErrorLoggingTransport()
|
||||
const { invokeTransportsWithPayload } = useLogToErrorLoggingTransports()
|
||||
|
||||
const collectMainInfo = (params: { isBrowser: boolean }) => {
|
||||
const info = {
|
||||
@@ -207,9 +221,16 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
|
||||
// Global error handler - handle all transports
|
||||
const transports = useGetErrorLoggingTransports()
|
||||
let serverFatalError: Optional<AbstractErrorHandlerParams> = undefined
|
||||
logger = enableCustomErrorHandling({
|
||||
logger,
|
||||
onError: (params) => {
|
||||
const { otherData } = params
|
||||
|
||||
if (process.server && otherData?.isAppError) {
|
||||
serverFatalError = params
|
||||
}
|
||||
|
||||
transports.forEach((handler) => handler.onError(params))
|
||||
}
|
||||
})
|
||||
@@ -243,7 +264,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
|
||||
logger.error(err, 'Unhandled error in routing', {
|
||||
to: to.path,
|
||||
from: from?.path
|
||||
from: from?.path,
|
||||
isAppError: !!process.server
|
||||
})
|
||||
})
|
||||
|
||||
@@ -257,6 +279,56 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
})
|
||||
})
|
||||
|
||||
// Hydrate server fatal error to CSR
|
||||
if (process.server) {
|
||||
nuxtApp.hook('app:rendered', () => {
|
||||
let serializableError: Optional<AbstractErrorHandlerParams> = undefined
|
||||
try {
|
||||
serializableError = serverFatalError
|
||||
? (JSON.parse(JSON.stringify(serverFatalError)) as AbstractErrorHandlerParams)
|
||||
: undefined
|
||||
if (serializableError && serverFatalError?.firstError) {
|
||||
serializableError.firstError = {
|
||||
code: get(serverFatalError.firstError, 'code', 'unknown') as string,
|
||||
message: get(serverFatalError.firstError, 'message', 'unknown') as string,
|
||||
stack: get(serverFatalError.firstError, 'stack', 'unknown') as string
|
||||
} as unknown as Error // fakin it
|
||||
}
|
||||
} catch (e) {
|
||||
serializableError = {
|
||||
args: [],
|
||||
firstString: 'Failed to serialize serverFatalError',
|
||||
firstError: e as Error,
|
||||
otherData: {},
|
||||
nonObjectOtherData: []
|
||||
}
|
||||
}
|
||||
|
||||
nuxtApp.ssrContext!.payload.serverFatalError = serializableError
|
||||
})
|
||||
} else {
|
||||
nuxtApp.hook('app:mounted', () => {
|
||||
const serverFatalError = nuxtApp.payload.serverFatalError
|
||||
if (serverFatalError) {
|
||||
if (serverFatalError.firstError) {
|
||||
serverFatalError.firstError = new CustomDeserializedError({
|
||||
message: get(serverFatalError.firstError, 'message', 'unknown') as string,
|
||||
code: get(serverFatalError.firstError, 'code', 'unknown') as string,
|
||||
stack: get(serverFatalError.firstError, 'stack', 'unknown') as string
|
||||
})
|
||||
}
|
||||
|
||||
invokeTransportsWithPayload(serverFatalError)
|
||||
|
||||
if (process.dev) {
|
||||
// intentionally skipping error pipeline:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Fatal error occurred on server:', serverFatalError)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
logger
|
||||
|
||||
@@ -105,7 +105,7 @@ async function initRumClient(app: PluginNuxtApp) {
|
||||
|
||||
router.beforeEach((to) => {
|
||||
const pathDefinition = getRouteDefinition(to)
|
||||
const routeName = to.meta.datadogName || pathDefinition
|
||||
const routeName = (to.meta.datadogName || pathDefinition) as string
|
||||
const realPath = to.path
|
||||
|
||||
window.DD_RUM_START_VIEW?.(realPath, routeName)
|
||||
|
||||
@@ -13,6 +13,10 @@ declare module '#app' {
|
||||
*/
|
||||
__scopedStates?: Record<string | symbol, any>
|
||||
}
|
||||
|
||||
interface NuxtPayload {
|
||||
serverFatalError?: import('~~/lib/core/helpers/observability').AbstractErrorHandlerParams
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
|
||||
Reference in New Issue
Block a user