hotelme-nuxt-logger 1.2.1 → 1.4.0
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 +17 -0
- package/dist/module.cjs +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -1
- package/dist/runtime/httpError.d.ts +17 -0
- package/dist/runtime/httpError.js +18 -0
- package/dist/runtime/logger.d.ts +15 -3
- package/dist/runtime/logger.js +17 -3
- package/dist/runtime/middleware/logger.server.js +2 -1
- package/dist/runtime/plugins/error.js +2 -1
- package/dist/runtime/server/plugin.d.ts +8 -0
- package/dist/runtime/server/plugin.js +15 -4
- package/dist/runtime/server/routes/client-error.post.js +2 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -70,6 +70,8 @@ export default defineNuxtConfig({
|
|
|
70
70
|
enableRequestLogging: true
|
|
71
71
|
},
|
|
72
72
|
|
|
73
|
+
// Назва потрапляє в runtimeConfig.public.projectName і використовується для всіх
|
|
74
|
+
// записів (запити, помилки Nitro, SSR, клієнтські помилки, ручні logger.*).
|
|
73
75
|
// Альтернативно через env
|
|
74
76
|
runtimeConfig: {
|
|
75
77
|
public: {
|
|
@@ -115,6 +117,8 @@ logger.warn('Rate limit approaching', { remaining: 10 })
|
|
|
115
117
|
|
|
116
118
|
### В API handlers
|
|
117
119
|
|
|
120
|
+
`logger` та `getRequestId` auto-import'яться і в серверному коді Nitro (з 1.4.0). Явний імпорт, якщо треба: `import { logger } from 'hotelme-nuxt-logger/runtime/logger'`.
|
|
121
|
+
|
|
118
122
|
```typescript
|
|
119
123
|
// server/api/hotels.get.ts
|
|
120
124
|
export default defineEventHandler((event) => {
|
|
@@ -169,6 +173,19 @@ export default defineEventHandler((event) => {
|
|
|
169
173
|
})
|
|
170
174
|
```
|
|
171
175
|
|
|
176
|
+
## Рівні для помилок сервера
|
|
177
|
+
|
|
178
|
+
Nitro `error` hook (модульна опція `enableErrorLogging`):
|
|
179
|
+
|
|
180
|
+
| Що сталося | msg | level | stack |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `createError({ statusCode: 4xx })`, відсутній `/_nuxt/*` chunk, валідація | `request_error` | Warn | ні |
|
|
183
|
+
| 5xx або не-HTTP виняток у handler/middleware | `unhandled_error` | Error | так |
|
|
184
|
+
| `unhandledRejection` / `uncaughtException` процесу | `unhandled_rejection` / `uncaught_exception` | Fatal | так |
|
|
185
|
+
|
|
186
|
+
4xx не є помилкою застосунку, тому не засмічує рівень Error; сам запит при цьому все одно
|
|
187
|
+
логується request-middleware як `stats` / `api request` з кодом відповіді.
|
|
188
|
+
|
|
172
189
|
## Формат логів
|
|
173
190
|
|
|
174
191
|
| Поле | Тип | Опис |
|
package/dist/module.cjs
CHANGED
|
@@ -26,6 +26,10 @@ const module$1 = kit.defineNuxtModule({
|
|
|
26
26
|
const clientErrorEndpoint = options.clientErrorEndpoint || "/_logger/client-error";
|
|
27
27
|
nuxt.options.runtimeConfig.public.loggerClientErrorEndpoint = clientErrorEndpoint;
|
|
28
28
|
kit.addImportsDir(resolver.resolve("./runtime"));
|
|
29
|
+
kit.addServerImports([
|
|
30
|
+
{ name: "logger", from: resolver.resolve("./runtime/logger") },
|
|
31
|
+
{ name: "getRequestId", from: resolver.resolve("./runtime/logger") }
|
|
32
|
+
]);
|
|
29
33
|
if (options.enableRequestLogging) {
|
|
30
34
|
kit.addServerHandler({
|
|
31
35
|
handler: resolver.resolve("./runtime/middleware/logger.server"),
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addImportsDir, addServerHandler, addServerPlugin, addPlugin } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, addImportsDir, addServerImports, addServerHandler, addServerPlugin, addPlugin } from '@nuxt/kit';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
4
|
|
|
@@ -32,6 +32,10 @@ const module = defineNuxtModule({
|
|
|
32
32
|
const clientErrorEndpoint = options.clientErrorEndpoint || "/_logger/client-error";
|
|
33
33
|
nuxt.options.runtimeConfig.public.loggerClientErrorEndpoint = clientErrorEndpoint;
|
|
34
34
|
addImportsDir(resolver.resolve("./runtime"));
|
|
35
|
+
addServerImports([
|
|
36
|
+
{ name: "logger", from: resolver.resolve("./runtime/logger") },
|
|
37
|
+
{ name: "getRequestId", from: resolver.resolve("./runtime/logger") }
|
|
38
|
+
]);
|
|
35
39
|
if (options.enableRequestLogging) {
|
|
36
40
|
addServerHandler({
|
|
37
41
|
handler: resolver.resolve("./runtime/middleware/logger.server"),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Чисті хелпери для класифікації помилок у Nitro `error` hook.
|
|
3
|
+
* Без імпортів із Nuxt/Nitro, щоб їх можна було перевірити звичайним Node.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* HTTP-статус помилки. h3 `createError()` кладе його в `statusCode`,
|
|
7
|
+
* деякі бібліотеки — у `status`. Якщо на помилці статусу немає,
|
|
8
|
+
* повертаємо `fallback` (зазвичай `res.statusCode`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function httpStatusOf(error: unknown, fallback?: number): number | undefined;
|
|
11
|
+
/** 4xx: помилка на боці клієнта (не знайдено, невалідний запит, заборонено). */
|
|
12
|
+
export declare function isClientError(status: number | undefined): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Текст помилки для логу: `message`, а якщо порожній — `statusMessage`
|
|
15
|
+
* (h3 для 404 на статику дає порожній message і "Not Found" у statusMessage).
|
|
16
|
+
*/
|
|
17
|
+
export declare function errorText(error: unknown): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function httpStatusOf(error, fallback) {
|
|
2
|
+
const e = error;
|
|
3
|
+
for (const candidate of [e?.statusCode, e?.status]) {
|
|
4
|
+
if (typeof candidate === "number" && candidate >= 100 && candidate <= 599) {
|
|
5
|
+
return candidate;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
export function isClientError(status) {
|
|
11
|
+
return status !== void 0 && status >= 400 && status < 500;
|
|
12
|
+
}
|
|
13
|
+
export function errorText(error) {
|
|
14
|
+
const e = error;
|
|
15
|
+
if (e && typeof e.message === "string" && e.message) return e.message;
|
|
16
|
+
if (e && typeof e.statusMessage === "string" && e.statusMessage) return e.statusMessage;
|
|
17
|
+
return String(error);
|
|
18
|
+
}
|
package/dist/runtime/logger.d.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Виводить логи в stdout у JSON-форматі, сумісному з бекенд-логами.
|
|
5
5
|
*
|
|
6
6
|
* Обов'язкові поля в кожному лозі:
|
|
7
|
-
* - ts:
|
|
7
|
+
* - ts: timestamp у локальному часі процесу з зсувом зони
|
|
8
8
|
* - level: рівень логування (Trace, Debug, Info, Warn, Error, Fatal)
|
|
9
9
|
* - reqid: унікальний ID запиту (з header X-Request-ID)
|
|
10
10
|
* - msg: текст повідомлення
|
|
11
|
-
* - project: назва проекту (
|
|
11
|
+
* - project: назва проекту (див. resolveProjectName)
|
|
12
12
|
* - log_type: 'frontend' для розрізнення з бекенд-логами
|
|
13
13
|
* - pid: process ID
|
|
14
14
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* "project": "hotelme",
|
|
21
21
|
* "log_type": "frontend",
|
|
22
22
|
* "pid": 53,
|
|
23
|
-
* "ts": "2025-06-17T09:58:18.
|
|
23
|
+
* "ts": "2025-06-17T09:58:18.539000-0500",
|
|
24
24
|
* "stage": "after",
|
|
25
25
|
* "domain": "localhost:3000",
|
|
26
26
|
* "request": "/api/hotels",
|
|
@@ -37,6 +37,17 @@ export declare enum LogLevel {
|
|
|
37
37
|
Error = "Error",
|
|
38
38
|
Fatal = "Fatal"
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Запам'ятати назву проекту для всіх наступних записів. Викликається серверними
|
|
42
|
+
* частинами модуля при старті (Nitro-плагін) та на першому запиті (middleware,
|
|
43
|
+
* SSR-плагін). Порожні значення ігноруються.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setProjectName(name: unknown): void;
|
|
46
|
+
/**
|
|
47
|
+
* Назва проекту для запису: явно передана → з runtimeConfig (setProjectName) →
|
|
48
|
+
* env `NUXT_PUBLIC_PROJECT_NAME` → 'hotelme'.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveProjectName(explicit?: unknown): string;
|
|
40
51
|
/**
|
|
41
52
|
* Генерує унікальний ID запиту
|
|
42
53
|
* Використовується UUID v4 без дефісів для компактності
|
|
@@ -51,6 +62,7 @@ export declare function getRequestId(): string;
|
|
|
51
62
|
*
|
|
52
63
|
* Важливі поля в details:
|
|
53
64
|
* - reqid: ID запиту (береться з X-Request-ID header)
|
|
65
|
+
* - project: назва проекту (за замовчуванням з runtimeConfig, див. resolveProjectName)
|
|
54
66
|
* - request: URL запиту
|
|
55
67
|
* - request_time: час виконання запиту в секундах
|
|
56
68
|
* - domain: домен
|
package/dist/runtime/logger.js
CHANGED
|
@@ -8,8 +8,22 @@ export var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
|
8
8
|
LogLevel2["Fatal"] = "Fatal";
|
|
9
9
|
return LogLevel2;
|
|
10
10
|
})(LogLevel || {});
|
|
11
|
-
const
|
|
11
|
+
const DEFAULT_PROJECT_NAME = "hotelme";
|
|
12
12
|
const PID = import.meta.server ? process.pid : void 0;
|
|
13
|
+
let projectName;
|
|
14
|
+
export function setProjectName(name) {
|
|
15
|
+
if (typeof name === "string" && name) {
|
|
16
|
+
projectName = name;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function resolveProjectName(explicit) {
|
|
20
|
+
if (typeof explicit === "string" && explicit) return explicit;
|
|
21
|
+
if (projectName) return projectName;
|
|
22
|
+
if (import.meta.server && typeof process !== "undefined" && process.env?.NUXT_PUBLIC_PROJECT_NAME) {
|
|
23
|
+
return process.env.NUXT_PUBLIC_PROJECT_NAME;
|
|
24
|
+
}
|
|
25
|
+
return DEFAULT_PROJECT_NAME;
|
|
26
|
+
}
|
|
13
27
|
export function getRequestId() {
|
|
14
28
|
return uuidv4().replace(/-/g, "");
|
|
15
29
|
}
|
|
@@ -27,12 +41,12 @@ function formatTimestamp() {
|
|
|
27
41
|
}
|
|
28
42
|
export function log(level, msg, details) {
|
|
29
43
|
const reqid = details?.reqid || getRequestId();
|
|
30
|
-
const { request, request_time, domain, method, response, stage, ...otherDetails } = details || {};
|
|
44
|
+
const { project, request, request_time, domain, method, response, stage, ...otherDetails } = details || {};
|
|
31
45
|
const logEntry = {
|
|
32
46
|
msg,
|
|
33
47
|
level,
|
|
34
48
|
reqid,
|
|
35
|
-
project:
|
|
49
|
+
project: resolveProjectName(project),
|
|
36
50
|
log_type: "frontend",
|
|
37
51
|
pid: PID,
|
|
38
52
|
ts: formatTimestamp(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineEventHandler, getRequestURL } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
-
import { logger } from "../logger.js";
|
|
3
|
+
import { logger, setProjectName } from "../logger.js";
|
|
4
4
|
import { v4 as uuidv4 } from "uuid";
|
|
5
5
|
export default defineEventHandler(async (event) => {
|
|
6
6
|
if (event.context.reqid) {
|
|
@@ -9,6 +9,7 @@ export default defineEventHandler(async (event) => {
|
|
|
9
9
|
try {
|
|
10
10
|
const config = useRuntimeConfig();
|
|
11
11
|
const projectName = config.public.projectName || "hotelme";
|
|
12
|
+
setProjectName(projectName);
|
|
12
13
|
let reqid = event.node.req.headers["x-request-id"];
|
|
13
14
|
if (!reqid) {
|
|
14
15
|
reqid = uuidv4().replace(/-/g, "");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig } from "#imports";
|
|
2
|
-
import { logger } from "../logger.js";
|
|
2
|
+
import { logger, setProjectName } from "../logger.js";
|
|
3
3
|
import { isBrowserExtensionError } from "../browserExtensionFilter.js";
|
|
4
4
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
5
5
|
if (import.meta.server) {
|
|
6
|
+
setProjectName(useRuntimeConfig().public.projectName);
|
|
6
7
|
const event = useRequestEvent();
|
|
7
8
|
const reqid = event?.context?.reqid;
|
|
8
9
|
const request = event?.path;
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* `reqid` is read from `event.context.reqid` set by `logger.server.ts` middleware,
|
|
9
9
|
* so SSR and backend logs stay correlated by the same id.
|
|
10
|
+
*
|
|
11
|
+
* Levels (since 1.3.0):
|
|
12
|
+
* - 4xx (`createError({ statusCode: 404 })`, missing `/_nuxt/*` asset, validation
|
|
13
|
+
* errors): `msg: "request_error"`, level Warn, no stack trace. These are the
|
|
14
|
+
* client's problem, and the request middleware already logs the same request
|
|
15
|
+
* as `stats`/`api request` with the status code. Stale hashed chunks after a
|
|
16
|
+
* deploy used to produce dozens of Error entries with 1 KB Nitro stacks each.
|
|
17
|
+
* - 5xx and non-HTTP throws: `msg: "unhandled_error"`, level Error, with stack.
|
|
10
18
|
*/
|
|
11
19
|
declare const _default: import("nitropack").NitroAppPlugin;
|
|
12
20
|
export default _default;
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { defineNitroPlugin } from "nitropack/runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { logger, setProjectName } from "../logger.js";
|
|
4
|
+
import { httpStatusOf, isClientError, errorText } from "../httpError.js";
|
|
3
5
|
export default defineNitroPlugin((nitroApp) => {
|
|
6
|
+
setProjectName(useRuntimeConfig().public.projectName);
|
|
4
7
|
nitroApp.hooks.hook("error", (error, ctx) => {
|
|
5
8
|
const event = ctx?.event;
|
|
6
9
|
const req = event?.node?.req;
|
|
7
10
|
const res = event?.node?.res;
|
|
8
|
-
|
|
11
|
+
const status = httpStatusOf(error, res?.statusCode);
|
|
12
|
+
const base = {
|
|
9
13
|
reqid: event?.context?.reqid,
|
|
10
14
|
domain: req?.headers?.host,
|
|
11
15
|
request: event?.path || req?.url,
|
|
12
16
|
method: req?.method,
|
|
13
|
-
response:
|
|
14
|
-
error:
|
|
17
|
+
response: status,
|
|
18
|
+
error: errorText(error)
|
|
19
|
+
};
|
|
20
|
+
if (isClientError(status)) {
|
|
21
|
+
logger.warn("request_error", base);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
logger.error("unhandled_error", {
|
|
25
|
+
...base,
|
|
15
26
|
error_name: error instanceof Error ? error.name : void 0,
|
|
16
27
|
stack: error instanceof Error ? error.stack : void 0
|
|
17
28
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineEventHandler, readBody } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
3
|
import { logger } from "../../logger.js";
|
|
3
4
|
import { isBrowserExtensionError } from "../../browserExtensionFilter.js";
|
|
4
5
|
export default defineEventHandler(async (event) => {
|
|
@@ -9,6 +10,7 @@ export default defineEventHandler(async (event) => {
|
|
|
9
10
|
}
|
|
10
11
|
logger.error(typeof msg === "string" && msg ? msg : "client_error", {
|
|
11
12
|
reqid: event.context.reqid,
|
|
13
|
+
project: useRuntimeConfig().public.projectName,
|
|
12
14
|
domain: event.node.req.headers.host,
|
|
13
15
|
referer: event.node.req.headers.referer,
|
|
14
16
|
user_agent: event.node.req.headers["user-agent"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hotelme-nuxt-logger",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Стандартний logger для Nuxt проектів з JSON виводом сумісним з бекенд-логами",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/types.d.ts",
|
|
21
21
|
"import": "./dist/module.mjs",
|
|
22
22
|
"require": "./dist/module.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./runtime/logger": {
|
|
25
|
+
"types": "./dist/runtime/logger.d.ts",
|
|
26
|
+
"import": "./dist/runtime/logger.js"
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"main": "./dist/module.cjs",
|