@ecdt/logger 1.0.1 → 1.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.dev.md +11 -3
- package/README.md +83 -0
- package/examples/express.js +44 -4
- package/package.json +2 -1
- package/src/constants.cjs +5 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +23 -31
- package/src/services/errorCapture.js +0 -0
- package/src/services/logFormatter.js +73 -0
- package/src/services/payload.js +16 -0
- package/src/constants.js +0 -3
package/README.dev.md
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
### Fiz uma atualização no código e agora?
|
|
2
|
+
1. Verificar as mudanças que subirão
|
|
3
|
+
``npm pack``
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
npm version patch
|
|
5
|
+
2. Atualize a versão
|
|
6
|
+
``npm version patch``
|
|
7
|
+
|
|
8
|
+
3. Publique na organização da econodata
|
|
9
|
+
``npm publish --access=public``
|
|
10
|
+
|
|
11
|
+
4. Atualize o cliente (Ms que importa a lib)
|
|
12
|
+
``npm install @ecdt/logger@latest``
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
### @ecdt/logger
|
|
2
|
+
|
|
3
|
+
Middleware de logging para **Express** (baseado em **morgan**) com saída em **JSON** ou **string**, com suporte a campos extras (`extraFields`) e captura opcional de **stack trace** em respostas **5xx**.
|
|
4
|
+
|
|
5
|
+
### Instalação
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @ecdt/logger
|
|
9
|
+
```
|
|
10
|
+
### Uso (Express)
|
|
11
|
+
|
|
12
|
+
#### Logger padrão (JSON)
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const express = require('express');
|
|
16
|
+
const { createLogger, createErrorCapture } = require('@ecdt/logger');
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
app.use(express.json());
|
|
20
|
+
|
|
21
|
+
// 1) Logger (no começo)
|
|
22
|
+
app.use(createLogger());
|
|
23
|
+
|
|
24
|
+
app.get('/', (_req, res) => res.status(200).send('ok'));
|
|
25
|
+
|
|
26
|
+
// 2) Error handler (sempre por último)
|
|
27
|
+
app.use(createErrorCapture());
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Logger com opções
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
app.use(
|
|
34
|
+
createLogger({
|
|
35
|
+
format: 'json', // 'json' | 'string'
|
|
36
|
+
extraFields: {
|
|
37
|
+
service: 'minha-api',
|
|
38
|
+
env: process.env.NODE_ENV,
|
|
39
|
+
},
|
|
40
|
+
includeStackErrors5xx: true, // default: true
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Captura de stack em 5xx
|
|
46
|
+
|
|
47
|
+
- Para o logger incluir `error.stack`, é necessário que **`res.error` esteja preenchido**.
|
|
48
|
+
- A forma recomendada é usar o middleware `createErrorCapture()` **por último**, pois ele seta `res.error` e responde `500`.
|
|
49
|
+
|
|
50
|
+
Exemplo:
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
app.get('/throw-error', (_req, _res) => {
|
|
54
|
+
// erro síncrono: o Express encaminha para o error handler
|
|
55
|
+
throw new Error('boom');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
app.use(createErrorCapture());
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Se você apenas fizer `res.status(500).send(...)` em uma rota, o status será 500, mas **não haverá stack** (porque não existe erro capturado em `res.error`).
|
|
62
|
+
|
|
63
|
+
### Formatos de saída
|
|
64
|
+
|
|
65
|
+
- **`format: 'json'` (default)**: log em JSON com `method`, `url`, `status`, `responseTime`, `payload` e `extraFields`.
|
|
66
|
+
- **`format: 'string'`**: log em texto (campos extras são concatenados).
|
|
67
|
+
|
|
68
|
+
### Opções (`createLogger(options)`)
|
|
69
|
+
|
|
70
|
+
- **`format?: 'json' | 'string' | string`**: formato do log (default: `'json'`).
|
|
71
|
+
- **`extraFields?: Record<string, unknown>`**: campos extras para serem adicionados ao log.
|
|
72
|
+
- **`includeStackErrors5xx?: boolean`**: inclui stack de erro quando `status >= 500` e `res.error.stack` existir (default: `true`).
|
|
73
|
+
|
|
74
|
+
### TypeScript
|
|
75
|
+
|
|
76
|
+
O pacote expõe definições em `index.d.ts`. Exemplo:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createLogger, createErrorCapture } from '@ecdt/logger';
|
|
80
|
+
|
|
81
|
+
app.use(createLogger({ format: 'json' }));
|
|
82
|
+
app.use(createErrorCapture());
|
|
83
|
+
```
|
package/examples/express.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const express = require("express");
|
|
2
|
-
const { createLogger } = require("../src");
|
|
2
|
+
const { createLogger, createErrorCapture } = require("../src");
|
|
3
3
|
|
|
4
4
|
const app = express();
|
|
5
|
-
|
|
5
|
+
app.use(express.json());
|
|
6
6
|
app.use(
|
|
7
7
|
createLogger({
|
|
8
8
|
format: 'json',
|
|
@@ -12,16 +12,56 @@ app.use(
|
|
|
12
12
|
})
|
|
13
13
|
);
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
|
|
15
17
|
app.get("/health", (_req, res) => {
|
|
16
18
|
res.status(200).json({ ok: true });
|
|
17
19
|
});
|
|
18
20
|
|
|
21
|
+
|
|
22
|
+
// Examples status 200
|
|
19
23
|
app.get("/", (_req, res) => {
|
|
20
24
|
res.status(200).send("hello");
|
|
21
25
|
});
|
|
26
|
+
app.post("/post", (_req, res) => {
|
|
27
|
+
res.status(200).send("posted");
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
app.patch("/patch", (_req, res) => {
|
|
31
|
+
res.status(200).send("patched");
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
app.put("/put", (_req, res) => {
|
|
36
|
+
res.status(200).send("putted");
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Examples status 500
|
|
40
|
+
app.get("/get-error", (_req, res) => {
|
|
41
|
+
res.status(500).send("get error example");
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
app.post("/post-error", (_req, res) => {
|
|
45
|
+
res.status(500).send("post error example");
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
app.patch("/patch-error", (_req, res) => {
|
|
49
|
+
res.status(500).send("error patch example");
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
app.put("/put-error", (_req, res) => {
|
|
53
|
+
res.status(500).send("error put example");
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Stack log
|
|
57
|
+
app.get("/throw-error", (_req, res) => {
|
|
58
|
+
throw new Error('throw error example');
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Error handler (sempre por último, após as rotas)
|
|
62
|
+
app.use(createErrorCapture());
|
|
22
63
|
|
|
23
64
|
const port = Number(process.env.PORT || 7878);
|
|
24
65
|
app.listen(port, () => {
|
|
25
66
|
console.log(`Example server listening on http://localhost:${port}`);
|
|
26
|
-
});
|
|
27
|
-
|
|
67
|
+
});
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -2,9 +2,17 @@ declare module '@ecdt/logger' {
|
|
|
2
2
|
export type LoggerOptions = {
|
|
3
3
|
format?: string;
|
|
4
4
|
extraFields?: Record<string, unknown>;
|
|
5
|
+
includeStackErrors5xx?: boolean;
|
|
5
6
|
};
|
|
6
7
|
|
|
7
8
|
export function createLogger(
|
|
8
9
|
options?: LoggerOptions
|
|
9
10
|
): (req: unknown, res: unknown, next: unknown) => void;
|
|
11
|
+
|
|
12
|
+
export function createErrorCapture(): (
|
|
13
|
+
err: unknown,
|
|
14
|
+
req: unknown,
|
|
15
|
+
res: { locals?: Record<string, unknown> } & Record<string, unknown>,
|
|
16
|
+
next: (err: unknown) => void
|
|
17
|
+
) => void;
|
|
10
18
|
}
|
package/src/index.js
CHANGED
|
@@ -1,41 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const morgan = require('morgan')
|
|
2
|
+
const logFormatter = require('./services/logFormatter');
|
|
3
|
+
const { loggersSupported } = require('./constants.cjs');
|
|
3
4
|
|
|
4
5
|
function createLogger(options = {}) {
|
|
5
|
-
|
|
6
|
-
return format(tokens, req, res, options);
|
|
6
|
+
const logger = options.logger ?? 'morgan'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const format = (options.format).toLowerCase()
|
|
15
|
-
if(!formatsSupported.includes(format)){
|
|
16
|
-
return `*!* Formato informado "[${format}]" não é suportado.\n*!*Formatos suportados: ${formatsSupported.join(' , ')} `
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const payload = addPayload(tokens.method(req,res), req)
|
|
20
|
-
|
|
21
|
-
switch (format) {
|
|
22
|
-
case 'json':
|
|
23
|
-
return JSON.stringify({
|
|
24
|
-
method: tokens.method(req, res),
|
|
25
|
-
url: tokens.url(req, res),
|
|
26
|
-
status: tokens.status(req, res),
|
|
27
|
-
responseTime: tokens['response-time'](req, res) + ' ms',
|
|
28
|
-
payload: payload,
|
|
29
|
-
...options.extraFields
|
|
8
|
+
switch (logger) {
|
|
9
|
+
case 'morgan': {
|
|
10
|
+
return morgan((tokens, req, res) => {
|
|
11
|
+
const log = logFormatter.formatLog(tokens, req, res, options);
|
|
12
|
+
return log;
|
|
30
13
|
});
|
|
14
|
+
}
|
|
15
|
+
default: {
|
|
16
|
+
console.warn(`Logger não suportado. Escolha uma das opções: ${loggersSupported}`);
|
|
17
|
+
// Evita quebrar o app com `app.use(null)`
|
|
18
|
+
return (_req, _res, next) => next();
|
|
19
|
+
}
|
|
31
20
|
}
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
function createErrorCapture() {
|
|
24
|
+
return function errorCapture(err, req, res, next) {
|
|
25
|
+
res.error = {
|
|
26
|
+
message: err?.message,
|
|
27
|
+
stack: err?.stack
|
|
38
28
|
}
|
|
29
|
+
res.status(500).json({ error: 'Erro interno' });
|
|
39
30
|
}
|
|
31
|
+
}
|
|
40
32
|
|
|
41
|
-
module.exports = { createLogger };
|
|
33
|
+
module.exports = { createLogger, createErrorCapture };
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { formatsSupported } = require('../constants.cjs');
|
|
2
|
+
const payload = require('./payload');
|
|
3
|
+
function formatLog(tokens, req, res, options = {}){
|
|
4
|
+
// Normaliza options sem mutar o objeto original (evita "vazar" error stack entre requests)
|
|
5
|
+
const normalized = normalizeOptions(options)
|
|
6
|
+
|
|
7
|
+
const format = (normalized.format).toLowerCase()
|
|
8
|
+
if(!formatsSupported.includes(format)){
|
|
9
|
+
return `*!* Formato informado "[${format}]" não é suportado.\n*!*Formatos suportados: ${formatsSupported.join(' , ')} `
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const _method = tokens.method(req, res);
|
|
13
|
+
const _url = tokens.url(req, res);
|
|
14
|
+
const _status = tokens.status(req, res);
|
|
15
|
+
const _responseTime = tokens['response-time'](req, res) + ' ms'
|
|
16
|
+
const _payload = payload.addPayload(req);
|
|
17
|
+
|
|
18
|
+
// `extraFields` é clonado por request, então podemos anexar info do erro com segurança
|
|
19
|
+
if(_status >= 500 && res.error && res.error.stack && normalized.includeStackErrors5xx) {
|
|
20
|
+
normalized.extraFields.error = {
|
|
21
|
+
message: res.error.message ?? '',
|
|
22
|
+
stack: res.error.stack
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
switch (format) {
|
|
27
|
+
case 'json':
|
|
28
|
+
return JSON.stringify({
|
|
29
|
+
method: _method,
|
|
30
|
+
url: _url,
|
|
31
|
+
status: _status,
|
|
32
|
+
responseTime: _responseTime,
|
|
33
|
+
payload: _payload,
|
|
34
|
+
...normalized.extraFields
|
|
35
|
+
});
|
|
36
|
+
case 'string':
|
|
37
|
+
const extraFieldsToString = formatExtraFields(normalized.extraFields)
|
|
38
|
+
return `${_method} - ${_status} - ${_responseTime} - ${_payload} - ${extraFieldsToString}`;;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatExtraFields(extraFields = {}){
|
|
43
|
+
|
|
44
|
+
const formatValues = (val) => {
|
|
45
|
+
switch(typeof(val)){
|
|
46
|
+
case 'object':
|
|
47
|
+
return Array.isArray(val) ? `[${val}]` : JSON.stringify(val);
|
|
48
|
+
default:
|
|
49
|
+
return val;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let string = ''
|
|
54
|
+
for(let val of Object.values(extraFields)){
|
|
55
|
+
val = formatValues(val);
|
|
56
|
+
string += `${val} - `
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
string = string.trim();
|
|
60
|
+
|
|
61
|
+
return string.slice(0, string.length-1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeOptions(options) {
|
|
65
|
+
const input = options ?? {};
|
|
66
|
+
return {
|
|
67
|
+
extraFields: { ...(input.extraFields ?? {}) },
|
|
68
|
+
format: input.format ?? 'json',
|
|
69
|
+
includeStackErrors5xx: input.includeStackErrors5xx ?? true
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { formatLog }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function addPayload(req){
|
|
2
|
+
switch(req.method){
|
|
3
|
+
case 'GET':
|
|
4
|
+
return '';
|
|
5
|
+
case 'POST':
|
|
6
|
+
case 'PUT':
|
|
7
|
+
case 'PATCH':
|
|
8
|
+
case 'DELETE':
|
|
9
|
+
if(req.body) return JSON.stringify(req.body);
|
|
10
|
+
return null;
|
|
11
|
+
default:
|
|
12
|
+
return `Request payload for ${req.method} method not implemented`;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = { addPayload }
|
package/src/constants.js
DELETED