express-sequelize-traffic 0.2.0 → 0.4.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 +179 -4
- package/dist/index.cjs +28 -6
- package/dist/index.js +28 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,9 +30,9 @@ npm install
|
|
|
30
30
|
npm run build
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Quick Setup
|
|
34
34
|
|
|
35
|
-
The package defines a `TrafficLog` model for you. You can either manage the table through your own migrations or call `traffic.sync()` for quick setup.
|
|
35
|
+
The package defines a `TrafficLog` model for you. You can either manage the table through your own migrations or call `traffic.sync()` for quick setup. `traffic.sync()` wraps Sequelize model sync and creates the `traffic_logs` table if it does not already exist.
|
|
36
36
|
|
|
37
37
|
```js
|
|
38
38
|
import { Sequelize } from "sequelize";
|
|
@@ -46,7 +46,9 @@ await sequelize.authenticate();
|
|
|
46
46
|
await traffic.sync();
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
##
|
|
49
|
+
## ES Module Setup
|
|
50
|
+
|
|
51
|
+
Use this form when the target app uses `"type": "module"` or native ESM imports.
|
|
50
52
|
|
|
51
53
|
```js
|
|
52
54
|
import express from "express";
|
|
@@ -63,6 +65,7 @@ const traffic = createTrafficTracker({
|
|
|
63
65
|
getUserId: (req) => req.user?.id || req.headers["x-user-id"],
|
|
64
66
|
getSessionId: (req) => req.sessionID || req.headers["x-session-id"],
|
|
65
67
|
slowRouteThresholdMs: 1000,
|
|
68
|
+
includeRoutes: ["/api/*"],
|
|
66
69
|
trackIp: false,
|
|
67
70
|
trackUserAgent: true,
|
|
68
71
|
ignoredRoutes: ["/health", "/favicon.ico"],
|
|
@@ -83,15 +86,186 @@ app.use("/traffic-dashboard", traffic.dashboard);
|
|
|
83
86
|
server.listen(3000);
|
|
84
87
|
```
|
|
85
88
|
|
|
86
|
-
CommonJS
|
|
89
|
+
## CommonJS Setup
|
|
90
|
+
|
|
91
|
+
The package also publishes a CommonJS entry, so `require(...)` works directly.
|
|
87
92
|
|
|
88
93
|
```js
|
|
89
94
|
const express = require("express");
|
|
90
95
|
const http = require("node:http");
|
|
91
96
|
const { Sequelize } = require("sequelize");
|
|
92
97
|
const { createTrafficTracker } = require("express-sequelize-traffic");
|
|
98
|
+
|
|
99
|
+
const app = express();
|
|
100
|
+
const server = http.createServer(app);
|
|
101
|
+
|
|
102
|
+
const sequelize = new Sequelize(process.env.DB_URL, {
|
|
103
|
+
logging: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
async function start() {
|
|
107
|
+
const traffic = createTrafficTracker({
|
|
108
|
+
sequelize,
|
|
109
|
+
getUserId: (req) => req.user?.id || req.headers["x-user-id"] || null,
|
|
110
|
+
getSessionId: (req) => req.sessionID || req.headers["x-session-id"] || null,
|
|
111
|
+
slowRouteThresholdMs: 1000,
|
|
112
|
+
includeRoutes: ["/api/*"],
|
|
113
|
+
ignoredRoutes: ["/health", "/favicon.ico"],
|
|
114
|
+
trackIp: false,
|
|
115
|
+
trackUserAgent: true,
|
|
116
|
+
dashboard: {
|
|
117
|
+
enabled: true,
|
|
118
|
+
mountPath: "/traffic-dashboard",
|
|
119
|
+
username: process.env.TRAFFIC_ADMIN_USER,
|
|
120
|
+
password: process.env.TRAFFIC_ADMIN_PASS,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await sequelize.authenticate();
|
|
125
|
+
await traffic.sync();
|
|
126
|
+
|
|
127
|
+
app.use(traffic.middleware);
|
|
128
|
+
traffic.attachRealtime(server);
|
|
129
|
+
app.use("/traffic-dashboard", traffic.dashboard);
|
|
130
|
+
|
|
131
|
+
app.get("/health", (_req, res) => {
|
|
132
|
+
res.json({ ok: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
server.listen(3000);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
start().catch((error) => {
|
|
139
|
+
console.error("Startup failed:", error);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Passing User Details
|
|
145
|
+
|
|
146
|
+
The package does not assume how authentication works in your app. You pass user and session details through resolver functions.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const traffic = createTrafficTracker({
|
|
150
|
+
sequelize,
|
|
151
|
+
getUserId: (req) =>
|
|
152
|
+
req.user?.id || req.auth?.sub || req.headers["x-user-id"] || null,
|
|
153
|
+
getSessionId: (req) =>
|
|
154
|
+
req.sessionID || req.cookies?.sessionId || req.headers["x-session-id"] || null,
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Common sources are:
|
|
159
|
+
|
|
160
|
+
- `req.user.id` from Passport or your own auth middleware
|
|
161
|
+
- `req.auth.sub` from JWT middleware
|
|
162
|
+
- `req.sessionID` from `express-session`
|
|
163
|
+
- custom request headers in internal systems
|
|
164
|
+
|
|
165
|
+
Rules to follow:
|
|
166
|
+
|
|
167
|
+
- Register auth and session middleware before `traffic.middleware`
|
|
168
|
+
- Return `null` when user or session data is unavailable
|
|
169
|
+
- If you omit `getUserId` or `getSessionId`, those fields are stored as `NULL`
|
|
170
|
+
|
|
171
|
+
## Why It Tracks More Than Application Routes
|
|
172
|
+
|
|
173
|
+
This package is request middleware. If you mount it globally with:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
app.use(traffic.middleware);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
it will see every request that reaches that point in the Express stack, not just your business endpoints.
|
|
180
|
+
|
|
181
|
+
That includes:
|
|
182
|
+
|
|
183
|
+
- API routes
|
|
184
|
+
- page routes
|
|
185
|
+
- health checks
|
|
186
|
+
- static files
|
|
187
|
+
- 404 requests
|
|
188
|
+
- dashboard API requests
|
|
189
|
+
- dashboard frontend asset requests
|
|
190
|
+
|
|
191
|
+
Internally, the tracker records the route from `req.route?.path`, then falls back to `req.path`, then `req.originalUrl`. That is why it can still log requests even when Express does not resolve them to a named application route.
|
|
192
|
+
|
|
193
|
+
## How To Track Only Application Routes
|
|
194
|
+
|
|
195
|
+
If you want to limit tracking to business endpoints, start with `includeRoutes`. It is usually easier than maintaining a long `ignoredRoutes` list.
|
|
196
|
+
|
|
197
|
+
Track only route families you want:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
const traffic = createTrafficTracker({
|
|
201
|
+
sequelize,
|
|
202
|
+
includeRoutes: ["/api/*", "/admin/*"],
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
String matchers support:
|
|
207
|
+
|
|
208
|
+
- exact matches like `"/health"`
|
|
209
|
+
- prefix-style wildcards like `"/api/*"`
|
|
210
|
+
- regex matchers like `/^\/v[0-9]+\//`
|
|
211
|
+
- custom functions
|
|
212
|
+
|
|
213
|
+
If `includeRoutes` is provided, only matching requests are tracked. You can still use `ignoredRoutes` to exclude specific subsets from a broader include rule.
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
const traffic = createTrafficTracker({
|
|
219
|
+
sequelize,
|
|
220
|
+
includeRoutes: ["/api/*"],
|
|
221
|
+
ignoredRoutes: ["/api/internal/*"],
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
You can also use one or more of these mounting patterns.
|
|
226
|
+
|
|
227
|
+
Mount the tracker only on the route prefix you care about:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
app.use("/api", traffic.middleware);
|
|
93
231
|
```
|
|
94
232
|
|
|
233
|
+
Register static assets or health checks before the tracker:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
app.use("/public", express.static("public"));
|
|
237
|
+
app.get("/health", (_req, res) => res.json({ ok: true }));
|
|
238
|
+
|
|
239
|
+
app.use(traffic.middleware);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Ignore route groups with regex:
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
const traffic = createTrafficTracker({
|
|
246
|
+
sequelize,
|
|
247
|
+
ignoredRoutes: [
|
|
248
|
+
"/health",
|
|
249
|
+
"/favicon.ico",
|
|
250
|
+
/^\/traffic-dashboard(\/|$)/,
|
|
251
|
+
/^\/public(\/|$)/,
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Use a function when the skip logic depends on route naming conventions:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
const traffic = createTrafficTracker({
|
|
260
|
+
sequelize,
|
|
261
|
+
ignoredRoutes: [
|
|
262
|
+
(route) => route.startsWith("/internal/"),
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
If you mount the tracker globally, global tracking is expected behavior.
|
|
268
|
+
|
|
95
269
|
## Dashboard Usage
|
|
96
270
|
|
|
97
271
|
The dashboard is optional. Set `dashboard.enabled` to `true`, build the frontend assets, attach realtime support, and mount the provided router.
|
|
@@ -110,6 +284,7 @@ npm run build:dashboard
|
|
|
110
284
|
| `getUserId` | `(req) => string \| null` | `undefined` | Custom resolver for `userId` |
|
|
111
285
|
| `getSessionId` | `(req) => string \| null` | `undefined` | Custom resolver for `sessionId` |
|
|
112
286
|
| `slowRouteThresholdMs` | `number` | `1000` | Marks requests as slow when duration meets or exceeds this value |
|
|
287
|
+
| `includeRoutes` | `Array<string \| RegExp \| Function>` | `[]` | If set, only matching routes or URLs are tracked |
|
|
113
288
|
| `ignoredRoutes` | `Array<string \| RegExp \| Function>` | `[]` | Routes or URLs to skip |
|
|
114
289
|
| `trackIp` | `boolean` | `false` | Persist `req.ip` when enabled |
|
|
115
290
|
| `trackUserAgent` | `boolean` | `true` | Persist the request `user-agent` header when enabled |
|
package/dist/index.cjs
CHANGED
|
@@ -147,7 +147,7 @@ function resolveTrackedRoute(req) {
|
|
|
147
147
|
}
|
|
148
148
|
return routePath || req.path || req.originalUrl || "/";
|
|
149
149
|
}
|
|
150
|
-
function
|
|
150
|
+
function matchesRouteMatcher(matcher, candidate) {
|
|
151
151
|
if (!candidate) {
|
|
152
152
|
return false;
|
|
153
153
|
}
|
|
@@ -158,18 +158,32 @@ function matchesIgnoredRoute(matcher, candidate) {
|
|
|
158
158
|
return Boolean(matcher(candidate));
|
|
159
159
|
}
|
|
160
160
|
if (typeof matcher === "string") {
|
|
161
|
+
if (matcher.endsWith("/*")) {
|
|
162
|
+
const prefix = matcher.slice(0, -2);
|
|
163
|
+
if (!prefix || prefix === "/") {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
return candidate === prefix || candidate.startsWith(`${prefix}/`) || candidate.startsWith(`${prefix}?`);
|
|
167
|
+
}
|
|
161
168
|
return candidate === matcher || candidate.startsWith(`${matcher}?`);
|
|
162
169
|
}
|
|
163
170
|
return false;
|
|
164
171
|
}
|
|
165
|
-
function
|
|
172
|
+
function matchesRouteList(matchers, route, originalUrl) {
|
|
173
|
+
return matchers.some(
|
|
174
|
+
(matcher) => matchesRouteMatcher(matcher, route) || matchesRouteMatcher(matcher, originalUrl)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function shouldTrackRoute({
|
|
166
178
|
route,
|
|
167
179
|
originalUrl,
|
|
180
|
+
includeRoutes = [],
|
|
168
181
|
ignoredRoutes = []
|
|
169
182
|
}) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
if (includeRoutes.length > 0 && !matchesRouteList(includeRoutes, route, originalUrl)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return !matchesRouteList(ignoredRoutes, route, originalUrl);
|
|
173
187
|
}
|
|
174
188
|
|
|
175
189
|
// src/middleware.js
|
|
@@ -223,6 +237,7 @@ function createTrackingMiddleware({
|
|
|
223
237
|
getUserId,
|
|
224
238
|
getSessionId,
|
|
225
239
|
slowRouteThresholdMs = 1e3,
|
|
240
|
+
includeRoutes = [],
|
|
226
241
|
ignoredRoutes = [],
|
|
227
242
|
trackIp = false,
|
|
228
243
|
trackUserAgent = true,
|
|
@@ -235,7 +250,12 @@ function createTrackingMiddleware({
|
|
|
235
250
|
const endedAt = /* @__PURE__ */ new Date();
|
|
236
251
|
const route = resolveTrackedRoute(req);
|
|
237
252
|
const originalUrl = req.originalUrl || route;
|
|
238
|
-
if (
|
|
253
|
+
if (!shouldTrackRoute({
|
|
254
|
+
route,
|
|
255
|
+
originalUrl,
|
|
256
|
+
includeRoutes,
|
|
257
|
+
ignoredRoutes
|
|
258
|
+
})) {
|
|
239
259
|
return;
|
|
240
260
|
}
|
|
241
261
|
void safeAsync(
|
|
@@ -828,6 +848,7 @@ function createTrafficTracker(options = {}) {
|
|
|
828
848
|
getUserId,
|
|
829
849
|
getSessionId,
|
|
830
850
|
slowRouteThresholdMs = 1e3,
|
|
851
|
+
includeRoutes = [],
|
|
831
852
|
ignoredRoutes = [],
|
|
832
853
|
trackIp = false,
|
|
833
854
|
trackUserAgent = true,
|
|
@@ -848,6 +869,7 @@ function createTrafficTracker(options = {}) {
|
|
|
848
869
|
getUserId,
|
|
849
870
|
getSessionId,
|
|
850
871
|
slowRouteThresholdMs,
|
|
872
|
+
includeRoutes,
|
|
851
873
|
ignoredRoutes,
|
|
852
874
|
trackIp,
|
|
853
875
|
trackUserAgent,
|
package/dist/index.js
CHANGED
|
@@ -112,7 +112,7 @@ function resolveTrackedRoute(req) {
|
|
|
112
112
|
}
|
|
113
113
|
return routePath || req.path || req.originalUrl || "/";
|
|
114
114
|
}
|
|
115
|
-
function
|
|
115
|
+
function matchesRouteMatcher(matcher, candidate) {
|
|
116
116
|
if (!candidate) {
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
@@ -123,18 +123,32 @@ function matchesIgnoredRoute(matcher, candidate) {
|
|
|
123
123
|
return Boolean(matcher(candidate));
|
|
124
124
|
}
|
|
125
125
|
if (typeof matcher === "string") {
|
|
126
|
+
if (matcher.endsWith("/*")) {
|
|
127
|
+
const prefix = matcher.slice(0, -2);
|
|
128
|
+
if (!prefix || prefix === "/") {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return candidate === prefix || candidate.startsWith(`${prefix}/`) || candidate.startsWith(`${prefix}?`);
|
|
132
|
+
}
|
|
126
133
|
return candidate === matcher || candidate.startsWith(`${matcher}?`);
|
|
127
134
|
}
|
|
128
135
|
return false;
|
|
129
136
|
}
|
|
130
|
-
function
|
|
137
|
+
function matchesRouteList(matchers, route, originalUrl) {
|
|
138
|
+
return matchers.some(
|
|
139
|
+
(matcher) => matchesRouteMatcher(matcher, route) || matchesRouteMatcher(matcher, originalUrl)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function shouldTrackRoute({
|
|
131
143
|
route,
|
|
132
144
|
originalUrl,
|
|
145
|
+
includeRoutes = [],
|
|
133
146
|
ignoredRoutes = []
|
|
134
147
|
}) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
if (includeRoutes.length > 0 && !matchesRouteList(includeRoutes, route, originalUrl)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return !matchesRouteList(ignoredRoutes, route, originalUrl);
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
// src/middleware.js
|
|
@@ -188,6 +202,7 @@ function createTrackingMiddleware({
|
|
|
188
202
|
getUserId,
|
|
189
203
|
getSessionId,
|
|
190
204
|
slowRouteThresholdMs = 1e3,
|
|
205
|
+
includeRoutes = [],
|
|
191
206
|
ignoredRoutes = [],
|
|
192
207
|
trackIp = false,
|
|
193
208
|
trackUserAgent = true,
|
|
@@ -200,7 +215,12 @@ function createTrackingMiddleware({
|
|
|
200
215
|
const endedAt = /* @__PURE__ */ new Date();
|
|
201
216
|
const route = resolveTrackedRoute(req);
|
|
202
217
|
const originalUrl = req.originalUrl || route;
|
|
203
|
-
if (
|
|
218
|
+
if (!shouldTrackRoute({
|
|
219
|
+
route,
|
|
220
|
+
originalUrl,
|
|
221
|
+
includeRoutes,
|
|
222
|
+
ignoredRoutes
|
|
223
|
+
})) {
|
|
204
224
|
return;
|
|
205
225
|
}
|
|
206
226
|
void safeAsync(
|
|
@@ -793,6 +813,7 @@ function createTrafficTracker(options = {}) {
|
|
|
793
813
|
getUserId,
|
|
794
814
|
getSessionId,
|
|
795
815
|
slowRouteThresholdMs = 1e3,
|
|
816
|
+
includeRoutes = [],
|
|
796
817
|
ignoredRoutes = [],
|
|
797
818
|
trackIp = false,
|
|
798
819
|
trackUserAgent = true,
|
|
@@ -813,6 +834,7 @@ function createTrafficTracker(options = {}) {
|
|
|
813
834
|
getUserId,
|
|
814
835
|
getSessionId,
|
|
815
836
|
slowRouteThresholdMs,
|
|
837
|
+
includeRoutes,
|
|
816
838
|
ignoredRoutes,
|
|
817
839
|
trackIp,
|
|
818
840
|
trackUserAgent,
|
package/package.json
CHANGED