@ecdt/server-common 1.2.1 → 1.5.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 +81 -0
- package/package.json +5 -1
- package/src/index.d.ts +113 -41
- package/src/index.js +2 -1
- package/src/services/redMetricsService.js +108 -0
- package/src/services/shutdownService.js +1 -5
package/README.md
CHANGED
|
@@ -79,6 +79,54 @@ app.use(expressCors({ origins: [...], extraHeaders: ["X-Custom-Header"] }));
|
|
|
79
79
|
app.use(expressCors({ origins: [...], headers: ["Content-Type"], methods: ["GET", "POST"] }));
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
### `setupRedMetrics(app, options?)`
|
|
83
|
+
|
|
84
|
+
Instrumenta o servidor com métricas **RED** (Rate, Errors, Duration) e expõe o endpoint `/metrics` no formato Prometheus, em uma linha. Todas as requisições passam a alimentar o histograma `http_request_duration_seconds{method, route, status_code}`.
|
|
85
|
+
|
|
86
|
+
> Requer `prom-client` instalado no microsserviço (é uma `peerDependency`).
|
|
87
|
+
|
|
88
|
+
| Opção | Tipo | Descrição |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `serviceName` | `string` | Nome do MS; vira o label `service` em todas as métricas. Fallback: env `SERVICE_NAME` > `npm_package_name` |
|
|
91
|
+
| `metricsPath` | `string` | Caminho do endpoint de métricas. Default: `/metrics` |
|
|
92
|
+
| `buckets` | `number[]` | Buckets de latência (segundos). Default: `[0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]` |
|
|
93
|
+
| `collectDefaultMetrics` | `boolean` | Coletar métricas default do Node (heap, event loop, GC...). Default: `true` |
|
|
94
|
+
| `registry` | `Registry` | Registry alternativo do prom-client. Default: registry global |
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
const express = require("express");
|
|
98
|
+
const { setupRedMetrics } = require("@ecdt/server-common");
|
|
99
|
+
|
|
100
|
+
const app = express();
|
|
101
|
+
|
|
102
|
+
// ANTES de montar as rotas:
|
|
103
|
+
setupRedMetrics(app, { serviceName: "dev-mkt-busca" });
|
|
104
|
+
|
|
105
|
+
app.get("/users/:id", (req, res) => res.json({ id: req.params.id }));
|
|
106
|
+
|
|
107
|
+
app.listen(3000);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
O label `route` usa o **template** da rota (`/users/:id`), nunca a URL crua — isso mantém a cardinalidade (e o custo de ingestão) sob controle.
|
|
111
|
+
|
|
112
|
+
#### Consultas (PromQL) habilitadas
|
|
113
|
+
|
|
114
|
+
```promql
|
|
115
|
+
# Latência p95 por endpoint
|
|
116
|
+
histogram_quantile(0.95, sum by (le, route)(rate(http_request_duration_seconds_bucket{service="dev-mkt-busca"}[5m])))
|
|
117
|
+
|
|
118
|
+
# Taxa de requisições por segundo (RPS)
|
|
119
|
+
sum(rate(http_request_duration_seconds_count{service="dev-mkt-busca"}[1m]))
|
|
120
|
+
|
|
121
|
+
# Erros 5xx por segundo
|
|
122
|
+
sum(rate(http_request_duration_seconds_count{service="dev-mkt-busca",status_code=~"5.."}[5m]))
|
|
123
|
+
|
|
124
|
+
# Endpoints que mais geram erro
|
|
125
|
+
topk(10, sum by (route)(rate(http_request_duration_seconds_count{service="dev-mkt-busca",status_code=~"5.."}[5m])))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Para wiring manual, a lib também exporta `redMetricsMiddleware(options?)` e `metricsHandler(registry?)`.
|
|
129
|
+
|
|
82
130
|
---
|
|
83
131
|
|
|
84
132
|
## Instalação
|
|
@@ -107,3 +155,36 @@ app.listen(3000);
|
|
|
107
155
|
```
|
|
108
156
|
|
|
109
157
|
---
|
|
158
|
+
|
|
159
|
+
## TypeScript
|
|
160
|
+
|
|
161
|
+
O pacote publica os tipos em `src/index.d.ts` (declarado em `types` no `package.json`), tipados com `express` e `http`. **Não é necessário declarar `declare module '@ecdt/server-common'` na aplicação** — se você tem um arquivo desses em `src/@types/`, pode apagar: uma declaração ambiente local sombreia os tipos do pacote e passa a mentir em silêncio se as assinaturas divergirem.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import express from "express";
|
|
165
|
+
import http from "http";
|
|
166
|
+
import { expressCommonMiddlewares, expressCors, setupRedMetrics, setupGracefulShutdown } from "@ecdt/server-common";
|
|
167
|
+
|
|
168
|
+
const app = express();
|
|
169
|
+
|
|
170
|
+
app.use(...expressCommonMiddlewares()); // RequestHandler[]
|
|
171
|
+
app.use(expressCors({ econodataOrigins: true })); // RequestHandler
|
|
172
|
+
setupRedMetrics(app, { serviceName: "meu-ms" });
|
|
173
|
+
|
|
174
|
+
const server = http.createServer(app);
|
|
175
|
+
server.listen(3000);
|
|
176
|
+
setupGracefulShutdown(server, { onShutdown: () => pool.end() });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
As interfaces de options são exportadas para reuso: `ExpressCommonMiddlewaresOptions`, `ExpressCorsOptions` (e sua base `ExpressCorsOptionsBase`), `GracefulShutdownOptions`, `RedMetricsOptions` e `MetricsRegistry`.
|
|
180
|
+
|
|
181
|
+
`ExpressCorsOptions` é uma união que exige `origins` e/ou `econodataOrigins: true` — sem nenhum dos dois o `expressCors` lança no boot, então o erro aparece já em compilação:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
expressCors({ econodataOrigins: true }); // ok
|
|
185
|
+
expressCors({ methods: ["GET"] }); // erro TS2345 — lançaria no boot
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`prom-client` aparece nos tipos como a interface estrutural `MetricsRegistry`, e não como `import` do pacote — assim quem não usa métricas compila sem tê-lo instalado.
|
|
189
|
+
|
|
190
|
+
---
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecdt/server-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Conjunto de ferramentas e configurações comuns nos servidores da Econodata",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
9
|
},
|
|
@@ -11,5 +12,8 @@
|
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"body-parser": "^2.2.2",
|
|
13
14
|
"cors": "^2.8.6"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"prom-client": "^15.0.0"
|
|
14
18
|
}
|
|
15
19
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,41 +1,113 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
1
|
+
import type { Express, RequestHandler } from 'express';
|
|
2
|
+
import type { Server } from 'http';
|
|
3
|
+
|
|
4
|
+
export interface ExpressCommonMiddlewaresOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Nome(s) do cookie de sessão, em ordem de preferência.
|
|
7
|
+
* Quando omitido, o nome é resolvido por requisição a partir da origem (origin > referer > host):
|
|
8
|
+
* origens com subdomínio "hml" usam `hml-ecdt_token_site`, as demais usam `ecdt_token_site`.
|
|
9
|
+
*/
|
|
10
|
+
cookieName?: string | string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Aplica bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}) e
|
|
15
|
+
* devMktTokenSanitaze() no middleware do express.
|
|
16
|
+
*
|
|
17
|
+
* Uso: app.use(...expressCommonMiddlewares())
|
|
18
|
+
*/
|
|
19
|
+
export declare function expressCommonMiddlewares(
|
|
20
|
+
options?: ExpressCommonMiddlewaresOptions
|
|
21
|
+
): RequestHandler[];
|
|
22
|
+
|
|
23
|
+
export interface ExpressCorsOptionsBase {
|
|
24
|
+
/** Origins permitidas (ex: ["https://app.econodata.com.br"]). */
|
|
25
|
+
origins?: string[];
|
|
26
|
+
/** Permite todas as origins https do domínio econodata.com.br (qualquer subdomínio). */
|
|
27
|
+
econodataOrigins?: boolean;
|
|
28
|
+
/** Headers adicionais além dos padrões (Authorization, Content-Type, X-Requested-With). */
|
|
29
|
+
extraHeaders?: string[];
|
|
30
|
+
/** Lista exata de headers permitidos — substitui os padrões. Não combinar com extraHeaders. */
|
|
31
|
+
headers?: string[];
|
|
32
|
+
/** Lista exata de métodos permitidos. Default: todos (GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS). */
|
|
33
|
+
methods?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Exige `origins` e/ou `econodataOrigins: true`: sem nenhum dos dois o expressCors lança no boot,
|
|
38
|
+
* então a união recusa em compilação o que o runtime já recusa.
|
|
39
|
+
*/
|
|
40
|
+
export type ExpressCorsOptions =
|
|
41
|
+
| (ExpressCorsOptionsBase & { origins: string[] })
|
|
42
|
+
| (ExpressCorsOptionsBase & { econodataOrigins: true });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cria o middleware de CORS padrão dos servidores Econodata.
|
|
46
|
+
* Credentials sempre habilitado; origins não listadas não recebem headers CORS.
|
|
47
|
+
*
|
|
48
|
+
* Uso: app.use(expressCors({ origins: ["https://app.econodata.com.br"] }))
|
|
49
|
+
*/
|
|
50
|
+
export declare function expressCors(options: ExpressCorsOptions): RequestHandler;
|
|
51
|
+
|
|
52
|
+
export interface GracefulShutdownOptions {
|
|
53
|
+
/** Cleanup extra (fechar pools, Redis, etc.) executado após o server fechar. */
|
|
54
|
+
onShutdown?: () => void | Promise<void>;
|
|
55
|
+
/** Tempo máximo do graceful antes de forçar a saída. Default: 10000 (grace de 40s − 20s de preStop). */
|
|
56
|
+
failsafeTimeoutMs?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Registra o graceful shutdown padrão dos microsserviços em Kubernetes: em SIGTERM/SIGINT fecha o
|
|
61
|
+
* server, drena as conexões em voo, roda o onShutdown e encerra o processo.
|
|
62
|
+
*
|
|
63
|
+
* Uso: setupGracefulShutdown(server, { onShutdown: () => pool.end() })
|
|
64
|
+
*/
|
|
65
|
+
export declare function setupGracefulShutdown(
|
|
66
|
+
server: Server,
|
|
67
|
+
options?: GracefulShutdownOptions
|
|
68
|
+
): void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Registry do prom-client, descrito estruturalmente para não acoplar a resolução destes tipos ao
|
|
72
|
+
* pacote: quem não usa métricas continua compilando sem `prom-client` instalado.
|
|
73
|
+
*/
|
|
74
|
+
export interface MetricsRegistry {
|
|
75
|
+
contentType: string;
|
|
76
|
+
metrics(): Promise<string>;
|
|
77
|
+
setDefaultLabels(labels: Record<string, string>): void;
|
|
78
|
+
getSingleMetric(name: string): unknown;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface RedMetricsOptions {
|
|
82
|
+
/** Nome do microsserviço. Vira o label `service` em todas as métricas. Fallback: env SERVICE_NAME > npm_package_name. */
|
|
83
|
+
serviceName?: string;
|
|
84
|
+
/** Caminho do endpoint de métricas. Default: '/metrics'. */
|
|
85
|
+
metricsPath?: string;
|
|
86
|
+
/** Buckets do histograma de latência, em segundos. */
|
|
87
|
+
buckets?: number[];
|
|
88
|
+
/** Coletar métricas default do Node (heap, event loop, GC...). Default: true. */
|
|
89
|
+
collectDefaultMetrics?: boolean;
|
|
90
|
+
/** Registry alternativo do prom-client. Default: registry global. */
|
|
91
|
+
registry?: MetricsRegistry;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Configura as métricas RED (Rate, Errors, Duration) no app express: registra o middleware de
|
|
96
|
+
* medição (antes das rotas) e o endpoint /metrics.
|
|
97
|
+
*
|
|
98
|
+
* Alimenta o histograma `http_request_duration_seconds{method,route,status_code}`, que habilita
|
|
99
|
+
* latência por endpoint, latência total, RPS, erros e top endpoints com erro.
|
|
100
|
+
*
|
|
101
|
+
* Requer `prom-client` instalado no microsserviço (peerDependency).
|
|
102
|
+
*
|
|
103
|
+
* Uso: setupRedMetrics(app, { serviceName: "dev-mkt-busca" })
|
|
104
|
+
*/
|
|
105
|
+
export declare function setupRedMetrics(app: Express, options?: RedMetricsOptions): void;
|
|
106
|
+
|
|
107
|
+
/** Middleware do express que alimenta o histograma `http_request_duration_seconds`. */
|
|
108
|
+
export declare function redMetricsMiddleware(
|
|
109
|
+
options?: Pick<RedMetricsOptions, 'buckets' | 'registry'>
|
|
110
|
+
): RequestHandler;
|
|
111
|
+
|
|
112
|
+
/** Handler do endpoint de métricas (exposição no formato Prometheus). */
|
|
113
|
+
export declare function metricsHandler(registry?: MetricsRegistry): RequestHandler;
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const bodyParser = require("body-parser");
|
|
|
2
2
|
const { devMktTokenSanitaze } = require("./services/tokenService.js");
|
|
3
3
|
const { expressCors } = require("./services/corsService.js");
|
|
4
4
|
const { setupGracefulShutdown } = require("./services/shutdownService.js");
|
|
5
|
+
const { setupRedMetrics, redMetricsMiddleware, metricsHandler } = require("./services/redMetricsService.js");
|
|
5
6
|
|
|
6
7
|
function expressCommonMiddlewares({ cookieName } = {}){
|
|
7
8
|
return [bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}), devMktTokenSanitaze({ cookieName })];
|
|
@@ -10,4 +11,4 @@ function expressCommonMiddlewares({ cookieName } = {}){
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
module.exports = { expressCommonMiddlewares, expressCors, setupGracefulShutdown }
|
|
14
|
+
module.exports = { expressCommonMiddlewares, expressCors, setupGracefulShutdown, setupRedMetrics, redMetricsMiddleware, metricsHandler }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const client = require("prom-client");
|
|
2
|
+
|
|
3
|
+
const METRIC_NAME = "http_request_duration_seconds";
|
|
4
|
+
const DEFAULT_BUCKETS = [0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* get-or-create do histograma. Reaproveita a métrica se já registrada, evitando
|
|
8
|
+
* o erro "A metric with the name ... has already been registered" em import duplo
|
|
9
|
+
* ou múltiplas chamadas de setup.
|
|
10
|
+
* @param {import("prom-client").Registry} registry
|
|
11
|
+
* @param {number[]} buckets
|
|
12
|
+
*/
|
|
13
|
+
function getHistogram(registry, buckets) {
|
|
14
|
+
const existing = registry.getSingleMetric(METRIC_NAME);
|
|
15
|
+
if (existing) return existing;
|
|
16
|
+
return new client.Histogram({
|
|
17
|
+
name: METRIC_NAME,
|
|
18
|
+
help: "Duração das requisições HTTP, em segundos",
|
|
19
|
+
labelNames: ["method", "route", "status_code"],
|
|
20
|
+
buckets,
|
|
21
|
+
registers: [registry],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extrai o TEMPLATE da rota (ex.: "/users/:id"), com o prefixo de routers aninhados.
|
|
27
|
+
* Nunca usa a URL crua — evita explosão de cardinalidade (e custo de ingestão no GMP).
|
|
28
|
+
*/
|
|
29
|
+
function routeTemplate(req) {
|
|
30
|
+
const path = req.route && req.route.path;
|
|
31
|
+
if (!path) return "unmatched";
|
|
32
|
+
const p = Array.isArray(path) ? path[0] : path;
|
|
33
|
+
return `${req.baseUrl || ""}${p}` || "unmatched";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Middleware do express que mede cada requisição e alimenta o histograma RED
|
|
38
|
+
* (http_request_duration_seconds) com os labels method, route e status_code.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} [options]
|
|
41
|
+
* @param {number[]} [options.buckets] - buckets de latência em segundos
|
|
42
|
+
* @param {import("prom-client").Registry} [options.registry] - registry (default: global do prom-client)
|
|
43
|
+
* @returns middleware do express
|
|
44
|
+
*/
|
|
45
|
+
function redMetricsMiddleware({ buckets, registry } = {}) {
|
|
46
|
+
const reg = registry || client.register;
|
|
47
|
+
const histogram = getHistogram(reg, buckets || DEFAULT_BUCKETS);
|
|
48
|
+
return function (req, res, next) {
|
|
49
|
+
const stop = histogram.startTimer();
|
|
50
|
+
// 'finish' dispara após a rota resolver -> req.route já está preenchido aqui.
|
|
51
|
+
res.on("finish", () =>
|
|
52
|
+
stop({
|
|
53
|
+
method: req.method,
|
|
54
|
+
route: routeTemplate(req),
|
|
55
|
+
status_code: String(res.statusCode),
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handler do endpoint de métricas (exposição no formato Prometheus).
|
|
64
|
+
* @param {import("prom-client").Registry} [registry] - default: global do prom-client
|
|
65
|
+
* @returns handler do express
|
|
66
|
+
*/
|
|
67
|
+
function metricsHandler(registry = client.register) {
|
|
68
|
+
return async function (_req, res) {
|
|
69
|
+
res.set("Content-Type", registry.contentType);
|
|
70
|
+
res.end(await registry.metrics());
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Configura as métricas RED no app express em uma linha. Registra o middleware
|
|
76
|
+
* (aplique ANTES das rotas) e o endpoint /metrics.
|
|
77
|
+
*
|
|
78
|
+
* Uso: setupRedMetrics(app, { serviceName: "dev-mkt-busca" })
|
|
79
|
+
*
|
|
80
|
+
* @param {object} app - instância do express
|
|
81
|
+
* @param {object} [options]
|
|
82
|
+
* @param {string} [options.serviceName] - nome do MS; vira o label `service` em todas as métricas.
|
|
83
|
+
* Fallback: env SERVICE_NAME > npm_package_name.
|
|
84
|
+
* @param {string} [options.metricsPath] - caminho do endpoint de métricas (default: "/metrics")
|
|
85
|
+
* @param {number[]} [options.buckets] - buckets de latência em segundos
|
|
86
|
+
* @param {boolean} [options.collectDefaultMetrics] - coletar métricas default do Node
|
|
87
|
+
* (heap, event loop, GC...). Default: true.
|
|
88
|
+
* @param {import("prom-client").Registry} [options.registry] - registry alternativo (default: global)
|
|
89
|
+
*/
|
|
90
|
+
function setupRedMetrics(app, options = {}) {
|
|
91
|
+
const registry = options.registry || client.register;
|
|
92
|
+
const serviceName =
|
|
93
|
+
options.serviceName || process.env.SERVICE_NAME || process.env.npm_package_name;
|
|
94
|
+
if (serviceName) registry.setDefaultLabels({ service: serviceName });
|
|
95
|
+
|
|
96
|
+
// guard: collectDefaultMetrics chamado 2x joga "already registered".
|
|
97
|
+
if (
|
|
98
|
+
options.collectDefaultMetrics !== false &&
|
|
99
|
+
!registry.getSingleMetric("process_cpu_user_seconds_total")
|
|
100
|
+
) {
|
|
101
|
+
client.collectDefaultMetrics({ register: registry });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
app.use(redMetricsMiddleware({ buckets: options.buckets, registry }));
|
|
105
|
+
app.get(options.metricsPath || "/metrics", metricsHandler(registry));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { setupRedMetrics, redMetricsMiddleware, metricsHandler };
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Graceful shutdown padrão dos microsserviços da Econodata em Kubernetes.
|
|
3
|
-
* Extraído do dev-mkt-busca: o preStop (20s) já tirou o pod do EndpointSlice
|
|
4
|
-
* antes do SIGTERM, então aqui só drenamos as conexões em voo e encerramos o
|
|
5
|
-
* processo antes do SIGKILL.
|
|
6
|
-
*
|
|
7
3
|
* @param {import('http').Server} server - servidor retornado por http.createServer(app).listen(...) ou app.listen(...)
|
|
8
4
|
* @param {object} [options]
|
|
9
5
|
* @param {() => (Promise<void>|void)} [options.onShutdown] - cleanup extra (fechar pools, Redis, etc.) executado após o server fechar
|
|
@@ -44,7 +40,7 @@ function setupGracefulShutdown(server, { onShutdown, failsafeTimeoutMs = 10 * 10
|
|
|
44
40
|
// fecha conexões keep-alive ociosas, senão um keepAliveTimeout alto segura o close()
|
|
45
41
|
server.closeIdleConnections();
|
|
46
42
|
|
|
47
|
-
// failsafe antes do SIGKILL
|
|
43
|
+
// failsafe antes do SIGKILL
|
|
48
44
|
setTimeout(() => {
|
|
49
45
|
console.error('[shutdown] Timeout no graceful shutdown, forçando saída');
|
|
50
46
|
process.exit(1);
|