hotelme-nuxt-logger 1.0.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 +229 -0
- package/dist/module.cjs +33 -0
- package/dist/module.d.cts +16 -0
- package/dist/module.d.mts +16 -0
- package/dist/module.d.ts +16 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +39 -0
- package/dist/runtime/logger.d.ts +79 -0
- package/dist/runtime/logger.js +57 -0
- package/dist/runtime/middleware/logger.server.d.ts +42 -0
- package/dist/runtime/middleware/logger.server.js +71 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @hotelme/nuxt-logger
|
|
2
|
+
|
|
3
|
+
Стандартний logger для Nuxt 3 проектів з JSON виводом, сумісним з бекенд-логами.
|
|
4
|
+
|
|
5
|
+
## Особливості
|
|
6
|
+
|
|
7
|
+
✅ **JSON формат виводу** в stdout
|
|
8
|
+
✅ **Обов'язкові поля**: `ts`, `level`, `reqid`, `msg`, `project`, `log_type`, `pid`
|
|
9
|
+
✅ **Автоматичне логування** всіх HTTP запитів з часом виконання
|
|
10
|
+
✅ **X-Request-ID support** - використовує reqid з header або генерує новий
|
|
11
|
+
✅ **Timestamp з timezone** - формат `2025-06-17T09:58:18.539733-0500`
|
|
12
|
+
✅ **Auto-import** - `logger` доступний глобально
|
|
13
|
+
✅ **TypeScript** підтримка з повною типізацією
|
|
14
|
+
✅ **Сумісність з бекенд-логами** для єдиної системи моніторингу
|
|
15
|
+
|
|
16
|
+
## Покращення порівняно зі стандартними логами
|
|
17
|
+
|
|
18
|
+
### 1. reqid з X-Request-ID header
|
|
19
|
+
```typescript
|
|
20
|
+
// Береться з header або генерується автоматично
|
|
21
|
+
const reqid = event.node.req.headers['x-request-id'] || generateUUID()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Timestamp з timezone та мікросекундами
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"ts": "2025-06-17T09:58:18.539733-0500"
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 3. Правильний msg для API запитів
|
|
32
|
+
```json
|
|
33
|
+
// Для /api/* запитів
|
|
34
|
+
{ "msg": "api request", "request": "/api/hotels" }
|
|
35
|
+
|
|
36
|
+
// Для інших запитів
|
|
37
|
+
{ "msg": "stats", "request": "/hotels" }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 4. response замість status_code
|
|
41
|
+
```json
|
|
42
|
+
{ "response": 200 }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 5. Всі логи в JSON форматі
|
|
46
|
+
Ніяких console.log з текстом - тільки structured JSON!
|
|
47
|
+
|
|
48
|
+
## Встановлення
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @hotelme/nuxt-logger uuid
|
|
52
|
+
# або
|
|
53
|
+
yarn add @hotelme/nuxt-logger uuid
|
|
54
|
+
# або
|
|
55
|
+
pnpm add @hotelme/nuxt-logger uuid
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Налаштування
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// nuxt.config.ts
|
|
62
|
+
export default defineNuxtConfig({
|
|
63
|
+
modules: ['@hotelme/nuxt-logger'],
|
|
64
|
+
|
|
65
|
+
logger: {
|
|
66
|
+
// Назва проекту для логів
|
|
67
|
+
projectName: 'my-project',
|
|
68
|
+
|
|
69
|
+
// Увімкнути автоматичне логування запитів
|
|
70
|
+
enableRequestLogging: true
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Альтернативно через env
|
|
74
|
+
runtimeConfig: {
|
|
75
|
+
public: {
|
|
76
|
+
projectName: process.env.NUXT_PUBLIC_PROJECT_NAME || 'my-project'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Використання
|
|
83
|
+
|
|
84
|
+
### Автоматичне логування запитів
|
|
85
|
+
|
|
86
|
+
Модуль автоматично логує кожен HTTP запит:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"msg": "api request",
|
|
91
|
+
"level": "Info",
|
|
92
|
+
"reqid": "aec7c27a231d1843",
|
|
93
|
+
"project": "hotelme",
|
|
94
|
+
"log_type": "frontend",
|
|
95
|
+
"pid": 53,
|
|
96
|
+
"ts": "2025-06-17T09:58:18.539733-0500",
|
|
97
|
+
"stage": "after",
|
|
98
|
+
"domain": "localhost:3000",
|
|
99
|
+
"request": "/api/hotels?city=kyiv",
|
|
100
|
+
"request_time": "0.045",
|
|
101
|
+
"method": "GET",
|
|
102
|
+
"response": 200,
|
|
103
|
+
"user_id": "a1b2c3d4e5f6"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Ручне логування
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Auto-imported - працює всюди
|
|
111
|
+
logger.info('User logged in', { user_id: '123' })
|
|
112
|
+
logger.error('Failed to fetch', { error: err.message })
|
|
113
|
+
logger.warn('Rate limit approaching', { remaining: 10 })
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### В API handlers
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// server/api/hotels.get.ts
|
|
120
|
+
export default defineEventHandler((event) => {
|
|
121
|
+
const reqid = event.context.reqid // reqid з X-Request-ID
|
|
122
|
+
|
|
123
|
+
logger.info('Fetching hotels', { reqid, city: event.query.city })
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const hotels = await fetchHotels(event.query.city)
|
|
127
|
+
return hotels
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error('Failed to fetch hotels', {
|
|
130
|
+
reqid,
|
|
131
|
+
error: error.message,
|
|
132
|
+
city: event.query.city
|
|
133
|
+
})
|
|
134
|
+
throw error
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Передача X-Request-ID між сервісами
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Передаємо reqid в запити до бекенду
|
|
143
|
+
const response = await $fetch('/api/hotels', {
|
|
144
|
+
headers: {
|
|
145
|
+
'X-Request-ID': event.context.reqid
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API
|
|
151
|
+
|
|
152
|
+
### Logger методи
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
logger.trace(msg: string, details?: Record<string, any>): void
|
|
156
|
+
logger.debug(msg: string, details?: Record<string, any>): void
|
|
157
|
+
logger.info(msg: string, details?: Record<string, any>): void
|
|
158
|
+
logger.warn(msg: string, details?: Record<string, any>): void
|
|
159
|
+
logger.error(msg: string, details?: Record<string, any>): void
|
|
160
|
+
logger.fatal(msg: string, details?: Record<string, any>): void
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Event Context
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
export default defineEventHandler((event) => {
|
|
167
|
+
const reqid = event.context.reqid // З X-Request-ID header
|
|
168
|
+
const userId = event.context.userId // UUID з cookie
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Формат логів
|
|
173
|
+
|
|
174
|
+
| Поле | Тип | Опис |
|
|
175
|
+
|------|-----|------|
|
|
176
|
+
| `msg` | string | "api request" для /api/*, "stats" для інших |
|
|
177
|
+
| `level` | string | Trace/Debug/Info/Warn/Error/Fatal |
|
|
178
|
+
| `reqid` | string | З header X-Request-ID або UUID |
|
|
179
|
+
| `project` | string | Назва проекту |
|
|
180
|
+
| `log_type` | string | "frontend" |
|
|
181
|
+
| `pid` | number | Process ID |
|
|
182
|
+
| `ts` | string | Timestamp з timezone |
|
|
183
|
+
| `stage` | string | "after" для завершених запитів |
|
|
184
|
+
| `domain` | string | Домен запиту |
|
|
185
|
+
| `request` | string | URL запиту |
|
|
186
|
+
| `request_time` | string | Час виконання в секундах |
|
|
187
|
+
| `method` | string | HTTP метод |
|
|
188
|
+
| `response` | number | HTTP статус код |
|
|
189
|
+
|
|
190
|
+
## Інтеграція з системами моніторингу
|
|
191
|
+
|
|
192
|
+
Легко інтегрується з:
|
|
193
|
+
- **ELK Stack** (Elasticsearch, Logstash, Kibana)
|
|
194
|
+
- **Grafana Loki**
|
|
195
|
+
- **Datadog**
|
|
196
|
+
- **CloudWatch**
|
|
197
|
+
- **Graylog**
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Просто направ stdout в систему збору логів
|
|
201
|
+
node .output/server/index.mjs | your-log-collector
|
|
202
|
+
|
|
203
|
+
# Або в Docker
|
|
204
|
+
docker logs your-container | jq '.msg'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Приклади фільтрації
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Логи з конкретним reqid
|
|
211
|
+
npm run dev | jq 'select(.reqid == "abc123")'
|
|
212
|
+
|
|
213
|
+
# Тільки помилки
|
|
214
|
+
npm run dev | jq 'select(.level == "Error")'
|
|
215
|
+
|
|
216
|
+
# API запити
|
|
217
|
+
npm run dev | jq 'select(.msg == "api request")'
|
|
218
|
+
|
|
219
|
+
# Повільні запити (>1s)
|
|
220
|
+
npm run dev | jq 'select(.request_time and (.request_time | tonumber) > 1.0)'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Ліцензія
|
|
224
|
+
|
|
225
|
+
MIT
|
|
226
|
+
|
|
227
|
+
## Підтримка
|
|
228
|
+
|
|
229
|
+
Для питань та багів: [GitHub Issues](https://github.com/hotelme/nuxt-logger/issues)
|
package/dist/module.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const kit = require('@nuxt/kit');
|
|
4
|
+
const url = require('url');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
8
|
+
const module$1 = kit.defineNuxtModule({
|
|
9
|
+
meta: {
|
|
10
|
+
name: "hotelme-nuxt-logger",
|
|
11
|
+
configKey: "logger",
|
|
12
|
+
compatibility: {
|
|
13
|
+
nuxt: "^3.0.0"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
defaults: {
|
|
17
|
+
enableRequestLogging: true
|
|
18
|
+
},
|
|
19
|
+
setup(options, nuxt) {
|
|
20
|
+
const moduleDir = typeof __dirname !== "undefined" ? __dirname : path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('module.cjs', document.baseURI).href))));
|
|
21
|
+
const resolver = kit.createResolver(moduleDir);
|
|
22
|
+
nuxt.options.runtimeConfig.public.projectName = options.projectName || process.env.NUXT_PUBLIC_PROJECT_NAME || "hotelme";
|
|
23
|
+
kit.addImportsDir(resolver.resolve("./runtime"));
|
|
24
|
+
if (options.enableRequestLogging) {
|
|
25
|
+
kit.addServerHandler({
|
|
26
|
+
handler: resolver.resolve("./runtime/middleware/logger.server"),
|
|
27
|
+
middleware: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = module$1;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface ModuleOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Назва проекту для логів
|
|
4
|
+
* @default process.env.NUXT_PUBLIC_PROJECT_NAME || 'hotelme'
|
|
5
|
+
*/
|
|
6
|
+
projectName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Увімкнути автоматичне логування запитів
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
enableRequestLogging?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const _default: any;
|
|
14
|
+
|
|
15
|
+
export { _default as default };
|
|
16
|
+
export type { ModuleOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface ModuleOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Назва проекту для логів
|
|
4
|
+
* @default process.env.NUXT_PUBLIC_PROJECT_NAME || 'hotelme'
|
|
5
|
+
*/
|
|
6
|
+
projectName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Увімкнути автоматичне логування запитів
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
enableRequestLogging?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const _default: any;
|
|
14
|
+
|
|
15
|
+
export { _default as default };
|
|
16
|
+
export type { ModuleOptions };
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface ModuleOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Назва проекту для логів
|
|
4
|
+
* @default process.env.NUXT_PUBLIC_PROJECT_NAME || 'hotelme'
|
|
5
|
+
*/
|
|
6
|
+
projectName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Увімкнути автоматичне логування запитів
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
enableRequestLogging?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const _default: any;
|
|
14
|
+
|
|
15
|
+
export { _default as default };
|
|
16
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// -- Unbuild CommonJS Shims --
|
|
8
|
+
import __cjs_url__ from 'url';
|
|
9
|
+
import __cjs_path__ from 'path';
|
|
10
|
+
import __cjs_mod__ from 'module';
|
|
11
|
+
const __filename = __cjs_url__.fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = __cjs_path__.dirname(__filename);
|
|
13
|
+
const require = __cjs_mod__.createRequire(import.meta.url);
|
|
14
|
+
const module = defineNuxtModule({
|
|
15
|
+
meta: {
|
|
16
|
+
name: "hotelme-nuxt-logger",
|
|
17
|
+
configKey: "logger",
|
|
18
|
+
compatibility: {
|
|
19
|
+
nuxt: "^3.0.0"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
defaults: {
|
|
23
|
+
enableRequestLogging: true
|
|
24
|
+
},
|
|
25
|
+
setup(options, nuxt) {
|
|
26
|
+
const moduleDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const resolver = createResolver(moduleDir);
|
|
28
|
+
nuxt.options.runtimeConfig.public.projectName = options.projectName || process.env.NUXT_PUBLIC_PROJECT_NAME || "hotelme";
|
|
29
|
+
addImportsDir(resolver.resolve("./runtime"));
|
|
30
|
+
if (options.enableRequestLogging) {
|
|
31
|
+
addServerHandler({
|
|
32
|
+
handler: resolver.resolve("./runtime/middleware/logger.server"),
|
|
33
|
+
middleware: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export { module as default };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard logger for Nuxt applications
|
|
3
|
+
*
|
|
4
|
+
* Виводить логи в stdout у JSON-форматі, сумісному з бекенд-логами.
|
|
5
|
+
*
|
|
6
|
+
* Обов'язкові поля в кожному лозі:
|
|
7
|
+
* - ts: ISO timestamp з timezone
|
|
8
|
+
* - level: рівень логування (Trace, Debug, Info, Warn, Error, Fatal)
|
|
9
|
+
* - reqid: унікальний ID запиту (з header X-Request-ID)
|
|
10
|
+
* - msg: текст повідомлення
|
|
11
|
+
* - project: назва проекту (з env або 'hotelme' за замовчуванням)
|
|
12
|
+
* - log_type: 'frontend' для розрізнення з бекенд-логами
|
|
13
|
+
* - pid: process ID
|
|
14
|
+
*
|
|
15
|
+
* Приклад виводу:
|
|
16
|
+
* {
|
|
17
|
+
* "msg": "api request",
|
|
18
|
+
* "level": "Info",
|
|
19
|
+
* "reqid": "aec7c27a231d1843",
|
|
20
|
+
* "project": "hotelme",
|
|
21
|
+
* "log_type": "frontend",
|
|
22
|
+
* "pid": 53,
|
|
23
|
+
* "ts": "2025-06-17T09:58:18.539733-0500",
|
|
24
|
+
* "stage": "after",
|
|
25
|
+
* "domain": "localhost:3000",
|
|
26
|
+
* "request": "/api/hotels",
|
|
27
|
+
* "request_time": "0.006",
|
|
28
|
+
* "method": "GET",
|
|
29
|
+
* "response": 200
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export declare enum LogLevel {
|
|
33
|
+
Trace = "Trace",
|
|
34
|
+
Debug = "Debug",
|
|
35
|
+
Info = "Info",
|
|
36
|
+
Warn = "Warn",
|
|
37
|
+
Error = "Error",
|
|
38
|
+
Fatal = "Fatal"
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Генерує унікальний ID запиту
|
|
42
|
+
* Використовується UUID v4 без дефісів для компактності
|
|
43
|
+
*/
|
|
44
|
+
export declare function getRequestId(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Основна функція логування
|
|
47
|
+
*
|
|
48
|
+
* @param level - рівень логування
|
|
49
|
+
* @param msg - текст повідомлення
|
|
50
|
+
* @param details - додаткові поля логу
|
|
51
|
+
*
|
|
52
|
+
* Важливі поля в details:
|
|
53
|
+
* - reqid: ID запиту (береться з X-Request-ID header)
|
|
54
|
+
* - request: URL запиту
|
|
55
|
+
* - request_time: час виконання запиту в секундах
|
|
56
|
+
* - domain: домен
|
|
57
|
+
* - method: HTTP метод
|
|
58
|
+
* - response: HTTP статус код
|
|
59
|
+
* - stage: етап обробки ('after' для завершених запитів)
|
|
60
|
+
*
|
|
61
|
+
* Всі інші поля з details додаються на кореневий рівень JSON
|
|
62
|
+
*/
|
|
63
|
+
export declare function log(level: LogLevel, msg: string, details?: Record<string, any>): void;
|
|
64
|
+
/**
|
|
65
|
+
* Зручні методи для різних рівнів логування
|
|
66
|
+
*
|
|
67
|
+
* Використання:
|
|
68
|
+
* logger.info('User logged in', { user_id: '123', reqid })
|
|
69
|
+
* logger.error('Database error', { error: err.message, reqid })
|
|
70
|
+
*/
|
|
71
|
+
export declare const logger: {
|
|
72
|
+
trace: (msg: string, details?: Record<string, any>) => void;
|
|
73
|
+
debug: (msg: string, details?: Record<string, any>) => void;
|
|
74
|
+
info: (msg: string, details?: Record<string, any>) => void;
|
|
75
|
+
warn: (msg: string, details?: Record<string, any>) => void;
|
|
76
|
+
error: (msg: string, details?: Record<string, any>) => void;
|
|
77
|
+
fatal: (msg: string, details?: Record<string, any>) => void;
|
|
78
|
+
};
|
|
79
|
+
export default logger;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
export var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
3
|
+
LogLevel2["Trace"] = "Trace";
|
|
4
|
+
LogLevel2["Debug"] = "Debug";
|
|
5
|
+
LogLevel2["Info"] = "Info";
|
|
6
|
+
LogLevel2["Warn"] = "Warn";
|
|
7
|
+
LogLevel2["Error"] = "Error";
|
|
8
|
+
LogLevel2["Fatal"] = "Fatal";
|
|
9
|
+
return LogLevel2;
|
|
10
|
+
})(LogLevel || {});
|
|
11
|
+
const PROJECT_NAME = process.env.NUXT_PUBLIC_PROJECT_NAME || "hotelme";
|
|
12
|
+
const PID = process.pid;
|
|
13
|
+
export function getRequestId() {
|
|
14
|
+
return uuidv4().replace(/-/g, "");
|
|
15
|
+
}
|
|
16
|
+
function formatTimestamp() {
|
|
17
|
+
const now = /* @__PURE__ */ new Date();
|
|
18
|
+
const isoString = now.toISOString();
|
|
19
|
+
const offset = -now.getTimezoneOffset();
|
|
20
|
+
const sign = offset >= 0 ? "+" : "-";
|
|
21
|
+
const absOffset = Math.abs(offset);
|
|
22
|
+
const hours = String(Math.floor(absOffset / 60)).padStart(2, "0");
|
|
23
|
+
const minutes = String(absOffset % 60).padStart(2, "0");
|
|
24
|
+
const timezone = `${sign}${hours}${minutes}`;
|
|
25
|
+
const timestampWithMicros = isoString.replace("Z", `${Math.floor(Math.random() * 1e3).toString().padStart(3, "0")}${timezone}`);
|
|
26
|
+
return timestampWithMicros;
|
|
27
|
+
}
|
|
28
|
+
export function log(level, msg, details) {
|
|
29
|
+
const reqid = details?.reqid || getRequestId();
|
|
30
|
+
const { request, request_time, domain, method, response, stage, ...otherDetails } = details || {};
|
|
31
|
+
const logEntry = {
|
|
32
|
+
msg,
|
|
33
|
+
level,
|
|
34
|
+
reqid,
|
|
35
|
+
project: PROJECT_NAME,
|
|
36
|
+
log_type: "frontend",
|
|
37
|
+
pid: PID,
|
|
38
|
+
ts: formatTimestamp(),
|
|
39
|
+
...stage && { stage },
|
|
40
|
+
...domain && { domain },
|
|
41
|
+
...request && { request },
|
|
42
|
+
...request_time && { request_time },
|
|
43
|
+
...method && { method },
|
|
44
|
+
...response !== void 0 && { response },
|
|
45
|
+
...otherDetails
|
|
46
|
+
};
|
|
47
|
+
console.log(JSON.stringify(logEntry));
|
|
48
|
+
}
|
|
49
|
+
export const logger = {
|
|
50
|
+
trace: (msg, details) => log("Trace" /* Trace */, msg, details),
|
|
51
|
+
debug: (msg, details) => log("Debug" /* Debug */, msg, details),
|
|
52
|
+
info: (msg, details) => log("Info" /* Info */, msg, details),
|
|
53
|
+
warn: (msg, details) => log("Warn" /* Warn */, msg, details),
|
|
54
|
+
error: (msg, details) => log("Error" /* Error */, msg, details),
|
|
55
|
+
fatal: (msg, details) => log("Fatal" /* Fatal */, msg, details)
|
|
56
|
+
};
|
|
57
|
+
export default logger;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare module 'h3' {
|
|
2
|
+
interface H3EventContext {
|
|
3
|
+
reqid?: string;
|
|
4
|
+
userId?: string;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Server middleware для логування запитів
|
|
9
|
+
*
|
|
10
|
+
* Виконує наступні функції:
|
|
11
|
+
*
|
|
12
|
+
* 1. Управління user_id:
|
|
13
|
+
* - Зчитує user_id з cookie або генерує новий UUID
|
|
14
|
+
* - Зберігає user_id в cookie на 1 рік
|
|
15
|
+
* - Використовується для відстеження користувачів між запитами
|
|
16
|
+
*
|
|
17
|
+
* 2. Отримання reqid:
|
|
18
|
+
* - Бере reqid з header X-Request-ID
|
|
19
|
+
* - Якщо header відсутній, генерує новий UUID
|
|
20
|
+
* - Зберігається в event.context для доступу в інших middleware та API handlers
|
|
21
|
+
*
|
|
22
|
+
* 3. Вимірювання часу виконання:
|
|
23
|
+
* - Засікає початок запиту через performance.now()
|
|
24
|
+
* - В кінці запиту (event 'finish') логує статистику
|
|
25
|
+
*
|
|
26
|
+
* 4. Логування завершення запиту:
|
|
27
|
+
* - Виводить лог з msg="api request" для API запитів
|
|
28
|
+
* - Виводить лог з msg="stats" для інших запитів
|
|
29
|
+
* - Включає: URL, метод, час виконання, статус-код, домен
|
|
30
|
+
* - Формат сумісний з бекенд-логами для єдиної системи моніторингу
|
|
31
|
+
*
|
|
32
|
+
* 5. Захист від дублювання:
|
|
33
|
+
* - Перевіряє event.context.reqid - якщо існує, middleware пропускається
|
|
34
|
+
* - Використовує _loggerAttached flag для запобігання множинним listener'ам
|
|
35
|
+
*
|
|
36
|
+
* 6. Error handling:
|
|
37
|
+
* - Всі помилки логуються але не блокують виконання запиту
|
|
38
|
+
* - Middleware ніколи не викидає exception
|
|
39
|
+
* - Fallback механізми для cookie операцій
|
|
40
|
+
*/
|
|
41
|
+
declare const _default: any;
|
|
42
|
+
export default _default;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { getCookie, setCookie } from "h3";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
if (event.context.reqid) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
let userId = getCookie(event, "user_id");
|
|
10
|
+
if (!userId) {
|
|
11
|
+
try {
|
|
12
|
+
userId = uuidv4().replace(/-/g, "");
|
|
13
|
+
setCookie(event, "user_id", userId, {
|
|
14
|
+
maxAge: 60 * 60 * 24 * 365,
|
|
15
|
+
httpOnly: true,
|
|
16
|
+
secure: process.env.NODE_ENV === "production",
|
|
17
|
+
sameSite: "strict"
|
|
18
|
+
});
|
|
19
|
+
} catch (cookieError) {
|
|
20
|
+
logger.warn("cookie_error", {
|
|
21
|
+
error: cookieError instanceof Error ? cookieError.message : String(cookieError)
|
|
22
|
+
});
|
|
23
|
+
userId = uuidv4().replace(/-/g, "");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
let reqid = event.node.req.headers["x-request-id"];
|
|
27
|
+
if (!reqid) {
|
|
28
|
+
reqid = uuidv4().replace(/-/g, "");
|
|
29
|
+
}
|
|
30
|
+
event.context.reqid = reqid;
|
|
31
|
+
event.context.userId = userId;
|
|
32
|
+
const url = getRequestURL(event);
|
|
33
|
+
const fullUrl = url.pathname + (url.search ? url.search : "");
|
|
34
|
+
const method = event.node.req.method || "UNKNOWN";
|
|
35
|
+
const startTime = performance.now();
|
|
36
|
+
const res = event.node.res;
|
|
37
|
+
if (!res._loggerAttached) {
|
|
38
|
+
;
|
|
39
|
+
res._loggerAttached = true;
|
|
40
|
+
res.once("finish", () => {
|
|
41
|
+
try {
|
|
42
|
+
const endTime = performance.now();
|
|
43
|
+
const requestTime = ((endTime - startTime) / 1e3).toFixed(3);
|
|
44
|
+
const statusCode = res.statusCode;
|
|
45
|
+
const isApiRequest = fullUrl.startsWith("/api/");
|
|
46
|
+
const msg = isApiRequest ? "api request" : "stats";
|
|
47
|
+
logger.info(msg, {
|
|
48
|
+
reqid,
|
|
49
|
+
stage: "after",
|
|
50
|
+
domain: event.node.req.headers.host,
|
|
51
|
+
request: fullUrl,
|
|
52
|
+
request_time: requestTime,
|
|
53
|
+
method,
|
|
54
|
+
response: statusCode,
|
|
55
|
+
user_id: userId
|
|
56
|
+
});
|
|
57
|
+
} catch (logError) {
|
|
58
|
+
logger.error("log_error", {
|
|
59
|
+
error: logError instanceof Error ? logError.message : String(logError),
|
|
60
|
+
request: fullUrl,
|
|
61
|
+
reqid
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error("middleware_error", {
|
|
68
|
+
error: error instanceof Error ? error.message : String(error)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
package/dist/types.d.mts
ADDED
package/dist/types.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hotelme-nuxt-logger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Стандартний logger для Nuxt проектів з JSON виводом сумісним з бекенд-логами",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nuxt",
|
|
7
|
+
"nuxt-module",
|
|
8
|
+
"logger",
|
|
9
|
+
"logging",
|
|
10
|
+
"json-logger"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/hotelme/nuxt-logger.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/types.d.ts",
|
|
21
|
+
"import": "./dist/module.mjs",
|
|
22
|
+
"require": "./dist/module.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/module.cjs",
|
|
26
|
+
"types": "./dist/types.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"prepack": "nuxt-module-build build",
|
|
32
|
+
"dev": "nuxi dev playground",
|
|
33
|
+
"dev:build": "nuxi build playground",
|
|
34
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
35
|
+
"release": "npm run prepack && npm publish",
|
|
36
|
+
"lint": "eslint .",
|
|
37
|
+
"test": "echo \"No tests yet\""
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@nuxt/kit": "^3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@nuxt/module-builder": "^0.8.0",
|
|
44
|
+
"@nuxt/schema": "^3.0.0",
|
|
45
|
+
"nuxt": "^3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"uuid": "^9.0.0 || ^10.0.0 || ^11.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|