From b569b30353ce3ec23bfa767a62d365f907a95c2c Mon Sep 17 00:00:00 2001 From: Lukas Mauser Date: Sun, 12 Mar 2023 23:54:22 +0100 Subject: [PATCH] feat(logger): add logger plugin (#56) * feat(logger): add logger plugin * feat(logging): add basic logs * fix: use log level WARNING as default --------- Co-authored-by: Jonas Scholz --- app.config.ts | 2 +- layouts/default.vue | 17 ++++- middleware/routing.global.ts | 4 ++ pages/[...slug].vue | 31 +++++++-- plugins/4.logger.ts | 122 +++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 plugins/4.logger.ts diff --git a/app.config.ts b/app.config.ts index 25411282..24f0c422 100644 --- a/app.config.ts +++ b/app.config.ts @@ -2,7 +2,7 @@ import { LogLevel } from "fsxa-api"; import { FSXAFileConfig } from "./types"; const fsxaConfig: FSXAFileConfig = { - logLevel: LogLevel.NONE, + logLevel: LogLevel.WARNING, devMode: false, defaultLocale: "de_DE", enableEventStream: false, diff --git a/layouts/default.vue b/layouts/default.vue index 7f470deb..764d32ba 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -15,26 +15,39 @@ const { activeLocale } = useLocale(); const { fetchProjectProperties, setProjectProperties } = useProjectProperties(); const { setNavigationData, fetchNavigationData } = useNavigationData(); +const { $logger } = useNuxtApp(); // This gets called when the layout is loaded or the locale changes const { pending } = useAsyncData( async () => { // fetch project properties const projectProperties = await fetchProjectProperties(activeLocale.value!); - if (!projectProperties) + if (!projectProperties) { + $logger.error( + "Project properties could not be fetched for locale: ", + activeLocale.value + ); throw showError({ message: "Project properties could not be fetched", statusCode: 500, }); + } + setProjectProperties(projectProperties, activeLocale.value!); // fetch navigationData const navigationData = await fetchNavigationData(activeLocale.value!); - if (!navigationData) + if (!navigationData) { + $logger.error( + "Navigation data could not be fetched for locale: ", + activeLocale.value + ); throw showError({ message: "Navigation data could not be fetched", statusCode: 500, }); + } + setNavigationData(navigationData); }, { watch: [activeLocale] } diff --git a/middleware/routing.global.ts b/middleware/routing.global.ts index 2d37f308..06bab954 100644 --- a/middleware/routing.global.ts +++ b/middleware/routing.global.ts @@ -2,6 +2,7 @@ // This can happen on both the client and the server. export default defineNuxtRouteMiddleware(async (to) => { const { activeLocale } = useLocale(); + const { $logger } = useNuxtApp(); const route = decodeURIComponent(to.path); const { @@ -12,6 +13,7 @@ export default defineNuxtRouteMiddleware(async (to) => { // "/" does not exist in the navigation tree, so we first need to figure out the mapped route and then navigate to it. if (route === "/") { + $logger.info("Trying to redirect / to home route..."); return navigateTo({ path: await getIndexRoute(), hash: to.hash, @@ -37,6 +39,8 @@ export default defineNuxtRouteMiddleware(async (to) => { } catch (_error: unknown) { // Theoretically this does not have to mean that the page does not exist. // It could also be a 500 server error or something completely different... + // TODO: Differentiate between 404 and other errors. + $logger.error("Server error or page not found."); throw createError({ statusCode: 404, message: "Page not found", diff --git a/pages/[...slug].vue b/pages/[...slug].vue index a5491f0e..9856f635 100644 --- a/pages/[...slug].vue +++ b/pages/[...slug].vue @@ -20,7 +20,7 @@ const { findCachedPageByRoute, findCachedDatasetByRoute, } = useContent(); -const { $fsxaApi, $setPreviewId } = useNuxtApp(); +const { $fsxaApi, $setPreviewId, $logger } = useNuxtApp(); const { activeLocale } = useLocale(); const { activeNavigationItem } = useNavigationData(); const currentRoute = decodeURIComponent(useRoute().path); @@ -35,8 +35,17 @@ const previewId = computed(() => { const { pending } = useAsyncData(async () => { // This state should not be possible. // The middleware should have figured out both the locale and our current navigation item - if (!activeNavigationItem.value || !activeLocale.value) - throw new Error("No navigation item found"); + if (!activeNavigationItem.value || !activeLocale.value) { + $logger.error( + "The middleware could not determine the navigation state for this route.", + currentRoute, + "Navigation item: ", + activeNavigationItem.value, + "Locale: ", + activeLocale.value + ); + throw new Error("No navigation item or locale found"); + } const { caasDocumentId, seoRouteRegex } = activeNavigationItem.value; const isContentProjection = seoRouteRegex !== null; @@ -44,39 +53,47 @@ const { pending } = useAsyncData(async () => { // for content projections we need to get the dataset first if (isContentProjection) { + $logger.info("On content projection"); // get dataset currentDataset.value = findCachedDatasetByRoute(currentRoute) || null; if (!currentDataset.value) { + $logger.info( + "Dataset not cached yet. Trying to fetch dataset with fsxa api" + ); currentDataset.value = await fetchDatasetByRoute( $fsxaApi, currentRoute, activeLocale.value ); - if (!currentDataset.value) + if (!currentDataset.value) { + $logger.error("Dataset could not be fetched!"); // Although it is recommended to use createError instead, there is a bug that prevents createError from triggering the error page // https://github.com/nuxt/nuxt/issues/15432 throw showError({ statusMessage: "Dataset not found", statusCode: 404, }); - + } addToCachedDatasets(currentRoute, currentDataset.value); } // get pageRefId from dataset const firstRoute = currentDataset.value.routes?.[0]; - if (!firstRoute) + + if (!firstRoute) { + $logger.error("Dataset has no matching route"); throw showError({ statusMessage: "Dataset has no matching route", statusCode: 404, }); - + } pageId = firstRoute.pageRef; } // get page data currentPage.value = findCachedPageByRoute(currentRoute) || null; if (!currentPage.value) { + $logger.info("Page data not cached yet. Trying to fetch with fsxa api..."); currentPage.value = await fetchPageById( $fsxaApi, pageId, diff --git a/plugins/4.logger.ts b/plugins/4.logger.ts new file mode 100644 index 00000000..8ea524e6 --- /dev/null +++ b/plugins/4.logger.ts @@ -0,0 +1,122 @@ +/* eslint no-console: 0 */ +// TODO: the logger should be exported from the fsxa api in a future ticket +import { inspect } from "util"; +import chalk from "chalk"; +import { LogLevel } from "fsxa-api"; + +const getCircularReplacer = () => { + const seen = new WeakMap(); + return (key: any, value: any) => { + if (typeof value === "object" && value !== null) { + if (seen.has(value)) { + const representation = []; + const firstSeen = seen.get(value); + if (firstSeen) representation.push(`first occurence: ${firstSeen}`); + if (value.type) representation.push(`type: ${value.type}`); + if (value.id) representation.push(`id: ${value.id}`); + return `[~circle. ${representation.join(", ")}]`; + } + seen.set(value, key); + } + return value; + }; +}; + +const formatOutput = (...args: any[]) => { + args = args.map((entry) => { + if (typeof entry === "object") { + return JSON.stringify(entry, getCircularReplacer()); + } + return entry; + }); + + return inspect(args.join(" | "), { + showHidden: false, + depth: null, + colors: false, + compact: true, + breakLength: Infinity, + }).replace(/'/g, ""); +}; + +class Logger { + private _logLevel: LogLevel; + private _name: string; + + constructor(logLevel: LogLevel, name: string) { + this._logLevel = logLevel; + this._name = name; + } + + get logLevel() { + return this._logLevel; + } + + debug(...args: any[]) { + if (this._logLevel <= LogLevel.DEBUG) { + console.info( + chalk.gray( + `${chalk.bgWhite.black(" DEBUG ")} ${this._name} | ${formatOutput( + ...args + )}` + ) + ); + } + } + + log(...args: any[]) { + this.info(args); + } + + info(...args: any[]) { + if (this._logLevel <= LogLevel.INFO) { + console.info( + chalk.blue( + `${chalk.bgBlue.white(" INFO ")} ${this._name} | ${formatOutput( + ...args + )}` + ) + ); + } + } + + warn(...args: any[]) { + if (this._logLevel <= LogLevel.WARNING) { + console.warn( + chalk.yellow( + `${chalk.bgYellow.black(" WARN ")} ${this._name} | ${formatOutput( + ...args + )}` + ) + ); + } + } + + error(...args: any[]) { + if (this._logLevel <= LogLevel.ERROR) { + console.error( + chalk.red( + `${chalk.bgRed.black(" ERROR ")} ${this._name} | ${formatOutput( + ...args + )}` + ) + ); + } + } +} + +export default defineNuxtPlugin(() => { + const { logLevel: logLevelFileConfig } = useAppConfig(); + const { + public: { logLevel: logLevelEnv }, + } = useRuntimeConfig(); + + const logLevel = Number(logLevelEnv) || logLevelFileConfig || LogLevel.NONE; + + const logger = new Logger(logLevel, "fsxa-nuxt3-pwa"); + return { + provide: { + logger, + }, + }; +});