nestjs-profiler 1.0.21 → 1.0.24
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 +114 -39
- package/collectors/http-collector.d.ts +12 -0
- package/collectors/http-collector.js +132 -0
- package/collectors/http-collector.js.map +1 -0
- package/collectors/log-collector.d.ts +3 -1
- package/collectors/log-collector.js +33 -12
- package/collectors/log-collector.js.map +1 -1
- package/common/profiler-options.interface.d.ts +1 -0
- package/common/profiler.model.d.ts +14 -0
- package/controllers/profiler.controller.d.ts +4 -0
- package/controllers/profiler.controller.js +86 -0
- package/controllers/profiler.controller.js.map +1 -1
- package/package.json +1 -1
- package/profiler.module.js +2 -0
- package/profiler.module.js.map +1 -1
- package/services/profiler.service.d.ts +43 -1
- package/services/profiler.service.js +120 -0
- package/services/profiler.service.js.map +1 -1
- package/services/template-builder.service.d.ts +5 -0
- package/services/template-builder.service.js +272 -1
- package/services/template-builder.service.js.map +1 -1
- package/services/view.service.js +20 -12
- package/services/view.service.js.map +1 -1
- package/views/dashboard.html +24 -0
- package/views/detail.html +2 -0
- package/views/layout.html +45 -0
- package/views/live-logs.html +324 -0
- package/views/summary.html +205 -0
package/README.md
CHANGED
|
@@ -4,19 +4,27 @@
|
|
|
4
4
|
<img src="https://raw.githubusercontent.com/MohammedRaslan/Nest-JS-Profiler/refs/heads/main/libs/nestjs-profiler/src/assets/logo.png" width="400" alt="NestJS Profiler Logo" />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
A NestJS module for profiling HTTP requests, database queries, and
|
|
7
|
+
A NestJS module for profiling HTTP requests, database queries, cache operations, outbound HTTP calls, and application logs. Inspired by Symfony Profiler, it provides a web-based dashboard to inspect everything that happens inside a request — from DB queries to downstream API calls — in real time.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **HTTP Request Tracing
|
|
12
|
-
- **Database Profiling
|
|
13
|
-
- **PostgreSQL
|
|
14
|
-
- **MongoDB
|
|
15
|
-
- **MySQL
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
11
|
+
- **HTTP Request Tracing** — Tracks method, URL, controller handler, duration, status code, headers, and request body.
|
|
12
|
+
- **Database Profiling**
|
|
13
|
+
- **PostgreSQL** — Captures all queries via `pg` (compatible with TypeORM, MikroORM, raw pg). Supports **Auto-Explain** to run `EXPLAIN` or `EXPLAIN ANALYZE` on slow queries automatically.
|
|
14
|
+
- **MongoDB** — Profiles MongoDB commands and queries.
|
|
15
|
+
- **MySQL** — Profiles MySQL queries.
|
|
16
|
+
- **N+1 Detection** — Automatically flags repeated identical queries within the same request.
|
|
17
|
+
- **Slow Query Tagging** — Tags queries exceeding 100ms.
|
|
18
|
+
- **Sequential Scan Detection** — Tags queries using a Seq Scan from the explain plan.
|
|
19
|
+
- **Outbound HTTP Tracking** — Captures every outbound `http`/`https` call made during a request: URL, method, status code, duration, and headers. Works automatically with axios, node-fetch, and any library built on Node's `http`/`https` modules. Enabled by default — no configuration required.
|
|
20
|
+
- **Cache Profiling** — Tracks cache operations (get, set, del, reset) and hit/miss ratio when using `@nestjs/cache-manager`.
|
|
21
|
+
- **Log Profiling** — Captures application logs associated with each request context.
|
|
22
|
+
- **Live Logs Terminal** — Real-time streaming log viewer at `/__profiler/view/logs/live` via Server-Sent Events. Supports level filtering, search, pause/resume, and auto-scroll.
|
|
23
|
+
- **Summary Dashboard** — Aggregate statistics: avg/p95 duration, error rate, cache hit rate, top slow endpoints, top slow queries, and recent errors.
|
|
24
|
+
- **Entity Explorer** — Lists all registered TypeORM/MikroORM entities and their columns.
|
|
25
|
+
- **Route Explorer** — Lists all registered routes with their controllers and handlers.
|
|
26
|
+
- **Web UI** — Built-in dashboard at `/__profiler` with no external dependencies.
|
|
27
|
+
- **Zero Hard Dependencies** — Core functionality works out of the box; database drivers are optional peer dependencies.
|
|
20
28
|
|
|
21
29
|
## Installation
|
|
22
30
|
|
|
@@ -26,7 +34,7 @@ npm install nestjs-profiler
|
|
|
26
34
|
|
|
27
35
|
### Peer Dependencies (Optional)
|
|
28
36
|
|
|
29
|
-
Install the dependencies relevant to your project:
|
|
37
|
+
Install only the dependencies relevant to your project:
|
|
30
38
|
|
|
31
39
|
```bash
|
|
32
40
|
# For PostgreSQL
|
|
@@ -44,48 +52,54 @@ npm install @nestjs/cache-manager cache-manager
|
|
|
44
52
|
|
|
45
53
|
## Configuration
|
|
46
54
|
|
|
47
|
-
Import `ProfilerModule` in your root `AppModule
|
|
55
|
+
Import `ProfilerModule` in your root `AppModule`:
|
|
48
56
|
|
|
49
57
|
```typescript
|
|
50
58
|
import { Module } from '@nestjs/common';
|
|
51
59
|
import { ProfilerModule } from 'nestjs-profiler';
|
|
60
|
+
import * as pg from 'pg';
|
|
52
61
|
|
|
53
62
|
@Module({
|
|
54
63
|
imports: [
|
|
55
64
|
ProfilerModule.forRoot({
|
|
56
65
|
// Global enable/disable (default: true)
|
|
66
|
+
// Recommended: disable in production
|
|
57
67
|
enabled: process.env.NODE_ENV !== 'production',
|
|
58
68
|
|
|
59
|
-
//
|
|
69
|
+
// ── Database ─────────────────────────────────────────────────
|
|
70
|
+
// PostgreSQL — pass the pg driver instance
|
|
71
|
+
pgDriver: pg,
|
|
60
72
|
collectQueries: true,
|
|
73
|
+
|
|
74
|
+
// Auto-Explain for slow queries
|
|
61
75
|
explain: {
|
|
62
76
|
enabled: true,
|
|
63
|
-
thresholdMs: 50,
|
|
64
|
-
analyze: false,
|
|
77
|
+
thresholdMs: 50, // Only explain queries taking > 50ms
|
|
78
|
+
analyze: false, // true = EXPLAIN ANALYZE (actually executes!)
|
|
65
79
|
},
|
|
66
|
-
// Add pgDriver
|
|
67
|
-
pgDriver: pg,
|
|
68
|
-
|
|
69
|
-
// Add mysql2 driver for MySQL profiling
|
|
70
|
-
mysqlDriver: mysql2,
|
|
71
80
|
|
|
72
|
-
//
|
|
73
|
-
mongoDriver: mongodb,
|
|
74
|
-
|
|
75
|
-
// MongoDB Profiling (default: true)
|
|
81
|
+
// MongoDB — pass the mongodb driver instance
|
|
82
|
+
mongoDriver: require('mongodb'),
|
|
76
83
|
collectMongo: true,
|
|
77
84
|
|
|
78
|
-
// MySQL
|
|
85
|
+
// MySQL — pass the mysql2 driver instance
|
|
86
|
+
mysqlDriver: require('mysql2'),
|
|
79
87
|
collectMysql: true,
|
|
80
88
|
|
|
81
|
-
//
|
|
82
|
-
|
|
89
|
+
// ── Outbound HTTP ─────────────────────────────────────────────
|
|
90
|
+
// Tracks all outbound http/https calls made during each request.
|
|
91
|
+
// Enabled by default — set false to disable.
|
|
92
|
+
collectHttp: true,
|
|
93
|
+
|
|
94
|
+
// ── Cache ─────────────────────────────────────────────────────
|
|
95
|
+
collectCache: true, // requires @nestjs/cache-manager
|
|
83
96
|
|
|
84
|
-
//
|
|
97
|
+
// ── Logs ──────────────────────────────────────────────────────
|
|
85
98
|
collectLogs: true,
|
|
86
99
|
|
|
87
|
-
// Storage
|
|
88
|
-
//
|
|
100
|
+
// ── Storage ───────────────────────────────────────────────────
|
|
101
|
+
// Default: in-memory (last 100 requests).
|
|
102
|
+
// Pass a custom object implementing ProfilerStorage for persistence.
|
|
89
103
|
storage: 'memory',
|
|
90
104
|
}),
|
|
91
105
|
],
|
|
@@ -93,14 +107,24 @@ import { ProfilerModule } from 'nestjs-profiler';
|
|
|
93
107
|
export class AppModule {}
|
|
94
108
|
```
|
|
95
109
|
|
|
110
|
+
### Default behaviour
|
|
111
|
+
|
|
112
|
+
| Option | Default | Notes |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `enabled` | `true` | Set `false` or tie to `NODE_ENV` |
|
|
115
|
+
| `collectHttp` | `true` | Patches `http`/`https` automatically |
|
|
116
|
+
| `collectLogs` | `true` | |
|
|
117
|
+
| `collectQueries` | `true` | Requires `pgDriver` / `mongoDriver` / `mysqlDriver` |
|
|
118
|
+
| `collectCache` | `true` | Requires `@nestjs/cache-manager` |
|
|
119
|
+
| `explain.enabled` | `false` | Opt-in only |
|
|
120
|
+
|
|
96
121
|
## Usage
|
|
97
122
|
|
|
98
123
|
### 1. Initialize Explorers (Optional but Recommended)
|
|
99
124
|
|
|
100
|
-
To enable the **Entity Explorer** and **Route Explorer
|
|
125
|
+
To enable the **Entity Explorer** and **Route Explorer**, call `initialize` in `main.ts` after creating the app:
|
|
101
126
|
|
|
102
127
|
```typescript
|
|
103
|
-
// main.ts
|
|
104
128
|
import { NestFactory } from '@nestjs/core';
|
|
105
129
|
import { AppModule } from './app.module';
|
|
106
130
|
import { ProfilerModule } from 'nestjs-profiler';
|
|
@@ -108,7 +132,6 @@ import { ProfilerModule } from 'nestjs-profiler';
|
|
|
108
132
|
async function bootstrap() {
|
|
109
133
|
const app = await NestFactory.create(AppModule);
|
|
110
134
|
|
|
111
|
-
// Initialize Profiler Explorers
|
|
112
135
|
ProfilerModule.initialize(app);
|
|
113
136
|
|
|
114
137
|
await app.listen(3000);
|
|
@@ -118,16 +141,68 @@ bootstrap();
|
|
|
118
141
|
|
|
119
142
|
### 2. Accessing the Dashboard
|
|
120
143
|
|
|
121
|
-
Once configured, start your application. The profiler automatically intercepts requests
|
|
144
|
+
Once configured, start your application. The profiler automatically intercepts all requests.
|
|
145
|
+
|
|
146
|
+
Navigate to `http://localhost:3000/__profiler`.
|
|
147
|
+
|
|
148
|
+
#### Dashboard Pages
|
|
149
|
+
|
|
150
|
+
| Path | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `/__profiler` | Request list — all captured requests |
|
|
153
|
+
| `/__profiler/view/summary` | Aggregate stats: avg/p95 duration, error rate, top slow endpoints |
|
|
154
|
+
| `/__profiler/view/queries` | All database queries across requests, sorted by duration |
|
|
155
|
+
| `/__profiler/view/http-calls` | All outbound HTTP calls across requests, sorted by duration |
|
|
156
|
+
| `/__profiler/view/logs` | Paginated application logs |
|
|
157
|
+
| `/__profiler/view/logs/live` | Live streaming terminal — real-time log output |
|
|
158
|
+
| `/__profiler/view/cache` | Cache operations with hit/miss breakdown |
|
|
159
|
+
| `/__profiler/view/entities` | Registered database entities |
|
|
160
|
+
| `/__profiler/view/routes` | All registered application routes |
|
|
161
|
+
| `/__profiler/:id` | Full detail view for a single request |
|
|
162
|
+
|
|
163
|
+
### 3. Outbound HTTP Tracking
|
|
164
|
+
|
|
165
|
+
Outbound HTTP tracking is **enabled by default**. Any `http.request`, `https.request`, `http.get`, or `https.get` call made during a request is automatically captured — including calls made via axios, node-fetch, and similar libraries.
|
|
122
166
|
|
|
123
|
-
|
|
167
|
+
Each captured call records:
|
|
124
168
|
|
|
125
|
-
|
|
169
|
+
- Method and full URL
|
|
170
|
+
- HTTP vs HTTPS protocol
|
|
171
|
+
- Response status code
|
|
172
|
+
- Duration in ms
|
|
173
|
+
- Request and response headers (Authorization/Cookie values are automatically redacted)
|
|
174
|
+
- Error message if the call failed
|
|
175
|
+
|
|
176
|
+
You can view outbound calls for a specific request in the request detail view, or browse all calls across all requests at `/__profiler/view/http-calls`.
|
|
177
|
+
|
|
178
|
+
To disable:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
ProfilerModule.forRoot({ collectHttp: false })
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 4. JSON API
|
|
185
|
+
|
|
186
|
+
Retrieve profile data programmatically:
|
|
187
|
+
|
|
188
|
+
- `GET /__profiler/json` — List all captured requests
|
|
189
|
+
- `GET /__profiler/:id/json` — Details for a specific request
|
|
190
|
+
|
|
191
|
+
## Global Prefix
|
|
192
|
+
|
|
193
|
+
If your app uses a global prefix (e.g. `/api`), exclude the profiler routes:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
app.setGlobalPrefix('api', {
|
|
197
|
+
exclude: [{ path: '__profiler/(.*)', method: RequestMethod.ALL }],
|
|
198
|
+
});
|
|
199
|
+
```
|
|
126
200
|
|
|
127
|
-
|
|
201
|
+
## Important Notes
|
|
128
202
|
|
|
129
|
-
-
|
|
130
|
-
-
|
|
203
|
+
- The profiler is designed for **development and debugging only**. Do not enable in production — it adds request overhead and exposes internal application data.
|
|
204
|
+
- The default in-memory storage holds the last 100 requests. Older profiles are evicted automatically.
|
|
205
|
+
- Outbound HTTP tracking patches `http`/`https` at the Node.js module level. The patch is applied once on module init and is safe for hot-reload (double-patch is guarded).
|
|
131
206
|
|
|
132
207
|
## License
|
|
133
208
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import type { ProfilerOptions } from '../common/profiler-options.interface';
|
|
3
|
+
import { ProfilerService } from '../services/profiler.service';
|
|
4
|
+
export declare class HttpCollector implements OnModuleInit {
|
|
5
|
+
private readonly profiler;
|
|
6
|
+
private readonly options;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(profiler: ProfilerService, options: ProfilerOptions);
|
|
9
|
+
onModuleInit(): void;
|
|
10
|
+
private patchModule;
|
|
11
|
+
private sanitiseHeaders;
|
|
12
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var HttpCollector_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.HttpCollector = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const profiler_service_1 = require("../services/profiler.service");
|
|
19
|
+
const PROFILER_INTERNAL_HEADER = 'x-profiler-internal';
|
|
20
|
+
let HttpCollector = HttpCollector_1 = class HttpCollector {
|
|
21
|
+
constructor(profiler, options) {
|
|
22
|
+
this.profiler = profiler;
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.logger = new common_1.Logger(HttpCollector_1.name);
|
|
25
|
+
}
|
|
26
|
+
onModuleInit() {
|
|
27
|
+
if (this.options.collectHttp === false)
|
|
28
|
+
return;
|
|
29
|
+
const httpMod = require('http');
|
|
30
|
+
const httpsMod = require('https');
|
|
31
|
+
this.patchModule(httpMod, 'http');
|
|
32
|
+
this.patchModule(httpsMod, 'https');
|
|
33
|
+
this.logger.log('HTTP/HTTPS outbound request tracking enabled');
|
|
34
|
+
}
|
|
35
|
+
patchModule(mod, protocol) {
|
|
36
|
+
if (mod.__profilerPatched)
|
|
37
|
+
return;
|
|
38
|
+
mod.__profilerPatched = true;
|
|
39
|
+
const self = this;
|
|
40
|
+
const originalRequest = mod.request.bind(mod);
|
|
41
|
+
const originalGet = mod.get.bind(mod);
|
|
42
|
+
mod.request = function (...args) {
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
let method = 'GET';
|
|
45
|
+
let host = '';
|
|
46
|
+
let path = '/';
|
|
47
|
+
let fullUrl = '';
|
|
48
|
+
let reqHeaders = {};
|
|
49
|
+
try {
|
|
50
|
+
const first = args[0];
|
|
51
|
+
if (typeof first === 'string' || first instanceof URL) {
|
|
52
|
+
const u = typeof first === 'string' ? new URL(first) : first;
|
|
53
|
+
host = u.hostname + (u.port ? `:${u.port}` : '');
|
|
54
|
+
path = u.pathname + u.search;
|
|
55
|
+
fullUrl = u.toString();
|
|
56
|
+
const opts = (args[1] && typeof args[1] === 'object' && typeof args[1] !== 'function') ? args[1] : {};
|
|
57
|
+
method = (opts?.method || 'GET').toUpperCase();
|
|
58
|
+
reqHeaders = opts?.headers || {};
|
|
59
|
+
}
|
|
60
|
+
else if (first && typeof first === 'object') {
|
|
61
|
+
method = (first.method || 'GET').toUpperCase();
|
|
62
|
+
host = first.hostname || first.host || 'localhost';
|
|
63
|
+
if (first.port && !String(host).includes(':'))
|
|
64
|
+
host += `:${first.port}`;
|
|
65
|
+
path = first.path || '/';
|
|
66
|
+
const defaultPort = protocol === 'https' ? 443 : 80;
|
|
67
|
+
const portStr = first.port && first.port !== defaultPort ? `:${first.port}` : '';
|
|
68
|
+
fullUrl = `${protocol}://${first.hostname || first.host || 'localhost'}${portStr}${path}`;
|
|
69
|
+
reqHeaders = first.headers || {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (_) { }
|
|
73
|
+
if (reqHeaders[PROFILER_INTERNAL_HEADER] || fullUrl.includes('/__profiler')) {
|
|
74
|
+
return originalRequest(...args);
|
|
75
|
+
}
|
|
76
|
+
const profile = self.profiler.getCurrentProfile();
|
|
77
|
+
const req = originalRequest(...args);
|
|
78
|
+
if (profile) {
|
|
79
|
+
const httpCall = {
|
|
80
|
+
method,
|
|
81
|
+
url: fullUrl,
|
|
82
|
+
host,
|
|
83
|
+
path,
|
|
84
|
+
protocol,
|
|
85
|
+
startTime,
|
|
86
|
+
duration: 0,
|
|
87
|
+
requestHeaders: self.sanitiseHeaders(reqHeaders),
|
|
88
|
+
};
|
|
89
|
+
req.on('response', (res) => {
|
|
90
|
+
httpCall.statusCode = res.statusCode;
|
|
91
|
+
httpCall.responseHeaders = self.sanitiseHeaders(res.headers);
|
|
92
|
+
res.on('end', () => {
|
|
93
|
+
httpCall.duration = Date.now() - startTime;
|
|
94
|
+
self.profiler.addHttpCall(httpCall);
|
|
95
|
+
});
|
|
96
|
+
res.resume();
|
|
97
|
+
});
|
|
98
|
+
req.on('error', (err) => {
|
|
99
|
+
httpCall.duration = Date.now() - startTime;
|
|
100
|
+
httpCall.error = err.message;
|
|
101
|
+
self.profiler.addHttpCall(httpCall);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return req;
|
|
105
|
+
};
|
|
106
|
+
mod.get = function (...args) {
|
|
107
|
+
const req = mod.request(...args);
|
|
108
|
+
req.end();
|
|
109
|
+
return req;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
sanitiseHeaders(headers) {
|
|
113
|
+
const result = {};
|
|
114
|
+
for (const [k, v] of Object.entries(headers || {})) {
|
|
115
|
+
const lower = k.toLowerCase();
|
|
116
|
+
if (lower === 'authorization' || lower === 'cookie' || lower === 'set-cookie') {
|
|
117
|
+
result[k] = '[redacted]';
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
result[k] = Array.isArray(v) ? v.join(', ') : String(v ?? '');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
exports.HttpCollector = HttpCollector;
|
|
127
|
+
exports.HttpCollector = HttpCollector = HttpCollector_1 = __decorate([
|
|
128
|
+
(0, common_1.Injectable)(),
|
|
129
|
+
__param(1, (0, common_1.Inject)('PROFILER_OPTIONS')),
|
|
130
|
+
__metadata("design:paramtypes", [profiler_service_1.ProfilerService, Object])
|
|
131
|
+
], HttpCollector);
|
|
132
|
+
//# sourceMappingURL=http-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-collector.js","sourceRoot":"","sources":["../../../libs/nestjs-profiler/src/collectors/http-collector.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,8 @@ export declare class LogCollector implements OnModuleInit {
|
|
|
7
7
|
private logger;
|
|
8
8
|
constructor(profiler: ProfilerService, options: ProfilerOptions);
|
|
9
9
|
onModuleInit(): void;
|
|
10
|
+
private stripAnsi;
|
|
11
|
+
private detectLevel;
|
|
12
|
+
private parseNestLog;
|
|
10
13
|
private patchProcessStream;
|
|
11
|
-
private capture;
|
|
12
14
|
}
|
|
@@ -30,6 +30,29 @@ let LogCollector = LogCollector_1 = class LogCollector {
|
|
|
30
30
|
this.patchProcessStream('stderr');
|
|
31
31
|
this.logger.log('LogCollector initialized: Patching process.stdout/stderr');
|
|
32
32
|
}
|
|
33
|
+
stripAnsi(str) {
|
|
34
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
35
|
+
}
|
|
36
|
+
detectLevel(raw, fallback) {
|
|
37
|
+
if (raw.includes('\x1b[31m'))
|
|
38
|
+
return 'error';
|
|
39
|
+
if (raw.includes('\x1b[33m'))
|
|
40
|
+
return 'warn';
|
|
41
|
+
if (raw.includes('\x1b[95m'))
|
|
42
|
+
return 'debug';
|
|
43
|
+
if (raw.includes('\x1b[96m'))
|
|
44
|
+
return 'verbose';
|
|
45
|
+
if (raw.includes('\x1b[32m'))
|
|
46
|
+
return 'log';
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
parseNestLog(clean) {
|
|
50
|
+
const match = clean.match(/^\[Nest\]\s+\d+\s+-\s+[\d/,: APM]+\s+\w+\s+\[([^\]]+)\]\s+(.+)$/s);
|
|
51
|
+
if (match)
|
|
52
|
+
return { context: match[1], message: match[2].trim() };
|
|
53
|
+
const prefixStripped = clean.replace(/^\[Nest\]\s+\d+\s+-\s+[\d/,: APM]+\s+\w+\s+/, '').trim();
|
|
54
|
+
return { message: prefixStripped || clean };
|
|
55
|
+
}
|
|
33
56
|
patchProcessStream(streamName) {
|
|
34
57
|
const stream = process[streamName];
|
|
35
58
|
const originalWrite = stream.write;
|
|
@@ -37,25 +60,23 @@ let LogCollector = LogCollector_1 = class LogCollector {
|
|
|
37
60
|
stream.write = function (chunk, encodingOrCb, cb) {
|
|
38
61
|
const result = originalWrite.apply(this, arguments);
|
|
39
62
|
try {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
63
|
+
const raw = chunk.toString();
|
|
64
|
+
if (raw.includes('[LogCollector]'))
|
|
65
|
+
return result;
|
|
66
|
+
const level = self.detectLevel(raw, streamName === 'stderr' ? 'error' : 'log');
|
|
67
|
+
const clean = self.stripAnsi(raw).trim();
|
|
68
|
+
if (!clean)
|
|
42
69
|
return result;
|
|
43
|
-
|
|
70
|
+
const { context, message } = self.parseNestLog(clean);
|
|
71
|
+
if (!message)
|
|
72
|
+
return result;
|
|
73
|
+
self.profiler.addLog({ level, message, context, timestamp: Date.now() });
|
|
44
74
|
}
|
|
45
75
|
catch (e) {
|
|
46
76
|
}
|
|
47
77
|
return result;
|
|
48
78
|
};
|
|
49
79
|
}
|
|
50
|
-
capture(level, message) {
|
|
51
|
-
if (!message.trim())
|
|
52
|
-
return;
|
|
53
|
-
this.profiler.addLog({
|
|
54
|
-
level,
|
|
55
|
-
message: message.trim(),
|
|
56
|
-
timestamp: Date.now()
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
80
|
};
|
|
60
81
|
exports.LogCollector = LogCollector;
|
|
61
82
|
exports.LogCollector = LogCollector = LogCollector_1 = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-collector.js","sourceRoot":"","sources":["../../../libs/nestjs-profiler/src/collectors/log-collector.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyF;AACzF,mEAA+D;AAIxD,IAAM,YAAY,oBAAlB,MAAM,YAAY;IAGrB,YACY,QAAyB,EACL,OAAgC;QADpD,aAAQ,GAAR,QAAQ,CAAiB;QACG,YAAO,GAAP,OAAO,CAAiB;QAJxD,WAAM,GAAG,IAAI,eAAM,CAAC,cAAY,CAAC,IAAI,CAAC,CAAC;IAK3C,CAAC;IAEL,YAAY;QACR,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YACrC,OAAO;QACX,CAAC;QAED,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IAChF,CAAC;IAEO,kBAAkB,CAAC,UAA+B;QACtD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACnC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,MAAM,CAAC,KAAK,GAAG,UACX,KAA0B,EAC1B,YAAuD,EACvD,EAA0B;YAE1B,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC;YAE3D,IAAI,CAAC;gBACD,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC7B,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC;oBAAE,OAAO,MAAM,CAAC;gBAElD,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"log-collector.js","sourceRoot":"","sources":["../../../libs/nestjs-profiler/src/collectors/log-collector.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,19 @@ export interface CacheProfile {
|
|
|
31
31
|
startTime: number;
|
|
32
32
|
value?: any;
|
|
33
33
|
}
|
|
34
|
+
export interface HttpCallProfile {
|
|
35
|
+
method: string;
|
|
36
|
+
url: string;
|
|
37
|
+
host: string;
|
|
38
|
+
path: string;
|
|
39
|
+
statusCode?: number;
|
|
40
|
+
duration: number;
|
|
41
|
+
startTime: number;
|
|
42
|
+
requestHeaders?: Record<string, string>;
|
|
43
|
+
responseHeaders?: Record<string, string>;
|
|
44
|
+
error?: string;
|
|
45
|
+
protocol: 'http' | 'https';
|
|
46
|
+
}
|
|
34
47
|
export interface RequestProfile {
|
|
35
48
|
id: string;
|
|
36
49
|
method: string;
|
|
@@ -52,6 +65,7 @@ export interface RequestProfile {
|
|
|
52
65
|
queries: QueryProfile[];
|
|
53
66
|
logs: LogProfile[];
|
|
54
67
|
cache?: CacheProfile[];
|
|
68
|
+
httpCalls?: HttpCallProfile[];
|
|
55
69
|
timestamp: number;
|
|
56
70
|
requestHeaders?: Record<string, any>;
|
|
57
71
|
requestBody?: any;
|
|
@@ -24,11 +24,15 @@ export declare class ProfilerController {
|
|
|
24
24
|
}>;
|
|
25
25
|
detail(id: string, res: Response): Promise<void>;
|
|
26
26
|
detailJson(id: string): Promise<import("..").RequestProfile>;
|
|
27
|
+
summary(res: Response): Promise<void>;
|
|
27
28
|
listQueries(res: Response): Promise<void>;
|
|
28
29
|
listLogs(res: Response, page?: number): Promise<void>;
|
|
29
30
|
listEntities(res: Response): Promise<void>;
|
|
30
31
|
listRoutes(res: Response): Promise<void>;
|
|
31
32
|
listCache(res: Response): Promise<void>;
|
|
33
|
+
listHttpCalls(res: Response): Promise<void>;
|
|
34
|
+
liveLogsPage(res: Response): Promise<void>;
|
|
35
|
+
liveLogsStream(res: Response): void;
|
|
32
36
|
serveAsset(file: string, res: Response): Promise<void>;
|
|
33
37
|
serveJs(file: string, res: Response): Promise<void>;
|
|
34
38
|
}
|
|
@@ -80,6 +80,13 @@ let ProfilerController = class ProfilerController {
|
|
|
80
80
|
throw new common_1.NotFoundException('Profile not found');
|
|
81
81
|
return profile;
|
|
82
82
|
}
|
|
83
|
+
async summary(res) {
|
|
84
|
+
const stats = await this.profilerService.getSummaryStats();
|
|
85
|
+
const content = this.templateBuilder.buildSummaryPage(stats);
|
|
86
|
+
const html = this.viewService.renderWithLayout('Summary', content, 'summary');
|
|
87
|
+
res.header('Content-Type', 'text/html');
|
|
88
|
+
res.send(html);
|
|
89
|
+
}
|
|
83
90
|
async listQueries(res) {
|
|
84
91
|
const queries = await this.profilerService.getQueriesList();
|
|
85
92
|
const content = this.templateBuilder.buildQueriesList(queries);
|
|
@@ -115,6 +122,57 @@ let ProfilerController = class ProfilerController {
|
|
|
115
122
|
res.header('Content-Type', 'text/html');
|
|
116
123
|
res.send(html);
|
|
117
124
|
}
|
|
125
|
+
async listHttpCalls(res) {
|
|
126
|
+
const calls = await this.profilerService.getHttpCallsList();
|
|
127
|
+
const content = this.templateBuilder.buildHttpCallsList(calls);
|
|
128
|
+
const html = this.viewService.renderWithLayout('Outbound HTTP', content, 'http-calls');
|
|
129
|
+
res.header('Content-Type', 'text/html');
|
|
130
|
+
res.send(html);
|
|
131
|
+
}
|
|
132
|
+
async liveLogsPage(res) {
|
|
133
|
+
const html = this.viewService.renderWithLayout('Live Logs', this.templateBuilder.buildLiveLogsPage(), 'live-logs');
|
|
134
|
+
res.header('Content-Type', 'text/html');
|
|
135
|
+
res.send(html);
|
|
136
|
+
}
|
|
137
|
+
liveLogsStream(res) {
|
|
138
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
139
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
140
|
+
res.setHeader('Connection', 'keep-alive');
|
|
141
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
142
|
+
res.flushHeaders();
|
|
143
|
+
this.profilerService.logEmitter.setMaxListeners(this.profilerService.logEmitter.getMaxListeners() + 1);
|
|
144
|
+
res.write(`data: ${JSON.stringify({ level: 'system', message: 'Stream connected', timestamp: Date.now() })}\n\n`);
|
|
145
|
+
let batch = [];
|
|
146
|
+
let batchTimer = null;
|
|
147
|
+
const flush = () => {
|
|
148
|
+
batchTimer = null;
|
|
149
|
+
if (batch.length === 0 || res.writableEnded)
|
|
150
|
+
return;
|
|
151
|
+
const payload = batch.splice(0);
|
|
152
|
+
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
153
|
+
if (typeof res.flush === 'function')
|
|
154
|
+
res.flush();
|
|
155
|
+
};
|
|
156
|
+
const onLog = (log) => {
|
|
157
|
+
if (res.writableEnded)
|
|
158
|
+
return;
|
|
159
|
+
batch.push(log);
|
|
160
|
+
if (!batchTimer)
|
|
161
|
+
batchTimer = setTimeout(flush, 50);
|
|
162
|
+
};
|
|
163
|
+
const heartbeat = setInterval(() => {
|
|
164
|
+
if (!res.writableEnded)
|
|
165
|
+
res.write(': heartbeat\n\n');
|
|
166
|
+
}, 15_000);
|
|
167
|
+
this.profilerService.logEmitter.on('log', onLog);
|
|
168
|
+
res.on('close', () => {
|
|
169
|
+
clearInterval(heartbeat);
|
|
170
|
+
if (batchTimer)
|
|
171
|
+
clearTimeout(batchTimer);
|
|
172
|
+
this.profilerService.logEmitter.off('log', onLog);
|
|
173
|
+
this.profilerService.logEmitter.setMaxListeners(Math.max(1, this.profilerService.logEmitter.getMaxListeners() - 1));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
118
176
|
async serveAsset(file, res) {
|
|
119
177
|
const fs = require('fs');
|
|
120
178
|
const path = require('path');
|
|
@@ -191,6 +249,13 @@ __decorate([
|
|
|
191
249
|
__metadata("design:paramtypes", [String]),
|
|
192
250
|
__metadata("design:returntype", Promise)
|
|
193
251
|
], ProfilerController.prototype, "detailJson", null);
|
|
252
|
+
__decorate([
|
|
253
|
+
(0, common_1.Get)('view/summary'),
|
|
254
|
+
__param(0, (0, common_1.Res)()),
|
|
255
|
+
__metadata("design:type", Function),
|
|
256
|
+
__metadata("design:paramtypes", [Object]),
|
|
257
|
+
__metadata("design:returntype", Promise)
|
|
258
|
+
], ProfilerController.prototype, "summary", null);
|
|
194
259
|
__decorate([
|
|
195
260
|
(0, common_1.Get)('view/queries'),
|
|
196
261
|
__param(0, (0, common_1.Res)()),
|
|
@@ -227,6 +292,27 @@ __decorate([
|
|
|
227
292
|
__metadata("design:paramtypes", [Object]),
|
|
228
293
|
__metadata("design:returntype", Promise)
|
|
229
294
|
], ProfilerController.prototype, "listCache", null);
|
|
295
|
+
__decorate([
|
|
296
|
+
(0, common_1.Get)('view/http-calls'),
|
|
297
|
+
__param(0, (0, common_1.Res)()),
|
|
298
|
+
__metadata("design:type", Function),
|
|
299
|
+
__metadata("design:paramtypes", [Object]),
|
|
300
|
+
__metadata("design:returntype", Promise)
|
|
301
|
+
], ProfilerController.prototype, "listHttpCalls", null);
|
|
302
|
+
__decorate([
|
|
303
|
+
(0, common_1.Get)('view/logs/live'),
|
|
304
|
+
__param(0, (0, common_1.Res)()),
|
|
305
|
+
__metadata("design:type", Function),
|
|
306
|
+
__metadata("design:paramtypes", [Object]),
|
|
307
|
+
__metadata("design:returntype", Promise)
|
|
308
|
+
], ProfilerController.prototype, "liveLogsPage", null);
|
|
309
|
+
__decorate([
|
|
310
|
+
(0, common_1.Get)('logs/stream'),
|
|
311
|
+
__param(0, (0, common_1.Res)()),
|
|
312
|
+
__metadata("design:type", Function),
|
|
313
|
+
__metadata("design:paramtypes", [Object]),
|
|
314
|
+
__metadata("design:returntype", void 0)
|
|
315
|
+
], ProfilerController.prototype, "liveLogsStream", null);
|
|
230
316
|
__decorate([
|
|
231
317
|
(0, common_1.Get)('assets/:file'),
|
|
232
318
|
__param(0, (0, common_1.Param)('file')),
|