fix(fe-2): structured logging fixes

This commit is contained in:
Kristaps Fabians Geikins
2023-07-19 16:32:51 +03:00
parent a7ba118f3a
commit e96241d911
7 changed files with 106 additions and 25 deletions
+32 -4
View File
@@ -3,13 +3,41 @@ import { Optional } from '@speckle/shared'
import { buildFakePinoLogger } from '~~/lib/core/helpers/observability'
export const useLogger = () => {
let nuxtApp: Optional<NuxtApp>
return useNuxtApp().$logger
}
/**
* Use when you need to be sure that the real structured pino logger is available
* (it isn't in some early startup contexts like apollo link setup)
*/
export const useStrictLogger = async (
options?: Partial<{ dontNotifyFallback: boolean }>
) => {
const { dontNotifyFallback } = options || {}
let nuxtApp: Optional<NuxtApp> = undefined
try {
nuxtApp = useNuxtApp()
} catch (e) {
console.error('Nuxt app for logger not found! Falling back to fake logger...')
return buildFakePinoLogger()
// suppress 'nuxt is not available'
}
return nuxtApp?.$logger
if (nuxtApp?.$logger) return nuxtApp?.$logger
// Nuxt app not found in this scope
const err = new Error(
'Nuxt app for logger not found! Initializing fallback structured logger...'
)
let logger: ReturnType<typeof buildFakePinoLogger>
if (process.server) {
const { buildLogger } = await import('~/server/lib/core/helpers/observability')
logger = buildLogger('info', process.dev ? true : false) // no runtime config, so falling back to default settings
} else {
logger = buildFakePinoLogger()
}
if (!dontNotifyFallback) logger.error(err)
return logger
}
@@ -369,6 +369,7 @@ export type Commit = {
authorAvatar?: Maybe<Scalars['String']>;
authorId?: Maybe<Scalars['String']>;
authorName?: Maybe<Scalars['String']>;
branch?: Maybe<Branch>;
branchName?: Maybe<Scalars['String']>;
/**
* The total number of comments for this commit. To actually get the comments, use the comments query and pass in a resource array consisting of of this commit's id.
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { isObjectLike as lodashIsObjectLike } from 'lodash-es'
import { SetNonNullable, SetRequired } from 'type-fest'
export type NonUndefined<T> = T extends undefined ? never : T
@@ -15,3 +16,6 @@ export type AddParameters<
TFunction extends (...args: any) => any,
TParameters extends [...args: any]
> = (...args: [...Parameters<TFunction>, ...TParameters]) => ReturnType<TFunction>
export const isObjectLike = (value: unknown): value is Record<string, unknown> =>
lodashIsObjectLike(value)
+17 -6
View File
@@ -8,7 +8,7 @@ import { createUploadLink } from 'apollo-upload-client'
import { WebSocketLink } from '@apollo/client/link/ws'
import { getMainDefinition } from '@apollo/client/utilities'
import { OperationDefinitionNode, Kind } from 'graphql'
import { CookieRef } from '#app'
import { CookieRef, NuxtApp } from '#app'
import { Optional } from '@speckle/shared'
import { useAuthCookie } from '~~/lib/auth/composables/auth'
import {
@@ -20,6 +20,7 @@ import { onError } from '@apollo/client/link/error'
import { useNavigateToLogin, loginRoute } from '~~/lib/common/helpers/route'
import { useAppErrorState } from '~~/lib/core/composables/appErrorState'
import { isInvalidAuth } from '~~/lib/common/helpers/graphql'
import { omit } from 'lodash-es'
const appName = 'frontend-2'
@@ -263,19 +264,28 @@ function createLink(params: {
httpEndpoint: string
wsClient?: SubscriptionClient
authToken: CookieRef<Optional<string>>
nuxtApp: NuxtApp
}): ApolloLink {
const { httpEndpoint, wsClient, authToken } = params
const { httpEndpoint, wsClient, authToken, nuxtApp } = params
const goToLogin = useNavigateToLogin()
const { registerError, isErrorState } = useAppErrorState()
const errorLink = onError((res) => {
const logger = useLogger()
const logger = nuxtApp.$logger
const isSubTokenMissingError = (res.networkError?.message || '').includes(
'need a token to subscribe'
)
if (!isSubTokenMissingError) logger.error('Apollo Client error', res)
if (!isSubTokenMissingError) {
logger.error(
{
...omit(res, ['forward']),
networkErrorMessage: res.networkError?.message,
gqlErrorMessages: res.graphQLErrors?.map((e) => e.message)
},
'Apollo Client error'
)
}
const { networkError } = res
if (networkError && isInvalidAuth(networkError)) {
@@ -348,6 +358,7 @@ const defaultConfigResolver: ApolloConfigResolver = async () => {
const {
public: { apiOrigin, speckleServerVersion = 'unknown' }
} = useRuntimeConfig()
const nuxtApp = useNuxtApp()
const httpEndpoint = `${apiOrigin}/graphql`
const wsEndpoint = httpEndpoint.replace('http', 'ws')
@@ -356,7 +367,7 @@ const defaultConfigResolver: ApolloConfigResolver = async () => {
const wsClient = process.client
? await createWsClient({ wsEndpoint, authToken })
: undefined
const link = createLink({ httpEndpoint, wsClient, authToken })
const link = createLink({ httpEndpoint, wsClient, authToken, nuxtApp })
return {
// If we don't markRaw the cache, sometimes we get cryptic internal Apollo Client errors that essentially
@@ -5,13 +5,21 @@
import { Observability } from '@speckle/shared'
import { noop } from 'lodash-es'
export function buildFakePinoLogger() {
export function buildFakePinoLogger(
options?: Partial<{ onError: (...args: any[]) => void }>
) {
const errLogger = (...args: unknown[]) => {
const { onError } = options || {}
if (onError) onError(...args)
console.error(...args)
}
const logger = {
debug: console.debug,
info: console.info,
warn: console.warn,
error: console.error,
fatal: console.error,
error: errLogger,
fatal: errLogger,
trace: console.debug,
silent: noop
} as unknown as ReturnType<typeof Observability.getLogger>
+36 -12
View File
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { isObjectLike } from '~~/lib/common/helpers/type'
import { buildFakePinoLogger } from '~~/lib/core/helpers/observability'
/**
@@ -18,38 +20,60 @@ export default defineNuxtPlugin(async () => {
let logger: ReturnType<typeof import('@speckle/shared').Observability.getLogger>
if (process.server) {
const { Observability } = await import('@speckle/shared')
logger = Observability.getLogger(logLevel, logPretty)
const { buildLogger } = await import('~/server/lib/core/helpers/observability')
logger = buildLogger(logLevel, logPretty)
} else {
logger = buildFakePinoLogger()
// set up seq ingestion
if (!process.dev && logClientApiToken?.length && logClientApiEndpoint?.length) {
if (logClientApiToken?.length && logClientApiEndpoint?.length) {
const seq = await import('seq-logging/browser')
const logger = new seq.Logger({
const seqLogger = new seq.Logger({
serverUrl: logClientApiEndpoint,
apiKey: logClientApiToken,
onError: console.error
})
const errorListener = (event: ErrorEvent) => {
logger.emit({
const errorListener = (
event: ErrorEvent | PromiseRejectionEvent | string | Error | unknown
) => {
const isUnhandledRejection = isObjectLike(event) && 'reason' in event
let err: Error
if (event instanceof Error) {
err = event
} else if (isObjectLike(event)) {
if ('reason' in event && event.reason instanceof Error) {
err = event.reason
} else if ('error' in event && event.error instanceof Error) {
err = event.error
} else {
err = new Error(`${JSON.stringify(event)}`)
}
} else {
err = new Error(`${event}`)
}
seqLogger.emit({
timestamp: new Date(),
level: 'error',
messageTemplate: 'Client-side error: {errorMessage}',
properties: {
errorMessage: event.message,
errorMessage: err.message,
browser: true,
frontendType: 'frontend-2',
speckleServerVersion,
serverName
serverName,
isUnhandledRejection
},
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
exception: event.error instanceof Error ? event.error.stack : `${event.error}`
exception: err.stack
})
}
window.addEventListener('error', errorListener)
window.addEventListener('unhandledrejection', errorListener)
logger = buildFakePinoLogger({ onError: errorListener })
logger.debug('Set up seq ingestion...')
} else {
logger = buildFakePinoLogger()
}
}
@@ -0,0 +1,5 @@
import { Observability } from '@speckle/shared'
export function buildLogger(logLevel: string, logPretty: boolean) {
return Observability.getLogger(logLevel, logPretty)
}