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:
Kristaps Fabians Geikins
2024-04-08 13:12:35 +02:00
committed by GitHub
parent 57a7d2206b
commit e384f2d299
5 changed files with 96 additions and 4 deletions
@@ -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
*/
+75 -3
View File
@@ -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
+1 -1
View File
@@ -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)
+4
View File
@@ -13,6 +13,10 @@ declare module '#app' {
*/
__scopedStates?: Record<string | symbol, any>
}
interface NuxtPayload {
serverFatalError?: import('~~/lib/core/helpers/observability').AbstractErrorHandlerParams
}
}
declare module 'vue' {