@pioneer-platform/markets 8.12.0 → 8.15.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.
@@ -0,0 +1,72 @@
1
+ import { Db } from 'mongodb';
2
+ export interface APICallLog {
3
+ apiName: string;
4
+ endpoint?: string;
5
+ method?: string;
6
+ caip?: string;
7
+ symbol?: string;
8
+ tokenTier?: number;
9
+ success: boolean;
10
+ responseTime?: number;
11
+ statusCode?: number;
12
+ price?: number;
13
+ error?: string;
14
+ tokensUsed?: number;
15
+ tokensRemaining?: number;
16
+ costUSD?: number;
17
+ wasRateLimited?: boolean;
18
+ wasThrottled?: boolean;
19
+ cacheHit?: boolean;
20
+ timestamp: Date;
21
+ requestId?: string;
22
+ }
23
+ export declare class MetricsLogger {
24
+ private db;
25
+ private logsCollection;
26
+ private enabled;
27
+ constructor(db: Db);
28
+ /**
29
+ * Enable/disable logging (for performance testing)
30
+ */
31
+ setEnabled(enabled: boolean): void;
32
+ /**
33
+ * Log API call
34
+ */
35
+ logAPICall(logData: APICallLog): Promise<void>;
36
+ /**
37
+ * Get hourly stats
38
+ */
39
+ getHourlyStats(hour: Date): Promise<any[]>;
40
+ /**
41
+ * Get daily stats
42
+ */
43
+ getDailyStats(date: Date): Promise<any[]>;
44
+ /**
45
+ * Get stats for date range
46
+ */
47
+ getStatsForRange(startDate: Date, endDate: Date): Promise<any[]>;
48
+ /**
49
+ * Get top requested tokens
50
+ */
51
+ getTopTokens(limit?: number, hours?: number): Promise<any[]>;
52
+ /**
53
+ * Get error rate for API
54
+ */
55
+ getErrorRate(apiName: string, hours?: number): Promise<number>;
56
+ /**
57
+ * Get rate limit events
58
+ */
59
+ getRateLimitEvents(apiName?: string, hours?: number): Promise<any[]>;
60
+ /**
61
+ * Get cost summary for month
62
+ */
63
+ getMonthlyCostSummary(year: number, month: number): Promise<any>;
64
+ /**
65
+ * Clean up old logs (run periodically)
66
+ */
67
+ cleanupOldLogs(daysToKeep?: number): Promise<number>;
68
+ /**
69
+ * Get cache hit rate
70
+ */
71
+ getCacheHitRate(hours?: number): Promise<number>;
72
+ }
@@ -0,0 +1,435 @@
1
+ "use strict";
2
+ /*
3
+ Metrics Logger
4
+
5
+ Logs all API calls to MongoDB for analytics and cost tracking
6
+ */
7
+ var __assign = (this && this.__assign) || function () {
8
+ __assign = Object.assign || function(t) {
9
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
10
+ s = arguments[i];
11
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
12
+ t[p] = s[p];
13
+ }
14
+ return t;
15
+ };
16
+ return __assign.apply(this, arguments);
17
+ };
18
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ };
27
+ var __generator = (this && this.__generator) || function (thisArg, body) {
28
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
29
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
30
+ function verb(n) { return function (v) { return step([n, v]); }; }
31
+ function step(op) {
32
+ if (f) throw new TypeError("Generator is already executing.");
33
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
34
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
35
+ if (y = 0, t) op = [op[0] & 2, t.value];
36
+ switch (op[0]) {
37
+ case 0: case 1: t = op; break;
38
+ case 4: _.label++; return { value: op[1], done: false };
39
+ case 5: _.label++; y = op[1]; op = [0]; continue;
40
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
41
+ default:
42
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
43
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
44
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
45
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
46
+ if (t[2]) _.ops.pop();
47
+ _.trys.pop(); continue;
48
+ }
49
+ op = body.call(thisArg, _);
50
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
51
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
52
+ }
53
+ };
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.MetricsLogger = void 0;
56
+ var log = require('@pioneer-platform/loggerdog')();
57
+ var TAG = ' | MetricsLogger | ';
58
+ var MetricsLogger = /** @class */ (function () {
59
+ function MetricsLogger(db) {
60
+ this.enabled = true;
61
+ this.db = db;
62
+ this.logsCollection = db.collection('api_usage_logs');
63
+ }
64
+ /**
65
+ * Enable/disable logging (for performance testing)
66
+ */
67
+ MetricsLogger.prototype.setEnabled = function (enabled) {
68
+ this.enabled = enabled;
69
+ };
70
+ /**
71
+ * Log API call
72
+ */
73
+ MetricsLogger.prototype.logAPICall = function (logData) {
74
+ return __awaiter(this, void 0, void 0, function () {
75
+ var error_1;
76
+ return __generator(this, function (_a) {
77
+ switch (_a.label) {
78
+ case 0:
79
+ if (!this.enabled)
80
+ return [2 /*return*/];
81
+ _a.label = 1;
82
+ case 1:
83
+ _a.trys.push([1, 3, , 4]);
84
+ return [4 /*yield*/, this.logsCollection.insertOne(__assign(__assign({}, logData), { timestamp: logData.timestamp || new Date() }))];
85
+ case 2:
86
+ _a.sent();
87
+ return [3 /*break*/, 4];
88
+ case 3:
89
+ error_1 = _a.sent();
90
+ // Don't block on metrics errors, but log them
91
+ log.debug(TAG, "Failed to log API metrics: ".concat(error_1.message));
92
+ return [3 /*break*/, 4];
93
+ case 4: return [2 /*return*/];
94
+ }
95
+ });
96
+ });
97
+ };
98
+ /**
99
+ * Get hourly stats
100
+ */
101
+ MetricsLogger.prototype.getHourlyStats = function (hour) {
102
+ return __awaiter(this, void 0, void 0, function () {
103
+ var hourStart, hourEnd, pipeline;
104
+ return __generator(this, function (_a) {
105
+ switch (_a.label) {
106
+ case 0:
107
+ hourStart = new Date(hour);
108
+ hourStart.setMinutes(0, 0, 0);
109
+ hourEnd = new Date(hourStart);
110
+ hourEnd.setHours(hourStart.getHours() + 1);
111
+ pipeline = [
112
+ {
113
+ $match: {
114
+ timestamp: { $gte: hourStart, $lt: hourEnd }
115
+ }
116
+ },
117
+ {
118
+ $group: {
119
+ _id: '$apiName',
120
+ requestCount: { $sum: 1 },
121
+ successCount: { $sum: { $cond: ['$success', 1, 0] } },
122
+ errorCount: { $sum: { $cond: ['$success', 0, 1] } },
123
+ rateLimitCount: { $sum: { $cond: ['$wasRateLimited', 1, 0] } },
124
+ tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
125
+ totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
126
+ avgResponseTime: { $avg: '$responseTime' }
127
+ }
128
+ }
129
+ ];
130
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
131
+ case 1: return [2 /*return*/, _a.sent()];
132
+ }
133
+ });
134
+ });
135
+ };
136
+ /**
137
+ * Get daily stats
138
+ */
139
+ MetricsLogger.prototype.getDailyStats = function (date) {
140
+ return __awaiter(this, void 0, void 0, function () {
141
+ var dayStart, dayEnd, pipeline;
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0:
145
+ dayStart = new Date(date);
146
+ dayStart.setHours(0, 0, 0, 0);
147
+ dayEnd = new Date(dayStart);
148
+ dayEnd.setDate(dayStart.getDate() + 1);
149
+ pipeline = [
150
+ {
151
+ $match: {
152
+ timestamp: { $gte: dayStart, $lt: dayEnd }
153
+ }
154
+ },
155
+ {
156
+ $group: {
157
+ _id: '$apiName',
158
+ requestCount: { $sum: 1 },
159
+ successCount: { $sum: { $cond: ['$success', 1, 0] } },
160
+ errorCount: { $sum: { $cond: ['$success', 0, 1] } },
161
+ rateLimitCount: { $sum: { $cond: ['$wasRateLimited', 1, 0] } },
162
+ tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
163
+ totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
164
+ avgResponseTime: { $avg: '$responseTime' }
165
+ }
166
+ }
167
+ ];
168
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
169
+ case 1: return [2 /*return*/, _a.sent()];
170
+ }
171
+ });
172
+ });
173
+ };
174
+ /**
175
+ * Get stats for date range
176
+ */
177
+ MetricsLogger.prototype.getStatsForRange = function (startDate, endDate) {
178
+ return __awaiter(this, void 0, void 0, function () {
179
+ var pipeline;
180
+ return __generator(this, function (_a) {
181
+ switch (_a.label) {
182
+ case 0:
183
+ pipeline = [
184
+ {
185
+ $match: {
186
+ timestamp: { $gte: startDate, $lt: endDate }
187
+ }
188
+ },
189
+ {
190
+ $group: {
191
+ _id: {
192
+ apiName: '$apiName',
193
+ date: { $dateToString: { format: '%Y-%m-%d', date: '$timestamp' } }
194
+ },
195
+ requestCount: { $sum: 1 },
196
+ successCount: { $sum: { $cond: ['$success', 1, 0] } },
197
+ tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } },
198
+ totalCost: { $sum: { $ifNull: ['$costUSD', 0] } }
199
+ }
200
+ },
201
+ {
202
+ $sort: { '_id.date': -1, '_id.apiName': 1 }
203
+ }
204
+ ];
205
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
206
+ case 1: return [2 /*return*/, _a.sent()];
207
+ }
208
+ });
209
+ });
210
+ };
211
+ /**
212
+ * Get top requested tokens
213
+ */
214
+ MetricsLogger.prototype.getTopTokens = function () {
215
+ return __awaiter(this, arguments, void 0, function (limit, hours) {
216
+ var since, pipeline;
217
+ if (limit === void 0) { limit = 10; }
218
+ if (hours === void 0) { hours = 24; }
219
+ return __generator(this, function (_a) {
220
+ switch (_a.label) {
221
+ case 0:
222
+ since = new Date(Date.now() - hours * 60 * 60 * 1000);
223
+ pipeline = [
224
+ {
225
+ $match: {
226
+ caip: { $exists: true },
227
+ timestamp: { $gte: since }
228
+ }
229
+ },
230
+ {
231
+ $group: {
232
+ _id: '$caip',
233
+ symbol: { $first: '$symbol' },
234
+ requestCount: { $sum: 1 },
235
+ tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } }
236
+ }
237
+ },
238
+ {
239
+ $sort: { requestCount: -1 }
240
+ },
241
+ {
242
+ $limit: limit
243
+ }
244
+ ];
245
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
246
+ case 1: return [2 /*return*/, _a.sent()];
247
+ }
248
+ });
249
+ });
250
+ };
251
+ /**
252
+ * Get error rate for API
253
+ */
254
+ MetricsLogger.prototype.getErrorRate = function (apiName_1) {
255
+ return __awaiter(this, arguments, void 0, function (apiName, hours) {
256
+ var since, pipeline, result, _a, total, errors;
257
+ if (hours === void 0) { hours = 24; }
258
+ return __generator(this, function (_b) {
259
+ switch (_b.label) {
260
+ case 0:
261
+ since = new Date(Date.now() - hours * 60 * 60 * 1000);
262
+ pipeline = [
263
+ {
264
+ $match: {
265
+ apiName: apiName,
266
+ timestamp: { $gte: since }
267
+ }
268
+ },
269
+ {
270
+ $group: {
271
+ _id: null,
272
+ total: { $sum: 1 },
273
+ errors: { $sum: { $cond: ['$success', 0, 1] } }
274
+ }
275
+ }
276
+ ];
277
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
278
+ case 1:
279
+ result = _b.sent();
280
+ if (result.length === 0)
281
+ return [2 /*return*/, 0];
282
+ _a = result[0], total = _a.total, errors = _a.errors;
283
+ return [2 /*return*/, errors / total];
284
+ }
285
+ });
286
+ });
287
+ };
288
+ /**
289
+ * Get rate limit events
290
+ */
291
+ MetricsLogger.prototype.getRateLimitEvents = function (apiName_1) {
292
+ return __awaiter(this, arguments, void 0, function (apiName, hours) {
293
+ var since, match;
294
+ if (hours === void 0) { hours = 24; }
295
+ return __generator(this, function (_a) {
296
+ switch (_a.label) {
297
+ case 0:
298
+ since = new Date(Date.now() - hours * 60 * 60 * 1000);
299
+ match = {
300
+ wasRateLimited: true,
301
+ timestamp: { $gte: since }
302
+ };
303
+ if (apiName) {
304
+ match.apiName = apiName;
305
+ }
306
+ return [4 /*yield*/, this.logsCollection
307
+ .find(match)
308
+ .sort({ timestamp: -1 })
309
+ .limit(100)
310
+ .toArray()];
311
+ case 1: return [2 /*return*/, _a.sent()];
312
+ }
313
+ });
314
+ });
315
+ };
316
+ /**
317
+ * Get cost summary for month
318
+ */
319
+ MetricsLogger.prototype.getMonthlyCostSummary = function (year, month) {
320
+ return __awaiter(this, void 0, void 0, function () {
321
+ var startDate, endDate, pipeline, results, totalCost, totalRequests, totalTokens, byAPI, _i, results_1, result, apiName;
322
+ return __generator(this, function (_a) {
323
+ switch (_a.label) {
324
+ case 0:
325
+ startDate = new Date(year, month - 1, 1);
326
+ endDate = new Date(year, month, 1);
327
+ pipeline = [
328
+ {
329
+ $match: {
330
+ timestamp: { $gte: startDate, $lt: endDate }
331
+ }
332
+ },
333
+ {
334
+ $group: {
335
+ _id: '$apiName',
336
+ totalCost: { $sum: { $ifNull: ['$costUSD', 0] } },
337
+ requestCount: { $sum: 1 },
338
+ tokensUsed: { $sum: { $ifNull: ['$tokensUsed', 0] } }
339
+ }
340
+ }
341
+ ];
342
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
343
+ case 1:
344
+ results = _a.sent();
345
+ totalCost = 0;
346
+ totalRequests = 0;
347
+ totalTokens = 0;
348
+ byAPI = {};
349
+ for (_i = 0, results_1 = results; _i < results_1.length; _i++) {
350
+ result = results_1[_i];
351
+ apiName = result._id;
352
+ byAPI[apiName] = {
353
+ cost: result.totalCost,
354
+ requests: result.requestCount,
355
+ tokens: result.tokensUsed
356
+ };
357
+ totalCost += result.totalCost;
358
+ totalRequests += result.requestCount;
359
+ totalTokens += result.tokensUsed;
360
+ }
361
+ return [2 /*return*/, {
362
+ month: "".concat(year, "-").concat(String(month).padStart(2, '0')),
363
+ byAPI: byAPI,
364
+ totals: {
365
+ cost: totalCost,
366
+ requests: totalRequests,
367
+ tokens: totalTokens
368
+ }
369
+ }];
370
+ }
371
+ });
372
+ });
373
+ };
374
+ /**
375
+ * Clean up old logs (run periodically)
376
+ */
377
+ MetricsLogger.prototype.cleanupOldLogs = function () {
378
+ return __awaiter(this, arguments, void 0, function (daysToKeep) {
379
+ var tag, cutoffDate, result;
380
+ if (daysToKeep === void 0) { daysToKeep = 90; }
381
+ return __generator(this, function (_a) {
382
+ switch (_a.label) {
383
+ case 0:
384
+ tag = TAG + ' | cleanupOldLogs | ';
385
+ cutoffDate = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000);
386
+ return [4 /*yield*/, this.logsCollection.deleteMany({
387
+ timestamp: { $lt: cutoffDate }
388
+ })];
389
+ case 1:
390
+ result = _a.sent();
391
+ log.info(tag, "Deleted ".concat(result.deletedCount, " logs older than ").concat(daysToKeep, " days"));
392
+ return [2 /*return*/, result.deletedCount];
393
+ }
394
+ });
395
+ });
396
+ };
397
+ /**
398
+ * Get cache hit rate
399
+ */
400
+ MetricsLogger.prototype.getCacheHitRate = function () {
401
+ return __awaiter(this, arguments, void 0, function (hours) {
402
+ var since, pipeline, result, _a, total, cacheHits;
403
+ if (hours === void 0) { hours = 24; }
404
+ return __generator(this, function (_b) {
405
+ switch (_b.label) {
406
+ case 0:
407
+ since = new Date(Date.now() - hours * 60 * 60 * 1000);
408
+ pipeline = [
409
+ {
410
+ $match: {
411
+ timestamp: { $gte: since }
412
+ }
413
+ },
414
+ {
415
+ $group: {
416
+ _id: null,
417
+ total: { $sum: 1 },
418
+ cacheHits: { $sum: { $cond: ['$cacheHit', 1, 0] } }
419
+ }
420
+ }
421
+ ];
422
+ return [4 /*yield*/, this.logsCollection.aggregate(pipeline).toArray()];
423
+ case 1:
424
+ result = _b.sent();
425
+ if (result.length === 0)
426
+ return [2 /*return*/, 0];
427
+ _a = result[0], total = _a.total, cacheHits = _a.cacheHits;
428
+ return [2 /*return*/, cacheHits / total];
429
+ }
430
+ });
431
+ });
432
+ };
433
+ return MetricsLogger;
434
+ }());
435
+ exports.MetricsLogger = MetricsLogger;
@@ -0,0 +1,22 @@
1
+ import { TokenBucketManager } from './token-bucket-manager';
2
+ import { MetricsLogger } from './metrics-logger';
3
+ /**
4
+ * Schedule daily token bucket refill at midnight UTC
5
+ */
6
+ export declare function scheduleDailyRefill(manager: TokenBucketManager, metricsLogger?: MetricsLogger): void;
7
+ /**
8
+ * Manual refill trigger (for admin use)
9
+ */
10
+ export declare function manualRefill(manager: TokenBucketManager, metricsLogger?: MetricsLogger): Promise<void>;
11
+ /**
12
+ * Cancel scheduled refill job
13
+ */
14
+ export declare function cancelRefillSchedule(): void;
15
+ /**
16
+ * Get next refill time
17
+ */
18
+ export declare function getNextRefillTime(): Date | null;
19
+ /**
20
+ * Check if refill is scheduled
21
+ */
22
+ export declare function isRefillScheduled(): boolean;