@ecdt/logger 1.0.0 → 1.0.2

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 ADDED
@@ -0,0 +1,12 @@
1
+ ### Fiz uma atualização no código e agora?
2
+ 1. Verificar as mudanças que subirão
3
+ ``npm pack``
4
+
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
+ ```
@@ -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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ecdt/logger",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Logger padronização de microsserviços da Econodata",
5
- "main": "src/index.js",
5
+ "type": "commonjs",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",
8
8
  "example:express": "node examples/express.js"
@@ -0,0 +1,5 @@
1
+ const formatsSupported = ['json', 'string']
2
+ const loggersSupported = ['morgan']
3
+
4
+ module.exports = { formatsSupported, loggersSupported }
5
+
package/src/index.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ declare module '@ecdt/logger' {
2
+ export type LoggerOptions = {
3
+ format?: string;
4
+ extraFields?: Record<string, unknown>;
5
+ includeStackErrors5xx?: boolean;
6
+ };
7
+
8
+ export function createLogger(
9
+ options?: LoggerOptions
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;
18
+ }
package/src/index.js CHANGED
@@ -1,41 +1,33 @@
1
- var { formatsSupported } = require('./constants')
2
- var morgan = require('morgan')
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
- return morgan((tokens, req, res) => {
6
- return format(tokens, req, res, options);
6
+ const logger = options.logger ?? 'morgan'
7
7
 
8
- });
9
- }
10
-
11
- function format(tokens, req, res, options = {}){
12
- if(!options || !options.format) options.format == 'json'
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 addPayload(log, method, req) {
35
- switch (method) {
36
- case "POST":
37
- return req.body || {};
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
@@ -1,3 +0,0 @@
1
- const formatsSupported = ['json', 'string']
2
-
3
- module.exports = { formatsSupported }