@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.
- package/README.md +1 -1
- package/dist/module.d.mts +75 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +88 -0
- package/dist/runtime/composables/setupElectronMainToRendererLogging.d.ts +1 -0
- package/dist/runtime/composables/setupElectronMainToRendererLogging.js +9 -0
- package/dist/runtime/composables/useLogger.d.ts +4 -0
- package/dist/runtime/composables/useLogger.js +3 -0
- package/dist/runtime/electron/createElectronLoggerApi.d.ts +9 -0
- package/dist/runtime/electron/createElectronLoggerApi.js +13 -0
- package/dist/runtime/electron/index.d.ts +4 -0
- package/dist/runtime/electron/index.js +4 -0
- package/dist/runtime/electron/receiveFromRendererOnMain.d.ts +2 -0
- package/dist/runtime/electron/receiveFromRendererOnMain.js +9 -0
- package/dist/runtime/electron/types.d.ts +6 -0
- package/dist/runtime/electron/types.js +5 -0
- package/dist/runtime/electron/useElectronLogger.d.ts +18 -0
- package/dist/runtime/electron/useElectronLogger.js +69 -0
- package/dist/runtime/electron/wrapLoggerForElectron.d.ts +5 -0
- package/dist/runtime/electron/wrapLoggerForElectron.js +9 -0
- package/dist/runtime/helpers/getBaseOptions.d.ts +13 -0
- package/dist/runtime/helpers/getBaseOptions.js +121 -0
- package/dist/runtime/plugins/init.d.ts +2 -0
- package/dist/runtime/plugins/init.js +23 -0
- package/dist/runtime/server/middleware/log.d.ts +2 -0
- package/dist/runtime/server/middleware/log.js +12 -0
- package/dist/runtime/server/plugins/init.d.ts +2 -0
- package/dist/runtime/server/plugins/init.js +21 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/useServerLogger.d.ts +4 -0
- package/dist/runtime/server/utils/useServerLogger.js +3 -0
- package/dist/runtime/shared/createUseLogger.d.ts +20 -0
- package/dist/runtime/shared/createUseLogger.js +46 -0
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/types.js +0 -0
- package/dist/types.d.mts +3 -0
- package/package.json +2 -2
- package/src/module.ts +4 -3
- 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](
|
|
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 };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -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,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,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,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,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,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,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,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,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
|
package/dist/types.d.mts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/nuxt-logger",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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
|
|
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 ??=
|
|
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 ??=
|
|
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
|
},
|