@witchcraft/nuxt-logger 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/module.d.mts +75 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +88 -0
  5. package/dist/runtime/composables/setupElectronMainToRendererLogging.d.ts +1 -0
  6. package/dist/runtime/composables/setupElectronMainToRendererLogging.js +9 -0
  7. package/dist/runtime/composables/useLogger.d.ts +4 -0
  8. package/dist/runtime/composables/useLogger.js +3 -0
  9. package/dist/runtime/electron/createElectronLoggerApi.d.ts +9 -0
  10. package/dist/runtime/electron/createElectronLoggerApi.js +13 -0
  11. package/dist/runtime/electron/index.d.ts +4 -0
  12. package/dist/runtime/electron/index.js +4 -0
  13. package/dist/runtime/electron/receiveFromRendererOnMain.d.ts +2 -0
  14. package/dist/runtime/electron/receiveFromRendererOnMain.js +9 -0
  15. package/dist/runtime/electron/types.d.ts +6 -0
  16. package/dist/runtime/electron/types.js +5 -0
  17. package/dist/runtime/electron/useElectronLogger.d.ts +18 -0
  18. package/dist/runtime/electron/useElectronLogger.js +69 -0
  19. package/dist/runtime/electron/wrapLoggerForElectron.d.ts +5 -0
  20. package/dist/runtime/electron/wrapLoggerForElectron.js +9 -0
  21. package/dist/runtime/helpers/getBaseOptions.d.ts +13 -0
  22. package/dist/runtime/helpers/getBaseOptions.js +121 -0
  23. package/dist/runtime/plugins/init.d.ts +2 -0
  24. package/dist/runtime/plugins/init.js +23 -0
  25. package/dist/runtime/server/middleware/log.d.ts +2 -0
  26. package/dist/runtime/server/middleware/log.js +12 -0
  27. package/dist/runtime/server/plugins/init.d.ts +2 -0
  28. package/dist/runtime/server/plugins/init.js +21 -0
  29. package/dist/runtime/server/tsconfig.json +3 -0
  30. package/dist/runtime/server/utils/useServerLogger.d.ts +4 -0
  31. package/dist/runtime/server/utils/useServerLogger.js +3 -0
  32. package/dist/runtime/shared/createUseLogger.d.ts +20 -0
  33. package/dist/runtime/shared/createUseLogger.js +46 -0
  34. package/dist/runtime/types.d.ts +1 -0
  35. package/dist/runtime/types.js +0 -0
  36. package/dist/types.d.mts +3 -0
  37. package/package.json +2 -2
  38. package/src/module.ts +4 -3
  39. package/src/runtime/helpers/getBaseOptions.ts +5 -1
package/README.md CHANGED
@@ -46,7 +46,7 @@ Also redacts the `redact` key, but only in production, so you can put anything s
46
46
  The electron logger requires a bit of setup since electron's main cannot be passed the runtime config normally.
47
47
 
48
48
  This assumes the usage of
49
- [@witchcraft/nuxt-electron](todo) since it can handle this among other issues. Note this plugin needs to be before it in the plugins list.
49
+ [@witchcraft/nuxt-electron](https://github.com/witchcraftjs/nuxt-electron) since it can handle this among other issues.
50
50
 
51
51
  There is a full example in the [@witchcraft/nuxt-electron playground](TODO).
52
52
 
@@ -0,0 +1,75 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { TransportMultiOptions } from 'pino';
3
+
4
+ declare module "@nuxt/schema" {
5
+ interface PublicRuntimeConfig {
6
+ logger: Pick<ModuleOptions, "redact" | "additionalServerTransportTargets" | "disabledServerTransportTargets"> & {
7
+ logPath: string;
8
+ };
9
+ }
10
+ }
11
+ interface ModuleOptions {
12
+ /**
13
+ * See {@link https://getpino.io/#/docs/redaction?id=redaction Pino redaction options}.
14
+ */
15
+ redact: string[];
16
+ /** This is "~~/logs/${appName}.log" by default. It's recommended you change it to something like "/var/log/${appName}.log" in production. */
17
+ serverLogPath?: string;
18
+ /** This is "~~/logs/server.log" by default. */
19
+ devServerLogPath?: string;
20
+ /** The module tries to get the app name from `runtimeConfig.appInfo.name`, otherwise it uses the name of the `~~` directory by default. */
21
+ appName?: string;
22
+ /** @default false */
23
+ enableServerRequestLogging?: boolean;
24
+ /**
25
+ * Additional transport targets to add to the server transport.
26
+ * File and console + pino-pretty are added by default. See disabledServerTransportTargets for disabling a transport.
27
+ *
28
+ * Since some transports will require options, but passing them via the nuxt config will hard code them, there is an additional `_loadFromEnv` property that can list a list of dot seperated properties to load from `process.env` and `_contexts` to set what context it will run in.
29
+ *
30
+ * For example, to add `pino-loki`:
31
+ * ```ts
32
+ * additionalServerTransportTargets: [
33
+ * {
34
+ * target: "pino-loki",
35
+ * _contexts: ["server"], // can be any of ["server", "client", "electron-client", "electron-main"] (all by default)
36
+ * _loadFromEnv: ["options.host", "options.basicAuth.username", "options.basicAuth.password"],
37
+ * options: {
38
+ * //...
39
+ * labels: {
40
+ * // set additional labels
41
+ * application: "...",
42
+ * },
43
+ * propsToLabels: ["level", ...don't forget to set your labels ],
44
+ * replaceTimestamp: true, // loki requires timestamps in a specific format
45
+ * host: "PINO_LOKI_HOST",
46
+ * basicAuth: {
47
+ * // don't forget the quotes!
48
+ * username: "PINO_LOKI_BASIC_AUTH_USERNAME",
49
+ * password: "PINO_LOKI_BASIC_AUTH_PASSWORD",
50
+ * },
51
+ * },
52
+ * },
53
+ * ]
54
+ * ```
55
+ * The location for the auth part on grafana cloud is confusing as fuck, leaving this here for posterity.
56
+ *
57
+ * Go to https://grafana.com/orgs/YOURORGHERE/stacks => Details => Loki => Send Logs
58
+ *
59
+ * There you can get the user and create a token with write access and it sets the right stuff. It will then appear in https://YOURORGHERE.grafana.net/a/grafana-auth-app
60
+ *
61
+ * If nothing is erroring, you should be able to see grafana receiving logs in Drilldown => Logs => Logs (https://YOURORGHERE.grafana.net/drilldown).
62
+ *
63
+ * You can then create a dashboard based on your labels.
64
+ */
65
+ additionalServerTransportTargets?: (TransportMultiOptions["targets"][number] & {
66
+ _contexts?: ("server" | "client" | "electron-client" | "electron-main")[];
67
+ _loadFromEnv: string[];
68
+ })[];
69
+ /** Disable a transport target by it's target name. */
70
+ disabledServerTransportTargets?: string[];
71
+ }
72
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
73
+
74
+ export { _default as default };
75
+ export type { ModuleOptions };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "logger",
3
+ "configKey": "logger",
4
+ "version": "0.0.2",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "3.6.1"
8
+ }
9
+ }
@@ -0,0 +1,88 @@
1
+ import { defineNuxtModule, createResolver, resolveAlias, resolvePath, addImportsDir, addServerImportsDir, addServerHandler, addPlugin } from '@nuxt/kit';
2
+ import { defu } from 'defu';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ const module = defineNuxtModule({
7
+ meta: {
8
+ name: "logger",
9
+ configKey: "logger"
10
+ },
11
+ defaults: {
12
+ redact: [
13
+ ...process.env.NODE_ENV === "development" ? [] : ["redact"],
14
+ `req.headers["sec-ch-ua"]`,
15
+ `req.headers.cookie`,
16
+ `req.headers["user-agent"]`,
17
+ `req.headers.authorization`,
18
+ `req.headers.referer`,
19
+ `req.headers["x-forwarded-for"]`,
20
+ `req.headers["set-cookie"]`,
21
+ // i don't think this is actually sensitive, but just in case
22
+ `req["sec-websocket-key"]`
23
+ ],
24
+ enableServerRequestLogging: false,
25
+ additionalServerTransportTargets: [],
26
+ disabledServerTransportTargets: []
27
+ },
28
+ async setup(options, nuxt) {
29
+ const { resolve } = createResolver(import.meta.url);
30
+ options.devServerLogPath ??= path.relative(nuxt.options.rootDir, resolveAlias("~~/logs/server.log", nuxt.options.alias));
31
+ const maybeAppName = nuxt.options.runtimeConfig.public?.appInfo?.name;
32
+ const appName = options.appName ?? maybeAppName ?? path.basename(await resolvePath("~~", { alias: nuxt.options.alias }));
33
+ options.serverLogPath ??= path.relative(nuxt.options.rootDir, resolveAlias(`~~/logs/${appName}.log`, nuxt.options.alias));
34
+ if (process.env.NODE_ENV === "development") {
35
+ if (!await fs.stat(options.devServerLogPath).then(() => true).catch(() => false)) {
36
+ await fs.mkdir(path.dirname(options.devServerLogPath), {
37
+ recursive: true
38
+ });
39
+ await fs.writeFile(options.devServerLogPath, "");
40
+ }
41
+ } else {
42
+ if (!await fs.stat(options.serverLogPath).then(() => true).catch(() => false)) {
43
+ await fs.mkdir(path.dirname(options.serverLogPath), { recursive: true });
44
+ await fs.writeFile(options.serverLogPath, "");
45
+ }
46
+ }
47
+ nuxt.options.runtimeConfig.public.logger = defu(
48
+ nuxt.options.runtimeConfig.public.logger,
49
+ {
50
+ redact: options.redact,
51
+ logPath: nuxt.options.dev ? options.devServerLogPath : options.serverLogPath,
52
+ additionalServerTransportTargets: options.additionalServerTransportTargets,
53
+ disabledServerTransportTargets: options.disabledServerTransportTargets
54
+ }
55
+ );
56
+ if (nuxt.options.modules.includes("@witchcraft/nuxt-electron")) {
57
+ const opts = nuxt.options;
58
+ opts.electron ??= {};
59
+ opts.electron.electronOnlyRuntimeConfig ??= {};
60
+ opts.electron.electronOnlyRuntimeConfig.logger ??= {};
61
+ opts.electron.electronOnlyRuntimeConfig.logger.logPath = void 0;
62
+ }
63
+ for (const file of [
64
+ "./runtime",
65
+ "./runtime/server/middleware/log",
66
+ "./runtime/server/utils/useServerLogger"
67
+ ]) {
68
+ nuxt.options.build.transpile.push(resolve(file));
69
+ }
70
+ addImportsDir(resolve("./runtime/composables"));
71
+ addServerImportsDir(resolve("./runtime/server/utils"));
72
+ if (options.enableServerRequestLogging) {
73
+ addServerHandler({
74
+ middleware: true,
75
+ handler: resolve("./runtime/server/middleware/log")
76
+ });
77
+ }
78
+ addPlugin(resolve("./runtime/plugins/init"));
79
+ nuxt.options.alias["#logger"] = resolve("./runtime");
80
+ nuxt.hook("vite:extendConfig", (config) => {
81
+ config.optimizeDeps ??= {};
82
+ config.optimizeDeps.include ??= [];
83
+ config.optimizeDeps.include.push("pino");
84
+ });
85
+ }
86
+ });
87
+
88
+ export { module as default };
@@ -0,0 +1 @@
1
+ export declare function setupElectronMainToRendererLogging(): void;
@@ -0,0 +1,9 @@
1
+ import { useLogger } from "./useLogger.js";
2
+ const isElectronClient = typeof window !== "undefined" && "electron" in window && window.electron;
3
+ export function setupElectronMainToRendererLogging() {
4
+ if (!isElectronClient) return;
5
+ const logger = useLogger();
6
+ window.electron?.api._logs.on((level, data) => {
7
+ logger[level](data);
8
+ });
9
+ }
@@ -0,0 +1,4 @@
1
+ export declare const useLogger: ({ redirectTo, redirectAll }?: {
2
+ redirectTo?: "electron";
3
+ redirectAll?: boolean | ((level: keyof import("pino").Logger, data: any) => boolean);
4
+ }) => import("../shared/createUseLogger.js.js").BaseLogger;
@@ -0,0 +1,3 @@
1
+ import { useRuntimeConfig } from "#app";
2
+ import { createUseLogger } from "../shared/createUseLogger.js";
3
+ export const useLogger = createUseLogger(useRuntimeConfig);
@@ -0,0 +1,9 @@
1
+ import type { ipcRenderer as IpcRenderer } from "electron";
2
+ import type { Levels } from "../types.js.js";
3
+ export type ElectronLoggerApi = {
4
+ log: (level: Levels, data: any) => void;
5
+ _logs: {
6
+ on(cb: (level: Levels, data: any) => void): void;
7
+ };
8
+ };
9
+ export declare function createElectronLoggerApi(ipcRenderer: typeof IpcRenderer): ElectronLoggerApi;
@@ -0,0 +1,13 @@
1
+ import { ELECTRON_LOG_TYPE } from "./types.js";
2
+ export function createElectronLoggerApi(ipcRenderer) {
3
+ return {
4
+ log: (level, data) => ipcRenderer.send(ELECTRON_LOG_TYPE.TO_MAIN, { level, data }),
5
+ _logs: {
6
+ on(cb) {
7
+ ipcRenderer.on(ELECTRON_LOG_TYPE.FROM_MAIN_ON_RENDERER, (_event, level, data) => {
8
+ cb(level, data);
9
+ });
10
+ }
11
+ }
12
+ };
13
+ }
@@ -0,0 +1,4 @@
1
+ export { createElectronLoggerApi, type ElectronLoggerApi } from "./createElectronLoggerApi.js";
2
+ export { useElectronLogger } from "./useElectronLogger.js";
3
+ export { wrapLoggerForElectron } from "./wrapLoggerForElectron.js";
4
+ export * from "./types.js";
@@ -0,0 +1,4 @@
1
+ export { createElectronLoggerApi } from "./createElectronLoggerApi.js";
2
+ export { useElectronLogger } from "./useElectronLogger.js";
3
+ export { wrapLoggerForElectron } from "./wrapLoggerForElectron.js";
4
+ export * from "./types.js";
@@ -0,0 +1,2 @@
1
+ import type { useElectronLogger } from "./useElectronLogger.js.js";
2
+ export declare function receiveFromRendererOnMain(logger: ReturnType<typeof useElectronLogger>): void;
@@ -0,0 +1,9 @@
1
+ import { ipcMain } from "electron";
2
+ import { ELECTRON_LOG_TYPE } from "./types.js";
3
+ export function receiveFromRendererOnMain(logger) {
4
+ ipcMain.on(ELECTRON_LOG_TYPE.TO_MAIN, (_event, message) => {
5
+ if (typeof message.data === "object" && typeof message.level === "string") {
6
+ logger[message.level](message.data);
7
+ }
8
+ });
9
+ }
@@ -0,0 +1,6 @@
1
+ import type { EnumLike } from "@alanscodelog/utils";
2
+ export declare const ELECTRON_LOG_TYPE: {
3
+ FROM_MAIN_ON_RENDERER: "ELECTRON_LOG_TYPE.FROM_MAIN_ON_RENDERER";
4
+ TO_MAIN: "ELECTRON_LOG_TYPE.TO_MAIN";
5
+ };
6
+ export type ElectronLogType = EnumLike<typeof ELECTRON_LOG_TYPE>;
@@ -0,0 +1,5 @@
1
+ import { enumFromArray } from "@alanscodelog/utils/enumFromArray";
2
+ export const ELECTRON_LOG_TYPE = enumFromArray([
3
+ "FROM_MAIN_ON_RENDERER",
4
+ "TO_MAIN"
5
+ ], "ELECTRON_LOG_TYPE.");
@@ -0,0 +1,18 @@
1
+ import type { PublicRuntimeConfig } from "@nuxt/schema";
2
+ import { type BrowserWindow } from "electron";
3
+ /**
4
+ * A simplified wrapper around pino for use in electron main. Note that it does not support child logger and is actually a simple wrapper around just the basic levels.
5
+ *
6
+ * This is in part because pino might get swapped out (see @{link usePino}, but also to allow us to forward messages to the renderer easily because otherwise to do so we would have to use a custom pino transport that then communicates back again with the main process to send messages to the correct window which is a ridiculous round trip for no benefit.
7
+ *
8
+ * This is seperate since it requires special handling because of the following:
9
+ * - We can't use useRuntimeConfig so it's injected by using vite define instead ({@link STATIC}).
10
+ * - We need to pass the logPath which is defined at runtime.
11
+ * - We need to use some electron only imports which causes problems for the regular server.
12
+ * - Pino needs to be told were to find it's workers electron side in prod.
13
+ * {@link https://github.com/pinojs/pino/blob/master/docs/bundling.md}
14
+ * Using vite's ?worker&url imports almost works (the paths are correct) but something about the transpiling breaks things I think.
15
+ */
16
+ export declare function useElectronLogger(pinoConfig?: PublicRuntimeConfig["logger"], windows?: () => BrowserWindow[], { receiveFromRenderer }?: {
17
+ receiveFromRenderer?: boolean;
18
+ }): Record<keyof import("pino").BaseLogger | keyof import("pino").LoggerExtras<never, boolean> | "customLevels" | "useOnlyCustomLevels", (data: any) => void>;
@@ -0,0 +1,69 @@
1
+ import { keys } from "@alanscodelog/utils/keys";
2
+ import { app } from "electron";
3
+ import path from "node:path";
4
+ import { pino } from "pino";
5
+ import { receiveFromRendererOnMain } from "./receiveFromRendererOnMain.js";
6
+ import { ELECTRON_LOG_TYPE } from "./types.js";
7
+ import { getBaseOptions } from "../helpers/getBaseOptions.js";
8
+ globalThis.__bundlerPathsOverrides = {
9
+ "pino-worker": `./node_modules/pino/lib/worker.js`,
10
+ "pino-worker-pipeline": `./node_modules/pino/lib/worker-pipeline.js`,
11
+ "thread-stream-worker": `./node_modules/thread-stream/lib/worker.js`,
12
+ "pino/file": `./node_modules/pino/file.js`,
13
+ "pino-pretty": `./node_modules/pino-pretty/index.js`
14
+ };
15
+ const bundlerOverrides = globalThis.__bundlerPathsOverrides;
16
+ const appPath = app.getAppPath();
17
+ for (const key of keys(bundlerOverrides)) {
18
+ bundlerOverrides[key] = path.resolve(appPath, bundlerOverrides[key]);
19
+ }
20
+ let initiated = false;
21
+ const levels = ["error", "warn", "debug", "trace", "info", "fatal"];
22
+ const logger = {};
23
+ export function useElectronLogger(pinoConfig, windows, {
24
+ receiveFromRenderer = true
25
+ } = {}) {
26
+ if (initiated && pinoConfig) {
27
+ throw new Error("Config can only be passed the first time when initiating.");
28
+ }
29
+ if (!initiated) {
30
+ if (!pinoConfig) {
31
+ throw new Error("Config must be passed the first time using useElectronLogger.");
32
+ }
33
+ if (!windows) {
34
+ throw new Error("windows getter must be passed the first time using useElectronLogger.");
35
+ }
36
+ initiated = true;
37
+ const { opts, transports, debug } = getBaseOptions(pinoConfig);
38
+ const pinoLogger = pino(
39
+ opts,
40
+ pino.transport(transports)
41
+ );
42
+ for (const level of levels) {
43
+ logger[level] = (data) => {
44
+ if (typeof data === "object") {
45
+ if (data.ns.startsWith("renderer:")) {
46
+ pinoLogger[level](data);
47
+ return;
48
+ } else if (!data.ns.startsWith("main:")) {
49
+ throw new Error(`Namespace on electron's main side must start with "main:", got ${data.ns} instead in log: ${JSON.stringify(data)}.`);
50
+ }
51
+ }
52
+ pinoLogger[level](data);
53
+ const wins = windows();
54
+ for (const win of wins) {
55
+ win.webContents.send(
56
+ ELECTRON_LOG_TYPE.FROM_MAIN_ON_RENDERER,
57
+ level,
58
+ data
59
+ );
60
+ }
61
+ };
62
+ }
63
+ if (receiveFromRenderer) {
64
+ receiveFromRendererOnMain(logger);
65
+ }
66
+ logger.debug({ ns: "main:log", ...debug });
67
+ }
68
+ return logger;
69
+ }
@@ -0,0 +1,5 @@
1
+ import type { useElectronLogger } from "./useElectronLogger.js.js";
2
+ /**
3
+ * Wraps a logger (prefixed the first argument's namespace key with "main:") for use in electron.
4
+ */
5
+ export declare function wrapLoggerForElectron<T extends ReturnType<typeof useElectronLogger>>(logger: T, prefix?: string): T;
@@ -0,0 +1,9 @@
1
+ export function wrapLoggerForElectron(logger, prefix = "main:") {
2
+ const res = {};
3
+ for (const key of Object.keys(logger)) {
4
+ res[key] = (...args) => {
5
+ logger[key]({ ...args[0], ns: prefix + args[0].ns }, ...args.slice(1));
6
+ };
7
+ }
8
+ return res;
9
+ }
@@ -0,0 +1,13 @@
1
+ import type { PublicRuntimeConfig } from "@nuxt/schema";
2
+ import type { LoggerOptions, TransportMultiOptions } from "pino";
3
+ export declare function getBaseOptions(config: PublicRuntimeConfig["logger"]): {
4
+ opts: LoggerOptions;
5
+ browserOpts: LoggerOptions;
6
+ transports: TransportMultiOptions;
7
+ debug: {
8
+ logPath: string;
9
+ resolvedLogPath: string;
10
+ writeLevel: string;
11
+ logLevel: string;
12
+ };
13
+ };
@@ -0,0 +1,121 @@
1
+ import { get } from "@alanscodelog/utils/get";
2
+ import { keys } from "@alanscodelog/utils/keys";
3
+ import { readable } from "@alanscodelog/utils/readable";
4
+ import { set } from "@alanscodelog/utils/set";
5
+ import { walk } from "@alanscodelog/utils/walk";
6
+ import path from "node:path";
7
+ import { pino } from "pino";
8
+ const levels = {
9
+ values: {
10
+ fatal: 60,
11
+ error: 50,
12
+ warn: 40,
13
+ info: 30,
14
+ debug: 20,
15
+ trace: 10
16
+ },
17
+ labels: {
18
+ 10: "trace",
19
+ 20: "debug",
20
+ 30: "info",
21
+ 40: "warn",
22
+ 50: "error",
23
+ 60: "fatal"
24
+ }
25
+ };
26
+ const isElectronClient = typeof window !== "undefined" && "electron" in window && window.electron;
27
+ export function getBaseOptions(config) {
28
+ if (isElectronClient) {
29
+ delete config.logPath;
30
+ }
31
+ const {
32
+ redact,
33
+ logPath,
34
+ additionalServerTransportTargets,
35
+ disabledServerTransportTargets
36
+ } = config;
37
+ const validLevels = keys(levels.values);
38
+ if (process.env.LOG_LEVEL && !validLevels.includes(process.env.LOG_LEVEL)) {
39
+ throw new Error(`logLevel is not a valid level: ${readable(validLevels)}`);
40
+ }
41
+ const maybeElectronLogLevel = import.meta.client ? window.electron?.meta?.env?.LOG_LEVEL : void 0;
42
+ const maybeElectronLogWriteLevel = import.meta.client ? window.electron?.meta?.env?.LOG_WRITE_LEVEL : void 0;
43
+ const writeLevel = maybeElectronLogWriteLevel ?? maybeElectronLogLevel ?? process.env.LOG_WRITE_LEVEL ?? process.env.LOG_LEVEL ?? (import.meta.dev ? "debug" : void 0) ?? "warn";
44
+ const logLevel = maybeElectronLogWriteLevel ?? process.env.LOG_LEVEL ?? (import.meta.dev ? "debug" : void 0) ?? "debug";
45
+ const processedAdditionalServerTransportTargets = [];
46
+ const thisContexts = [
47
+ ...isElectronClient ? ["electron-client"] : [],
48
+ // might not be available, careful
49
+ ...import.meta?.electron ? ["electron-main"] : [],
50
+ ...import.meta.client ? ["client"] : [],
51
+ ...import.meta.server ? ["server"] : []
52
+ ];
53
+ for (const target of additionalServerTransportTargets ?? []) {
54
+ const contexts = target._contexts;
55
+ const contextMatches = contexts === void 0 || contexts.find((c) => thisContexts.includes(c));
56
+ if (!contextMatches) continue;
57
+ const propPaths = target._loadFromEnv;
58
+ const clone = walk(target, void 0, { save: true });
59
+ processedAdditionalServerTransportTargets.push(clone);
60
+ for (const propPath of propPaths) {
61
+ const splitPath = propPath.split(".");
62
+ if (propPath) {
63
+ set(clone, splitPath, process.env[get(clone, splitPath)]);
64
+ }
65
+ }
66
+ delete clone._loadFromEnv;
67
+ }
68
+ return {
69
+ debug: {
70
+ logPath,
71
+ resolvedLogPath: path.resolve(logPath),
72
+ writeLevel,
73
+ logLevel
74
+ },
75
+ opts: {
76
+ redact,
77
+ // do not under any circumstances change how level looks, it breaks the logging ??? :/
78
+ level: logLevel,
79
+ formatters: {
80
+ bindings: () => ({})
81
+ },
82
+ // @ts-expect-error https://github.com/pinojs/pino/issues/2260
83
+ timestamp: pino.stdTimeFunctions.isoTime
84
+ },
85
+ browserOpts: {
86
+ browser: {
87
+ asObject: false,
88
+ transmit: isElectronClient ? {
89
+ level: logLevel,
90
+ send: (level, logEvent) => {
91
+ if (!logEvent.messages[0]?.ns?.startsWith("main") && levels.values[level] < levels.values[writeLevel]) {
92
+ logEvent.messages[0].ns &&= `renderer:${logEvent.messages[0].ns}`;
93
+ window.electron.api.log(logEvent);
94
+ }
95
+ }
96
+ } : void 0
97
+ }
98
+ },
99
+ transports: {
100
+ targets: [
101
+ {
102
+ level: writeLevel,
103
+ target: "pino/file",
104
+ options: {
105
+ destination: path.resolve(logPath),
106
+ mkdir: true
107
+ }
108
+ },
109
+ {
110
+ level: logLevel,
111
+ target: "pino-pretty",
112
+ options: {
113
+ colorize: true
114
+ }
115
+ },
116
+ ...processedAdditionalServerTransportTargets ?? []
117
+ // not sure when target doesn't exist, #future investigate
118
+ ].filter((t) => !disabledServerTransportTargets?.includes(t.target))
119
+ }
120
+ };
121
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "#imports";
2
+ import { useLogger } from "../composables/useLogger.js";
3
+ export default defineNuxtPlugin({
4
+ name: "client-init-log",
5
+ setup(nuxtApp) {
6
+ if (import.meta.client) {
7
+ nuxtApp.hook("app:created", () => {
8
+ const config = useRuntimeConfig().public;
9
+ const initialLog = { appInfo: config.appInfo };
10
+ if (window.navigator !== void 0) {
11
+ initialLog.environmentInfo = {
12
+ userAgent: navigator.userAgent
13
+ };
14
+ }
15
+ useLogger().info({ ns: "client:init", ...initialLog });
16
+ });
17
+ }
18
+ },
19
+ env: {
20
+ // don't run when rendering server-only or island components.
21
+ islands: false
22
+ }
23
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { defineEventHandler, useServerLogger } from "#imports";
2
+ export default defineEventHandler((event) => {
3
+ const logger = useServerLogger();
4
+ logger.trace({
5
+ ns: "server:req:all",
6
+ req: {
7
+ headers: event.node.req.headers,
8
+ method: event.node.req.method,
9
+ url: event.node.req.url
10
+ }
11
+ });
12
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import os from "node:os";
2
+ import { defineNitroPlugin, useRuntimeConfig, useServerLogger } from "#imports";
3
+ export default defineNitroPlugin((_nitroApp) => {
4
+ const cpus = os.cpus();
5
+ const environmentInfo = {
6
+ os: os.platform(),
7
+ osType: os.type(),
8
+ osVersion: os.release(),
9
+ arch: os.arch(),
10
+ nodeVersion: process.version,
11
+ cpu: cpus?.[0]?.model,
12
+ cpus: cpus.length,
13
+ totalMem: os.totalmem()
14
+ };
15
+ useServerLogger().info({
16
+ ns: "server:init",
17
+ appInfo: useRuntimeConfig().public,
18
+ environmentInfo,
19
+ isUsingTestingDb: process.env.LOCALSERVERDB
20
+ });
21
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }
@@ -0,0 +1,4 @@
1
+ export declare const useServerLogger: ({ redirectTo, redirectAll }?: {
2
+ redirectTo?: "electron";
3
+ redirectAll?: boolean | ((level: keyof import("pino").Logger, data: any) => boolean);
4
+ }) => import("../../shared/createUseLogger.js.js").BaseLogger;
@@ -0,0 +1,3 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { createUseLogger } from "../../shared/createUseLogger.js";
3
+ export const useServerLogger = createUseLogger(useRuntimeConfig);
@@ -0,0 +1,20 @@
1
+ import type { RuntimeConfig } from "nuxt/schema";
2
+ import { type Logger } from "pino";
3
+ import type { Levels } from "../types.js.js";
4
+ export type BaseLogger = Record<Levels, ((data: any) => void)> & {
5
+ flush: Logger["flush"];
6
+ };
7
+ /**
8
+ * This exists to allow creating useLogger both on the client and the server since for the module to work on the server we must import from `#imports` and have the module transpile the files, but this is not neccesary from the composable.
9
+ *
10
+ * So they both create useLogger by passing in useRuntimeConfig how they can.
11
+ */
12
+ export declare function createUseLogger(rc: () => RuntimeConfig): ({ redirectTo, redirectAll }?: {
13
+ redirectTo?: "electron";
14
+ /**
15
+ * Prefixes all messages not coming from main with `renderer:` so that they will be forwarded.
16
+ *
17
+ * Can also be a function to filter what gets forwarded.
18
+ */
19
+ redirectAll?: boolean | ((level: keyof Logger, data: any) => boolean);
20
+ }) => BaseLogger;
@@ -0,0 +1,46 @@
1
+ import { pino } from "pino";
2
+ import { getBaseOptions } from "../helpers/getBaseOptions.js";
3
+ const logger = {};
4
+ export function createUseLogger(rc) {
5
+ let initiated = false;
6
+ return function useLogger({
7
+ redirectTo,
8
+ redirectAll = true
9
+ } = {}) {
10
+ const levels = ["error", "warn", "debug", "trace", "info", "fatal"];
11
+ if (!initiated) {
12
+ initiated = true;
13
+ const config = rc().public;
14
+ const { opts, transports, browserOpts, debug } = getBaseOptions(config.logger);
15
+ let transport;
16
+ if (import.meta.server) {
17
+ transport = pino.transport(transports);
18
+ }
19
+ const pinoLogger = pino(
20
+ {
21
+ ...opts,
22
+ ...import.meta.client ? browserOpts : {}
23
+ },
24
+ transport
25
+ );
26
+ for (const level of levels) {
27
+ logger[level] = (data) => {
28
+ if (redirectTo === "electron") {
29
+ if (typeof data === "object" && typeof data.ns === "string" && !data.ns.startsWith("main:")) {
30
+ if (data.ns.startsWith("renderer:")) {
31
+ window.electron?.api.log(level, data);
32
+ } else if (typeof redirectAll === "function" ? redirectAll(level, data) : redirectAll) {
33
+ data.ns = `renderer:${data.ns}`;
34
+ window.electron?.api.log(level, data);
35
+ }
36
+ }
37
+ }
38
+ pinoLogger[level](data);
39
+ };
40
+ }
41
+ logger.debug({ ns: "log", ...debug, enabledTransportTargets: transports.targets.map((t) => t?.target) });
42
+ return logger;
43
+ }
44
+ return logger;
45
+ };
46
+ }
@@ -0,0 +1 @@
1
+ export type Levels = "error" | "warn" | "debug" | "trace" | "info" | "fatal";
File without changes
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witchcraft/nuxt-logger",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Nuxt module for logging.",
5
5
  "repository": "witchcraftjs/nuxt-logger",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "pino-pretty": "^11.2.2"
42
42
  },
43
43
  "dependencies": {
44
- "@alanscodelog/tsconfigs": "^6.1.0",
44
+ "@alanscodelog/tsconfigs": "^6.2.0",
45
45
  "@alanscodelog/utils": "^6.0.1",
46
46
  "@nuxt/kit": "^4.0.3",
47
47
  "defu": "latest",
package/src/module.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  addServerImportsDir,
6
6
  createResolver,
7
7
  defineNuxtModule,
8
+ resolveAlias,
8
9
  resolvePath
9
10
  } from "@nuxt/kit"
10
11
  import { defu } from "defu"
@@ -25,7 +26,7 @@ export interface ModuleOptions {
25
26
  * See {@link https://getpino.io/#/docs/redaction?id=redaction Pino redaction options}.
26
27
  */
27
28
  redact: string[]
28
- /** This is "/var/log/testing/${appName}.log". */
29
+ /** This is "~~/logs/${appName}.log" by default. It's recommended you change it to something like "/var/log/${appName}.log" in production. */
29
30
  serverLogPath?: string
30
31
  /** This is "~~/logs/server.log" by default. */
31
32
  devServerLogPath?: string
@@ -106,10 +107,10 @@ export default defineNuxtModule<ModuleOptions>({
106
107
  },
107
108
  async setup(options, nuxt) {
108
109
  const { resolve } = createResolver(import.meta.url)
109
- options.devServerLogPath ??= await resolvePath("~~/logs/server.log", { alias: nuxt.options.alias })
110
+ options.devServerLogPath ??= path.relative(nuxt.options.rootDir, resolveAlias("~~/logs/server.log", nuxt.options.alias))
110
111
  const maybeAppName = (nuxt.options.runtimeConfig.public as any)?.appInfo?.name
111
112
  const appName = options.appName ?? maybeAppName ?? path.basename(await resolvePath("~~", { alias: nuxt.options.alias }))
112
- options.serverLogPath ??= `/var/log/testing/${appName}.log`
113
+ options.serverLogPath ??= path.relative(nuxt.options.rootDir, resolveAlias(`~~/logs/${appName}.log`, nuxt.options.alias))
113
114
  if (process.env.NODE_ENV === "development") {
114
115
  if (!await fs.stat(options.devServerLogPath).then(() => true).catch(() => false)) {
115
116
  await fs.mkdir(path.dirname(options.devServerLogPath), {
@@ -4,6 +4,7 @@ import { readable } from "@alanscodelog/utils/readable"
4
4
  import { set } from "@alanscodelog/utils/set"
5
5
  import { walk } from "@alanscodelog/utils/walk"
6
6
  import type { PublicRuntimeConfig } from "@nuxt/schema"
7
+ import path from "node:path"
7
8
  import type { LoggerOptions, TransportMultiOptions } from "pino"
8
9
  import { pino } from "pino"
9
10
 
@@ -38,6 +39,7 @@ export function getBaseOptions(
38
39
  transports: TransportMultiOptions
39
40
  debug: {
40
41
  logPath: string
42
+ resolvedLogPath: string
41
43
  writeLevel: string
42
44
  logLevel: string
43
45
  }
@@ -103,6 +105,7 @@ export function getBaseOptions(
103
105
  return {
104
106
  debug: {
105
107
  logPath,
108
+ resolvedLogPath: path.resolve(logPath),
106
109
  writeLevel,
107
110
  logLevel
108
111
  },
@@ -113,6 +116,7 @@ export function getBaseOptions(
113
116
  formatters: {
114
117
  bindings: () => ({})
115
118
  },
119
+ // @ts-expect-error https://github.com/pinojs/pino/issues/2260
116
120
  timestamp: pino.stdTimeFunctions.isoTime
117
121
  },
118
122
  browserOpts: {
@@ -137,7 +141,7 @@ export function getBaseOptions(
137
141
  level: writeLevel,
138
142
  target: "pino/file",
139
143
  options: {
140
- destination: logPath,
144
+ destination: path.resolve(logPath),
141
145
  mkdir: true
142
146
  }
143
147
  },