azify-logger 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.md +295 -0
- package/index.js +114 -0
- package/middleware-express.js +277 -0
- package/middleware-restify.js +280 -0
- package/package.json +47 -0
- package/register-otel.js +39 -0
- package/register-restify.js +203 -0
- package/server.js +229 -0
- package/store.js +42 -0
- package/streams/bunyan.js +60 -0
- package/streams/pino.js +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# 🚀 Azify Logger - Logging Centralizado
|
|
2
|
+
|
|
3
|
+
Sistema de logging centralizado com OpenTelemetry e OpenSearch para múltiplas aplicações.
|
|
4
|
+
|
|
5
|
+
## 📦 Instalação
|
|
6
|
+
|
|
7
|
+
Na sua aplicação, adicione ao `package.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"azify-logger": "latest"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Ou via npm:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install azify-logger
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ⚡ Utilização
|
|
24
|
+
|
|
25
|
+
### Para aplicações Restify:
|
|
26
|
+
|
|
27
|
+
**1. No arquivo de inicialização:**
|
|
28
|
+
```javascript
|
|
29
|
+
#!/usr/bin/env node
|
|
30
|
+
require('azify-logger/register-otel');
|
|
31
|
+
// Resto do código...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**2. No servidor Restify:**
|
|
35
|
+
```javascript
|
|
36
|
+
import { middleware as azifyMiddleware } from 'azify-logger'
|
|
37
|
+
|
|
38
|
+
const server = restify.createServer()
|
|
39
|
+
server.use(azifyMiddleware.restify())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**3. Variável de ambiente:**
|
|
43
|
+
```bash
|
|
44
|
+
APP_NAME=nome-app
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**PRONTO! 🎉**
|
|
48
|
+
|
|
49
|
+
### Para aplicações Express:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
require('azify-logger')
|
|
53
|
+
|
|
54
|
+
const express = require('express')
|
|
55
|
+
const app = express()
|
|
56
|
+
// Logs automáticos via OpenTelemetry
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### ❌ O que NÃO precisa:
|
|
60
|
+
- ❌ Instalar dependências OpenTelemetry separadamente
|
|
61
|
+
- ❌ Configurar tracing manualmente
|
|
62
|
+
|
|
63
|
+
## ⚙️ Variáveis de Ambiente
|
|
64
|
+
|
|
65
|
+
| Variável | Padrão | Descrição |
|
|
66
|
+
|----------|-------|-----------|
|
|
67
|
+
| `APP_NAME` | - | Nome da aplicação |
|
|
68
|
+
| `AZIFY_LOGGER_URL` | `http://localhost:3000` | URL do logger |
|
|
69
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4318/v1/traces` | OTLP endpoint |
|
|
70
|
+
| `NODE_ENV` | `development` | Ambiente |
|
|
71
|
+
| `AZIFY_LOGGER_AUTOREG_DISABLE` | `""` | Se `"1"`, desativa auto-registro do OTEL |
|
|
72
|
+
|
|
73
|
+
**Para desenvolvimento local:** só precisa de `APP_NAME` (o resto usa defaults)
|
|
74
|
+
|
|
75
|
+
**Para produção:** configure todas as URLs apontando para servidores de produção
|
|
76
|
+
|
|
77
|
+
### Docker e Node antigos
|
|
78
|
+
|
|
79
|
+
Se seu container usa uma versão antiga do Node e você ver erros de inicialização do OpenTelemetry (ex.: `Cannot find module 'node:events'` saindo de `google-logging-utils` ou `@opentelemetry/resource-detector-gcp`), defina no container:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
AZIFY_LOGGER_AUTOREG_DISABLE=1
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Isso evita carregar `register-otel.js` e mantém os envios de log via streams/middleware normalmente, permitindo visualização no OpenSearch.
|
|
86
|
+
|
|
87
|
+
## 🎯 O Que Você Ganha
|
|
88
|
+
|
|
89
|
+
- ✅ **Zero Config**: OpenTelemetry habilitado automaticamente
|
|
90
|
+
- ✅ **Logs Completos**: Headers, body, query params, status
|
|
91
|
+
- ✅ **Trace Consistente**: REQUEST e RESPONSE com mesmo traceId/spanId
|
|
92
|
+
- ✅ **Genérico**: Funciona com Bunyan, Pino, console.log ou qualquer logger
|
|
93
|
+
- ✅ **Centralizado**: Todos os logs no OpenSearch
|
|
94
|
+
|
|
95
|
+
## 🔧 Uso Avançado
|
|
96
|
+
|
|
97
|
+
### Com Bunyan existente (adicionar stream)
|
|
98
|
+
|
|
99
|
+
Se sua app **já tem Bunyan** e você quer adicionar o azify-logger como stream adicional:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Forma segura com try/catch (não quebra se não tiver instalado)
|
|
103
|
+
let createAzifyBunyanStream
|
|
104
|
+
try {
|
|
105
|
+
createAzifyBunyanStream = require('azify-logger/streams/bunyan')
|
|
106
|
+
} catch (_) {
|
|
107
|
+
createAzifyBunyanStream = null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const bunyan = require('bunyan')
|
|
111
|
+
const streams = [
|
|
112
|
+
{ level: 'info', stream: process.stdout }
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
// Adiciona stream do azify-logger se disponível
|
|
116
|
+
if (createAzifyBunyanStream) {
|
|
117
|
+
streams.push({
|
|
118
|
+
level: 'info',
|
|
119
|
+
type: 'raw',
|
|
120
|
+
stream: createAzifyBunyanStream({
|
|
121
|
+
loggerUrl: process.env.AZIFY_LOGGER_URL || 'http://localhost:3000',
|
|
122
|
+
serviceName: process.env.APP_NAME || 'app'
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const logger = bunyan.createLogger({ name: 'app', streams })
|
|
128
|
+
logger.info('Teste') // Vai para stdout E para azify-logger
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Com Bunyan (auto-patch - mais simples)
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
require('azify-logger')
|
|
135
|
+
|
|
136
|
+
const bunyan = require('bunyan')
|
|
137
|
+
const log = bunyan.createLogger({ name: 'nome-app' })
|
|
138
|
+
|
|
139
|
+
log.info('Teste') // Automaticamente enviado para azify-logger
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Com Pino
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const { streams } = require('azify-logger')
|
|
146
|
+
const pino = require('pino')
|
|
147
|
+
|
|
148
|
+
const logger = pino({ level: 'info' }, streams.createPinoStream({
|
|
149
|
+
loggerUrl: 'http://localhost:3000',
|
|
150
|
+
serviceName: 'nome-app'
|
|
151
|
+
}))
|
|
152
|
+
|
|
153
|
+
logger.info('Teste')
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Logger Direto (sem Bunyan/Pino)
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
const { createAzifyLogger } = require('azify-logger')
|
|
160
|
+
|
|
161
|
+
const logger = createAzifyLogger({
|
|
162
|
+
serviceName: 'nome-app',
|
|
163
|
+
loggerUrl: 'http://localhost:3000'
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
logger.info('Mensagem', { userId: '123' })
|
|
167
|
+
logger.error('Erro', new Error('Falha'), { context: 'payment' })
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🛠️ Setup do Serviço de Logging (Infra)
|
|
173
|
+
|
|
174
|
+
Se você precisa subir a infraestrutura do azify-logger:
|
|
175
|
+
|
|
176
|
+
### 1. Iniciar serviços
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
./start-docker.sh
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Aguarde alguns minutos. Você verá:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
✅ Tudo pronto!
|
|
186
|
+
📊 OpenSearch: http://localhost:9200
|
|
187
|
+
🎨 OpenSearch Dashboards: http://localhost:5601
|
|
188
|
+
📝 Logger API: http://localhost:3000
|
|
189
|
+
🔭 OTEL Collector: http://localhost:4318
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 2. Testar
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
curl -X POST http://localhost:3000/log \
|
|
196
|
+
-H "Content-Type: application/json" \
|
|
197
|
+
-d '{
|
|
198
|
+
"level": "info",
|
|
199
|
+
"message": "Teste",
|
|
200
|
+
"meta": {"service": {"name": "teste"}}
|
|
201
|
+
}'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 3. Ver logs
|
|
205
|
+
|
|
206
|
+
**OpenSearch Dashboards:**
|
|
207
|
+
```bash
|
|
208
|
+
http://localhost:5601
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Como usar:**
|
|
212
|
+
Acesse: http://localhost:5601
|
|
213
|
+
|
|
214
|
+
## 📊 Estrutura dos Logs
|
|
215
|
+
|
|
216
|
+
Cada log no OpenSearch contém:
|
|
217
|
+
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"timestamp": "2025-10-01T10:15:30.123Z",
|
|
221
|
+
"level": "info",
|
|
222
|
+
"message": "[REQUEST] POST /api/payment",
|
|
223
|
+
"service": {
|
|
224
|
+
"name": "nome-app",
|
|
225
|
+
"version": "1.0.0"
|
|
226
|
+
},
|
|
227
|
+
"traceId": "abc123...",
|
|
228
|
+
"spanId": "xyz789...",
|
|
229
|
+
"method": "POST",
|
|
230
|
+
"url": "/api/payment",
|
|
231
|
+
"requestBody": { ... },
|
|
232
|
+
"statusCode": 200,
|
|
233
|
+
"responseTime": 123.45,
|
|
234
|
+
"responseBody": { ... },
|
|
235
|
+
"environment": "production",
|
|
236
|
+
"hostname": "app-server-01"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## 🔍 Como Funciona
|
|
241
|
+
|
|
242
|
+
1. **OpenTelemetry** cria um span para cada request HTTP
|
|
243
|
+
2. **Middleware** captura request e response com o **mesmo span ativo**
|
|
244
|
+
3. **Logger** envia ambos os logs com **traceId e spanId iguais**
|
|
245
|
+
4. **OpenSearch** indexa e permite buscar por traceId
|
|
246
|
+
|
|
247
|
+
## 📖 Arquitetura
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
┌─────────────┐
|
|
251
|
+
│ aplicação │
|
|
252
|
+
└──────┬──────┘
|
|
253
|
+
│ HTTP POST
|
|
254
|
+
↓
|
|
255
|
+
┌─────────────┐
|
|
256
|
+
│ azify-logger│ (recebe logs)
|
|
257
|
+
└──────┬──────┘
|
|
258
|
+
│
|
|
259
|
+
↓
|
|
260
|
+
┌─────────────┐
|
|
261
|
+
│ OpenSearch │ (armazena)
|
|
262
|
+
└──────┬──────┘
|
|
263
|
+
│
|
|
264
|
+
↓
|
|
265
|
+
┌─────────────┐
|
|
266
|
+
│ Dashboards |
|
|
267
|
+
| OpenSearch │ (visualiza)
|
|
268
|
+
└─────────────┘
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## 📈 OpenSearch Dashboards
|
|
272
|
+
|
|
273
|
+
O azify-logger usa OpenSearch Dashboards para visualização dos logs:
|
|
274
|
+
|
|
275
|
+
### 🎯 Funcionalidades Disponíveis
|
|
276
|
+
|
|
277
|
+
1. **📊 Discover** - Visualização e busca de logs
|
|
278
|
+
- Filtros por serviço, app, traceId, statusCode
|
|
279
|
+
- Busca em tempo real
|
|
280
|
+
- Visualização detalhada de cada log
|
|
281
|
+
|
|
282
|
+
2. **📈 Dashboard** - "Application Health Dashboard"
|
|
283
|
+
- **Taxa de Sucesso** - Percentual de requisições 2xx
|
|
284
|
+
- **Latência Média** - Tempo médio de resposta
|
|
285
|
+
- **Erros 4xx/5xx** - Contador de erros
|
|
286
|
+
- **Requisições por Minuto** - Gráfico temporal
|
|
287
|
+
|
|
288
|
+
### 🚀 Acesso
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Local
|
|
292
|
+
http://localhost:5601
|
|
293
|
+
|
|
294
|
+
# Sem autenticação necessária (desenvolvimento)
|
|
295
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const axios = require('axios')
|
|
2
|
+
const { context, propagation, trace } = require('@opentelemetry/api')
|
|
3
|
+
const { W3CTraceContextPropagator } = require('@opentelemetry/core')
|
|
4
|
+
|
|
5
|
+
if (process.env.AZIFY_LOGGER_AUTOREG_DISABLE !== '1') {
|
|
6
|
+
try { require('./register-otel') } catch (_) {}
|
|
7
|
+
try { require('./register') } catch (_) {}
|
|
8
|
+
try { require('./register-restify') } catch (_) {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class AzifyLogger {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.options = {
|
|
14
|
+
serviceName: options.serviceName || 'azipay',
|
|
15
|
+
loggerUrl: options.loggerUrl || 'http://localhost:3000',
|
|
16
|
+
environment: options.environment || process.env.NODE_ENV || 'development',
|
|
17
|
+
...options
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.propagator = new W3CTraceContextPropagator()
|
|
21
|
+
propagation.setGlobalPropagator(this.propagator)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async log(level, message, meta = {}) {
|
|
25
|
+
const span = trace.getSpan(context.active())
|
|
26
|
+
const spanContext = span?.spanContext()
|
|
27
|
+
|
|
28
|
+
const logData = {
|
|
29
|
+
level,
|
|
30
|
+
message,
|
|
31
|
+
meta: {
|
|
32
|
+
...meta,
|
|
33
|
+
service: {
|
|
34
|
+
name: this.options.serviceName,
|
|
35
|
+
version: meta.service?.version || '1.0.0'
|
|
36
|
+
},
|
|
37
|
+
environment: this.options.environment,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
hostname: require('os').hostname()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (spanContext) {
|
|
44
|
+
logData.meta.traceId = spanContext.traceId
|
|
45
|
+
logData.meta.spanId = spanContext.spanId
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const headers = {}
|
|
50
|
+
try {
|
|
51
|
+
propagation.inject(context.active(), headers, {
|
|
52
|
+
set (carrier, key, value) {
|
|
53
|
+
carrier[key] = value
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
|
|
58
|
+
await axios.post(`${this.options.loggerUrl}/log`, logData, {
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
headers
|
|
61
|
+
})
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Erro ao enviar log:', error.message)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
info(message, meta = {}) {
|
|
68
|
+
return this.log('info', message, meta)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
error(message, error = null, meta = {}) {
|
|
72
|
+
const logMeta = { ...meta }
|
|
73
|
+
if (error) {
|
|
74
|
+
logMeta.error = {
|
|
75
|
+
message: error.message,
|
|
76
|
+
stack: error.stack,
|
|
77
|
+
name: error.name
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return this.log('error', message, logMeta)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
warn(message, meta = {}) {
|
|
84
|
+
return this.log('warn', message, meta)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
debug(message, meta = {}) {
|
|
88
|
+
return this.log('debug', message, meta)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createAzifyLogger(options = {}) {
|
|
93
|
+
return new AzifyLogger(options)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createAzifyLoggerFromEnv() {
|
|
97
|
+
return createAzifyLogger({
|
|
98
|
+
serviceName: process.env.APP_NAME || 'azipay',
|
|
99
|
+
loggerUrl: process.env.AZIFY_LOGGER_URL || 'http://localhost:3000',
|
|
100
|
+
environment: process.env.NODE_ENV || 'development'
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = AzifyLogger
|
|
105
|
+
module.exports.createAzifyLogger = createAzifyLogger
|
|
106
|
+
module.exports.createAzifyLoggerFromEnv = createAzifyLoggerFromEnv
|
|
107
|
+
module.exports.streams = {
|
|
108
|
+
createBunyanStream: require('./streams/bunyan'),
|
|
109
|
+
createPinoStream: require('./streams/pino')
|
|
110
|
+
}
|
|
111
|
+
module.exports.middleware = {
|
|
112
|
+
restify: require('./middleware-restify'),
|
|
113
|
+
express: require('./middleware-express')
|
|
114
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
const axios = require('axios')
|
|
2
|
+
const { als, runWithRequestContext, startRequestContext, getRequestContext } = require('./store')
|
|
3
|
+
|
|
4
|
+
function createExpressLoggingMiddleware(options = {}) {
|
|
5
|
+
const config = {
|
|
6
|
+
serviceName: options.serviceName || process.env.APP_NAME || 'assemble',
|
|
7
|
+
loggerUrl: options.loggerUrl || process.env.AZIFY_LOGGER_URL || 'http://localhost:3000',
|
|
8
|
+
environment: options.environment || process.env.NODE_ENV || 'development'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function sendLog(level, message, meta = {}) {
|
|
12
|
+
const logData = {
|
|
13
|
+
level,
|
|
14
|
+
message,
|
|
15
|
+
meta: {
|
|
16
|
+
...meta,
|
|
17
|
+
service: {
|
|
18
|
+
name: config.serviceName,
|
|
19
|
+
version: '1.0.0'
|
|
20
|
+
},
|
|
21
|
+
environment: config.environment,
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
hostname: require('os').hostname()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await axios.post(`${config.loggerUrl}/log`, logData, {
|
|
29
|
+
timeout: 5000
|
|
30
|
+
})
|
|
31
|
+
} catch (error) {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return function azifyExpressLoggingMiddleware(req, res, next) {
|
|
35
|
+
const startTime = Date.now()
|
|
36
|
+
const requestId = req.requestId || require('uuid').v4()
|
|
37
|
+
const middlewareId = require('uuid').v4().substring(0, 8)
|
|
38
|
+
|
|
39
|
+
const reqCtx = startRequestContext({ requestId })
|
|
40
|
+
req.__azifyTraceId = reqCtx.traceId
|
|
41
|
+
req.__azifySpanId = reqCtx.spanId
|
|
42
|
+
req.__azifyParentSpanId = reqCtx.parentSpanId
|
|
43
|
+
|
|
44
|
+
const requestTraceId = req.__azifyTraceId
|
|
45
|
+
const requestSpanId = req.__azifySpanId
|
|
46
|
+
const requestParentSpanId = req.__azifyParentSpanId
|
|
47
|
+
|
|
48
|
+
let baseUrl = req.url
|
|
49
|
+
if (baseUrl.includes('?')) {
|
|
50
|
+
baseUrl = baseUrl.substring(0, baseUrl.indexOf('?'))
|
|
51
|
+
}
|
|
52
|
+
baseUrl = baseUrl.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/{id}')
|
|
53
|
+
baseUrl = baseUrl.replace(/\/[0-9]+/g, '/{id}')
|
|
54
|
+
|
|
55
|
+
req._azifyRequestData = {
|
|
56
|
+
requestId,
|
|
57
|
+
method: req.method,
|
|
58
|
+
url: req.url,
|
|
59
|
+
baseUrl: baseUrl,
|
|
60
|
+
path: req.url,
|
|
61
|
+
headers: req.headers,
|
|
62
|
+
query: req.query,
|
|
63
|
+
userAgent: req.headers['user-agent'],
|
|
64
|
+
ip: req.connection?.remoteAddress || req.socket?.remoteAddress || req.ip,
|
|
65
|
+
traceId: requestTraceId,
|
|
66
|
+
spanId: requestSpanId,
|
|
67
|
+
parentSpanId: requestParentSpanId
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (req.method === 'GET') {
|
|
71
|
+
sendLog('info', `[REQUEST] ${req.method} ${req.url}`, req._azifyRequestData)
|
|
72
|
+
} else {
|
|
73
|
+
if (req.body !== undefined) {
|
|
74
|
+
req._azifyRequestData.requestBody = req.body
|
|
75
|
+
}
|
|
76
|
+
sendLog('info', `[REQUEST] ${req.method} ${req.url}`, req._azifyRequestData)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
res.on('finish', () => {
|
|
80
|
+
if (!responseLogged) {
|
|
81
|
+
logResponse()
|
|
82
|
+
responseLogged = true
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
res.on('close', () => {
|
|
87
|
+
if (!responseLogged) {
|
|
88
|
+
logResponse()
|
|
89
|
+
responseLogged = true
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
let sentBody
|
|
94
|
+
let responseLogged = false
|
|
95
|
+
|
|
96
|
+
const originalSend = res.send && res.send.bind(res)
|
|
97
|
+
if (originalSend) {
|
|
98
|
+
res.send = function patchedSend() {
|
|
99
|
+
try {
|
|
100
|
+
if (arguments.length === 1) {
|
|
101
|
+
sentBody = arguments[0]
|
|
102
|
+
} else if (arguments.length >= 2) {
|
|
103
|
+
sentBody = typeof arguments[0] === 'number' ? arguments[1] : (arguments[1] || arguments[0])
|
|
104
|
+
}
|
|
105
|
+
} catch (_) {}
|
|
106
|
+
|
|
107
|
+
if (!responseLogged) {
|
|
108
|
+
logResponse()
|
|
109
|
+
responseLogged = true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return originalSend.apply(this, arguments)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const originalStatus = res.status
|
|
117
|
+
res.status = function(code) {
|
|
118
|
+
res._actualStatusCode = code
|
|
119
|
+
return originalStatus.call(this, code)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const originalWriteHead = res.writeHead
|
|
123
|
+
res.writeHead = function(statusCode, statusMessage, headers) {
|
|
124
|
+
res._actualStatusCode = statusCode
|
|
125
|
+
if (typeof statusMessage === 'object') {
|
|
126
|
+
headers = statusMessage
|
|
127
|
+
statusMessage = undefined
|
|
128
|
+
}
|
|
129
|
+
return originalWriteHead.call(this, statusCode, statusMessage, headers)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const originalJsonMethod = res.json
|
|
133
|
+
res.json = function(code, body) {
|
|
134
|
+
try {
|
|
135
|
+
if (arguments.length === 1) {
|
|
136
|
+
sentBody = arguments[0]
|
|
137
|
+
} else if (arguments.length >= 2) {
|
|
138
|
+
sentBody = typeof arguments[0] === 'number' ? arguments[1] : (arguments[1] || arguments[0])
|
|
139
|
+
}
|
|
140
|
+
} catch (_) {}
|
|
141
|
+
|
|
142
|
+
if (typeof code === 'number') {
|
|
143
|
+
res._actualStatusCode = code
|
|
144
|
+
} else {
|
|
145
|
+
const errorObj = arguments.length === 1 ? arguments[0] : (typeof arguments[0] === 'number' ? arguments[1] : arguments[0])
|
|
146
|
+
|
|
147
|
+
if (errorObj && errorObj.constructor && errorObj.constructor.name === 'ErrCtor') {
|
|
148
|
+
const errorName = errorObj.toString()
|
|
149
|
+
if (errorName.includes('InternalServerError') || errorName.includes('InternalError')) {
|
|
150
|
+
res._actualStatusCode = 500
|
|
151
|
+
} else if (errorName.includes('BadRequest') || errorName.includes('BadDigest')) {
|
|
152
|
+
res._actualStatusCode = 400
|
|
153
|
+
} else if (errorName.includes('NotFound')) {
|
|
154
|
+
res._actualStatusCode = 404
|
|
155
|
+
} else if (errorName.includes('Unauthorized')) {
|
|
156
|
+
res._actualStatusCode = 401
|
|
157
|
+
} else if (errorName.includes('Forbidden')) {
|
|
158
|
+
res._actualStatusCode = 403
|
|
159
|
+
} else {
|
|
160
|
+
res._actualStatusCode = 500
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
res._actualStatusCode = res.statusCode || 200
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!responseLogged) {
|
|
168
|
+
logResponse()
|
|
169
|
+
responseLogged = true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return originalJsonMethod.apply(this, arguments)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
res.on('finish', function() {
|
|
176
|
+
if (!responseLogged) {
|
|
177
|
+
logResponse()
|
|
178
|
+
responseLogged = true
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const originalEnd = res.end
|
|
183
|
+
res.end = function(chunk, encoding) {
|
|
184
|
+
const duration = Date.now() - startTime
|
|
185
|
+
|
|
186
|
+
let responseBody = sentBody
|
|
187
|
+
try {
|
|
188
|
+
if (responseBody == null && chunk) {
|
|
189
|
+
if (Buffer.isBuffer(chunk)) {
|
|
190
|
+
responseBody = chunk.toString('utf8')
|
|
191
|
+
} else if (typeof chunk === 'string') {
|
|
192
|
+
responseBody = chunk
|
|
193
|
+
} else {
|
|
194
|
+
responseBody = JSON.stringify(chunk)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (_) {}
|
|
198
|
+
|
|
199
|
+
if (!responseLogged) {
|
|
200
|
+
logResponse()
|
|
201
|
+
responseLogged = true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
originalEnd.call(this, chunk, encoding)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function logResponse() {
|
|
208
|
+
const duration = Date.now() - startTime
|
|
209
|
+
|
|
210
|
+
let responseBody = sentBody
|
|
211
|
+
|
|
212
|
+
let serializedResponseBody
|
|
213
|
+
try {
|
|
214
|
+
if (typeof responseBody === 'string') {
|
|
215
|
+
serializedResponseBody = responseBody
|
|
216
|
+
} else if (Array.isArray(responseBody)) {
|
|
217
|
+
serializedResponseBody = JSON.stringify(responseBody)
|
|
218
|
+
} else if (responseBody && typeof responseBody === 'object') {
|
|
219
|
+
if (responseBody.toJSON && typeof responseBody.toJSON === 'function') {
|
|
220
|
+
serializedResponseBody = JSON.stringify(responseBody.toJSON())
|
|
221
|
+
} else if (responseBody.toString && typeof responseBody.toString === 'function' && responseBody.toString() !== '[object Object]') {
|
|
222
|
+
serializedResponseBody = responseBody.toString()
|
|
223
|
+
} else {
|
|
224
|
+
serializedResponseBody = JSON.stringify(responseBody, (key, value) => {
|
|
225
|
+
if (typeof value === 'function') {
|
|
226
|
+
return '[Function]'
|
|
227
|
+
}
|
|
228
|
+
if (value instanceof Error) {
|
|
229
|
+
return { name: value.name, message: value.message, stack: value.stack }
|
|
230
|
+
}
|
|
231
|
+
return value
|
|
232
|
+
}, null, 0)
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
serializedResponseBody = responseBody != null ? String(responseBody) : ''
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
try {
|
|
239
|
+
serializedResponseBody = JSON.stringify(responseBody, null, 2)
|
|
240
|
+
} catch (secondError) {
|
|
241
|
+
serializedResponseBody = '[Complex object - serialization failed]'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const statusCode = res._actualStatusCode || res._statusCode || res.statusCode || 200
|
|
246
|
+
|
|
247
|
+
const responseMessage = serializedResponseBody && serializedResponseBody.length > 0
|
|
248
|
+
? `[RESPONSE] ${serializedResponseBody}`
|
|
249
|
+
: `[RESPONSE] ${req.method} ${req.url} ${statusCode} ${duration}ms`
|
|
250
|
+
|
|
251
|
+
const responseData = {
|
|
252
|
+
...req._azifyRequestData,
|
|
253
|
+
requestBody: req.body,
|
|
254
|
+
statusCode: statusCode,
|
|
255
|
+
responseTime: duration,
|
|
256
|
+
responseHeaders: res.getHeaders ? res.getHeaders() : {},
|
|
257
|
+
responseBody: serializedResponseBody
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try { res._azifyResponseLogged = true } catch (_) {}
|
|
261
|
+
sendLog('info', responseMessage, responseData)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
req._azifyContext = reqCtx
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
runWithRequestContext(reqCtx, () => {
|
|
268
|
+
next()
|
|
269
|
+
})
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Error in azify middleware:', error)
|
|
272
|
+
next()
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = createExpressLoggingMiddleware
|