@qiaolei81/copilot-session-viewer 0.3.2 → 0.3.3
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/AGENTS.md +109 -0
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +104 -0
- package/RELEASE.md +146 -0
- package/docs/API.md +471 -0
- package/docs/DEVELOPMENT.md +556 -0
- package/docs/INSTALLATION.md +329 -0
- package/docs/README.md +102 -0
- package/docs/TROUBLESHOOTING.md +630 -0
- package/docs/images/homepage.png +0 -0
- package/docs/images/session-detail.png +0 -0
- package/docs/images/time-analysis.png +0 -0
- package/docs/unified-event-format-design.md +844 -0
- package/docs/unified-event-format-implementation.md +350 -0
- package/eslint.config.mjs +133 -0
- package/package.json +10 -4
- package/public/js/homepage.min.js +35 -0
- package/public/js/session-detail.min.js +461 -0
- package/public/js/telemetry-browser.min.js +1 -0
- package/public/js/time-analyze.min.js +518 -0
- package/scripts/release.sh +43 -0
- package/server.js +3 -0
- package/src/app.js +2 -1
- package/src/controllers/insightController.js +31 -0
- package/src/controllers/sessionController.js +56 -0
- package/src/controllers/tagController.js +8 -0
- package/src/controllers/uploadController.js +32 -1
- package/src/middleware/common.js +20 -1
- package/src/telemetry.js +152 -0
- package/views/index.ejs +9 -494
- package/views/session-vue.ejs +166 -1869
- package/views/telemetry-snippet.ejs +26 -0
- package/views/time-analyze.ejs +2 -2217
- package/.env.example +0 -14
package/src/app.js
CHANGED
|
@@ -9,7 +9,7 @@ const config = require('./config');
|
|
|
9
9
|
// Middleware
|
|
10
10
|
// Rate limiting disabled for local development
|
|
11
11
|
// const { globalLimiter, insightGenerationLimiter, insightAccessLimiter, uploadLimiter } = require('./middleware/rateLimiting');
|
|
12
|
-
const { requestTimeout, developmentCors, errorHandler, notFoundHandler } = require('./middleware/common');
|
|
12
|
+
const { requestTimeout, developmentCors, errorHandler, notFoundHandler, telemetryLocals } = require('./middleware/common');
|
|
13
13
|
|
|
14
14
|
// Controllers
|
|
15
15
|
const SessionController = require('./controllers/sessionController');
|
|
@@ -68,6 +68,7 @@ function createApp(options = {}) {
|
|
|
68
68
|
app.use(express.json({ limit: '1mb' }));
|
|
69
69
|
app.use(express.urlencoded({ extended: true }));
|
|
70
70
|
app.use(requestTimeout);
|
|
71
|
+
app.use(telemetryLocals);
|
|
71
72
|
|
|
72
73
|
// CORS in development
|
|
73
74
|
if (config.NODE_ENV === 'development') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const InsightService = require('../services/insightService');
|
|
2
2
|
const { isValidSessionId } = require('../utils/helpers');
|
|
3
|
+
const { trackEvent, trackMetric, trackException } = require('../telemetry');
|
|
3
4
|
|
|
4
5
|
class InsightController {
|
|
5
6
|
constructor(insightService = null, sessionService = null) {
|
|
@@ -39,10 +40,30 @@ class InsightController {
|
|
|
39
40
|
return res.status(400).json({ error: 'Session directory not available' });
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
const startTime = Date.now();
|
|
42
44
|
const result = await this.insightService.generateInsight(session.id, session.directory, session.source, forceRegenerate);
|
|
45
|
+
const durationMs = Date.now() - startTime;
|
|
46
|
+
|
|
47
|
+
// Track InsightGenerated event
|
|
48
|
+
trackEvent('InsightGenerated', {
|
|
49
|
+
sessionId,
|
|
50
|
+
source: session.source || 'unknown',
|
|
51
|
+
durationMs: durationMs.toString()
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Track InsightGenerationTime metric
|
|
55
|
+
trackMetric('InsightGenerationTime', durationMs, { sessionId, source: session.source || 'unknown' });
|
|
56
|
+
|
|
43
57
|
res.json(result);
|
|
44
58
|
} catch (err) {
|
|
45
59
|
console.error('Error generating insight:', err);
|
|
60
|
+
|
|
61
|
+
// Track insight generation failure
|
|
62
|
+
trackException(err, {
|
|
63
|
+
sessionId: req.params.id,
|
|
64
|
+
operation: 'generateInsight'
|
|
65
|
+
});
|
|
66
|
+
|
|
46
67
|
res.status(500).json({ error: err.message || 'Error generating insight' });
|
|
47
68
|
}
|
|
48
69
|
}
|
|
@@ -67,6 +88,12 @@ class InsightController {
|
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
const result = await this.insightService.getInsightStatus(session.id, session.directory, session.source);
|
|
91
|
+
|
|
92
|
+
// Track InsightViewed event if insight is ready
|
|
93
|
+
if (result.status === 'ready' && result.report) {
|
|
94
|
+
trackEvent('InsightViewed', { sessionId });
|
|
95
|
+
}
|
|
96
|
+
|
|
70
97
|
res.json(result);
|
|
71
98
|
} catch (err) {
|
|
72
99
|
console.error('Error getting insight status:', err);
|
|
@@ -94,6 +121,10 @@ class InsightController {
|
|
|
94
121
|
}
|
|
95
122
|
|
|
96
123
|
const result = await this.insightService.deleteInsight(session.id, session.directory, session.source);
|
|
124
|
+
|
|
125
|
+
// Track InsightDeleted event
|
|
126
|
+
trackEvent('InsightDeleted', { sessionId });
|
|
127
|
+
|
|
97
128
|
res.json(result);
|
|
98
129
|
} catch (err) {
|
|
99
130
|
console.error('Error deleting insight:', err);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const SessionService = require('../services/sessionService');
|
|
2
2
|
const { isValidSessionId, buildMetadata } = require('../utils/helpers');
|
|
3
|
+
const { trackEvent, trackMetric } = require('../telemetry');
|
|
3
4
|
const AdmZip = require('adm-zip');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const fs = require('fs');
|
|
@@ -40,6 +41,12 @@ class SessionController {
|
|
|
40
41
|
sourceHints: JSON.stringify(sourceHints)
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
// Track HomepageViewed event
|
|
45
|
+
trackEvent('HomepageViewed', {
|
|
46
|
+
sessionCount: paginationData.totalSessions.toString(),
|
|
47
|
+
sourceFilter: 'copilot'
|
|
48
|
+
});
|
|
49
|
+
|
|
43
50
|
res.render('index', templateData);
|
|
44
51
|
} catch (err) {
|
|
45
52
|
console.error('Error loading sessions:', err);
|
|
@@ -62,6 +69,29 @@ class SessionController {
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
const metadata = buildMetadata(session);
|
|
72
|
+
|
|
73
|
+
// Track SessionViewed event
|
|
74
|
+
trackEvent('SessionViewed', {
|
|
75
|
+
sessionId,
|
|
76
|
+
source: session.source || 'unknown',
|
|
77
|
+
eventCount: (session.eventCount || metadata.totalEvents || 0).toString(),
|
|
78
|
+
duration: (session.duration || metadata.duration || 0).toString(),
|
|
79
|
+
model: session.model || metadata.model || 'unknown',
|
|
80
|
+
sessionStatus: session.status || metadata.status || 'unknown'
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Track SessionEventCount metric
|
|
84
|
+
const eventCount = session.eventCount || metadata.totalEvents || 0;
|
|
85
|
+
if (eventCount > 0) {
|
|
86
|
+
trackMetric('SessionEventCount', eventCount, { sessionId, source: session.source || 'unknown' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Track SessionDuration metric
|
|
90
|
+
const duration = session.duration || metadata.duration || 0;
|
|
91
|
+
if (duration > 0) {
|
|
92
|
+
trackMetric('SessionDuration', duration, { sessionId, source: session.source || 'unknown' });
|
|
93
|
+
}
|
|
94
|
+
|
|
65
95
|
res.render('session-vue', { sessionId, events: [], metadata });
|
|
66
96
|
} catch (err) {
|
|
67
97
|
console.error('Error loading session:', err);
|
|
@@ -84,6 +114,13 @@ class SessionController {
|
|
|
84
114
|
}
|
|
85
115
|
|
|
86
116
|
const metadata = buildMetadata(session);
|
|
117
|
+
|
|
118
|
+
// Track TimeAnalysisViewed event
|
|
119
|
+
trackEvent('TimeAnalysisViewed', {
|
|
120
|
+
sessionId,
|
|
121
|
+
turnCount: (metadata.totalEvents || 0).toString()
|
|
122
|
+
});
|
|
123
|
+
|
|
87
124
|
res.render('time-analyze', { sessionId, events: [], metadata });
|
|
88
125
|
} catch (err) {
|
|
89
126
|
console.error('Error loading time analysis:', err);
|
|
@@ -104,6 +141,14 @@ class SessionController {
|
|
|
104
141
|
return res.status(400).json({ error: 'Invalid pagination parameters' });
|
|
105
142
|
}
|
|
106
143
|
const paginationData = await this.sessionService.getPaginatedSessions(page, limit, sourceFilter);
|
|
144
|
+
|
|
145
|
+
// Track SessionListLoaded event for API pagination
|
|
146
|
+
trackEvent('SessionListLoaded', {
|
|
147
|
+
page: page.toString(),
|
|
148
|
+
limit: limit.toString(),
|
|
149
|
+
totalSessions: paginationData.totalSessions.toString()
|
|
150
|
+
});
|
|
151
|
+
|
|
107
152
|
res.set({ 'Cache-Control': 'public, max-age=60' });
|
|
108
153
|
res.json(paginationData);
|
|
109
154
|
} else if (sourceFilter && limit) {
|
|
@@ -140,6 +185,13 @@ class SessionController {
|
|
|
140
185
|
const page = Math.floor(offset / limit) + 1;
|
|
141
186
|
const paginationData = await this.sessionService.getPaginatedSessions(page, limit, sourceFilter);
|
|
142
187
|
|
|
188
|
+
// Track SessionListLoaded event
|
|
189
|
+
trackEvent('SessionListLoaded', {
|
|
190
|
+
page: page.toString(),
|
|
191
|
+
limit: limit.toString(),
|
|
192
|
+
totalSessions: paginationData.totalSessions.toString()
|
|
193
|
+
});
|
|
194
|
+
|
|
143
195
|
res.json({
|
|
144
196
|
sessions: paginationData.sessions,
|
|
145
197
|
hasMore: paginationData.hasNextPage,
|
|
@@ -382,6 +434,10 @@ class SessionController {
|
|
|
382
434
|
const zipBuffer = zip.toBuffer();
|
|
383
435
|
res.setHeader('Content-Type', 'application/zip');
|
|
384
436
|
res.setHeader('Content-Disposition', `attachment; filename="session-${sessionId}.zip"`);
|
|
437
|
+
|
|
438
|
+
// Track SessionExported event
|
|
439
|
+
trackEvent('SessionExported', { sessionId });
|
|
440
|
+
|
|
385
441
|
res.send(zipBuffer);
|
|
386
442
|
} catch (err) {
|
|
387
443
|
console.error('Error exporting session:', err);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const TagService = require('../services/tagService');
|
|
2
2
|
const SessionRepository = require('../services/sessionRepository');
|
|
3
3
|
const { isValidSessionId } = require('../utils/helpers');
|
|
4
|
+
const { trackEvent } = require('../telemetry');
|
|
4
5
|
|
|
5
6
|
class TagController {
|
|
6
7
|
constructor(tagService = null, sessionRepository = null) {
|
|
@@ -88,6 +89,13 @@ class TagController {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
const savedTags = await this.tagService.setSessionTags(session, tags);
|
|
92
|
+
|
|
93
|
+
// Track TagUpdated event
|
|
94
|
+
trackEvent('TagUpdated', {
|
|
95
|
+
sessionId,
|
|
96
|
+
tagCount: savedTags.length.toString()
|
|
97
|
+
});
|
|
98
|
+
|
|
91
99
|
res.json({ tags: savedTags });
|
|
92
100
|
} catch (err) {
|
|
93
101
|
console.error('Error setting session tags:', err);
|
|
@@ -4,6 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const multer = require('multer');
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
const { isValidSessionId } = require('../utils/helpers');
|
|
7
|
+
const { trackEvent, trackException } = require('../telemetry');
|
|
7
8
|
const processManager = require('../utils/processManager');
|
|
8
9
|
const config = require('../config');
|
|
9
10
|
|
|
@@ -72,6 +73,9 @@ class UploadController {
|
|
|
72
73
|
return res.status(500).json({ error: 'Failed to create zip file' });
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Track SessionShared event
|
|
77
|
+
trackEvent('SessionShared', { sessionId });
|
|
78
|
+
|
|
75
79
|
res.download(zipFile, `session-${sessionId}.zip`, (err) => {
|
|
76
80
|
fs.promises.unlink(zipFile).catch(() => {});
|
|
77
81
|
if (err) {
|
|
@@ -181,6 +185,7 @@ class UploadController {
|
|
|
181
185
|
processManager.register(unzipProcess, { name: 'unzip-import' });
|
|
182
186
|
|
|
183
187
|
unzipProcess.on('close', async (code) => {
|
|
188
|
+
let sessionDirName; // Declare here for access in catch block
|
|
184
189
|
try {
|
|
185
190
|
await fs.promises.unlink(zipPath);
|
|
186
191
|
|
|
@@ -195,7 +200,7 @@ class UploadController {
|
|
|
195
200
|
return res.status(400).json({ error: 'Empty zip file' });
|
|
196
201
|
}
|
|
197
202
|
|
|
198
|
-
|
|
203
|
+
sessionDirName = entries[0];
|
|
199
204
|
|
|
200
205
|
// Validate session directory name to prevent Zip Slip path traversal
|
|
201
206
|
if (!isValidSessionId(sessionDirName)) {
|
|
@@ -222,9 +227,23 @@ class UploadController {
|
|
|
222
227
|
await fs.promises.rename(sessionPath, targetPath);
|
|
223
228
|
await fs.promises.rm(extractDir, { recursive: true, force: true });
|
|
224
229
|
|
|
230
|
+
// Track SessionImported event
|
|
231
|
+
const stats = await fs.promises.stat(zipPath).catch(() => ({ size: 0 }));
|
|
232
|
+
trackEvent('SessionImported', {
|
|
233
|
+
format: 'copilot',
|
|
234
|
+
fileSize: stats.size.toString()
|
|
235
|
+
});
|
|
236
|
+
|
|
225
237
|
res.json({ success: true, sessionId: sessionDirName });
|
|
226
238
|
} catch (err) {
|
|
227
239
|
console.error('Error importing session:', err);
|
|
240
|
+
|
|
241
|
+
// Track import failure
|
|
242
|
+
trackException(err, {
|
|
243
|
+
operation: 'importSession',
|
|
244
|
+
sessionId: sessionDirName || 'unknown'
|
|
245
|
+
});
|
|
246
|
+
|
|
228
247
|
await fs.promises.rm(extractDir, { recursive: true, force: true }).catch(() => {});
|
|
229
248
|
res.status(500).json({ error: 'Error importing session' });
|
|
230
249
|
}
|
|
@@ -232,12 +251,24 @@ class UploadController {
|
|
|
232
251
|
|
|
233
252
|
unzipProcess.on('error', async (err) => {
|
|
234
253
|
console.error('Error extracting zip:', err);
|
|
254
|
+
|
|
255
|
+
// Track upload/extraction failure
|
|
256
|
+
trackException(err, {
|
|
257
|
+
operation: 'importSession_unzip'
|
|
258
|
+
});
|
|
259
|
+
|
|
235
260
|
await fs.promises.unlink(zipPath).catch(() => {});
|
|
236
261
|
await fs.promises.rm(extractDir, { recursive: true, force: true }).catch(() => {});
|
|
237
262
|
res.status(500).json({ error: 'Failed to extract zip file' });
|
|
238
263
|
});
|
|
239
264
|
} catch (err) {
|
|
240
265
|
console.error('Error processing upload:', err);
|
|
266
|
+
|
|
267
|
+
// Track upload processing failure
|
|
268
|
+
trackException(err, {
|
|
269
|
+
operation: 'importSession_upload'
|
|
270
|
+
});
|
|
271
|
+
|
|
241
272
|
if (req.file) {
|
|
242
273
|
await fs.promises.unlink(req.file.path).catch(() => {});
|
|
243
274
|
}
|
package/src/middleware/common.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const config = require('../config');
|
|
2
|
+
const { trackException, isEnabled: isTelemetryEnabled } = require('../telemetry');
|
|
2
3
|
|
|
3
4
|
// Request timeout middleware
|
|
4
5
|
const requestTimeout = (req, res, next) => {
|
|
@@ -6,6 +7,15 @@ const requestTimeout = (req, res, next) => {
|
|
|
6
7
|
next();
|
|
7
8
|
};
|
|
8
9
|
|
|
10
|
+
// Telemetry middleware - makes telemetry settings available to templates
|
|
11
|
+
const telemetryLocals = (req, res, next) => {
|
|
12
|
+
res.locals.telemetryEnabled = isTelemetryEnabled;
|
|
13
|
+
res.locals.telemetryConnectionString = isTelemetryEnabled
|
|
14
|
+
? (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || 'InstrumentationKey=39f4fbf1-d82f-42c3-b4ef-ea92a1fd82cb;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=7d4bb432-f2f5-4526-a5e6-31901e5a2db2')
|
|
15
|
+
: null;
|
|
16
|
+
next();
|
|
17
|
+
};
|
|
18
|
+
|
|
9
19
|
// CORS middleware for development
|
|
10
20
|
const developmentCors = (req, res, next) => {
|
|
11
21
|
if (config.NODE_ENV === 'development') {
|
|
@@ -24,6 +34,14 @@ const developmentCors = (req, res, next) => {
|
|
|
24
34
|
const errorHandler = (err, req, res, _next) => {
|
|
25
35
|
console.error('Unhandled error:', err.stack);
|
|
26
36
|
|
|
37
|
+
// Track exception in Application Insights
|
|
38
|
+
trackException(err, {
|
|
39
|
+
url: req.url,
|
|
40
|
+
method: req.method,
|
|
41
|
+
statusCode: (err.status || 500).toString(),
|
|
42
|
+
userAgent: (req.headers && req.headers['user-agent']) || 'unknown'
|
|
43
|
+
});
|
|
44
|
+
|
|
27
45
|
const statusCode = err.status || 500;
|
|
28
46
|
// Default to production-safe behavior if NODE_ENV is not set
|
|
29
47
|
const isDevelopment = config.NODE_ENV === 'development';
|
|
@@ -44,5 +62,6 @@ module.exports = {
|
|
|
44
62
|
requestTimeout,
|
|
45
63
|
developmentCors,
|
|
46
64
|
errorHandler,
|
|
47
|
-
notFoundHandler
|
|
65
|
+
notFoundHandler,
|
|
66
|
+
telemetryLocals
|
|
48
67
|
};
|
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Insights Telemetry Module
|
|
3
|
+
*
|
|
4
|
+
* This module initializes and configures Application Insights for telemetry tracking.
|
|
5
|
+
* Must be required BEFORE any other modules (especially Express) in server.js.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Auto-collection of requests, dependencies, exceptions, and performance counters
|
|
9
|
+
* - Custom event and metric tracking
|
|
10
|
+
* - Automatic disabling in test environments
|
|
11
|
+
* - Support for manual disabling via DISABLE_TELEMETRY env var
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const appInsights = require('applicationinsights');
|
|
15
|
+
|
|
16
|
+
// Determine if telemetry should be disabled
|
|
17
|
+
const isTestEnvironment = process.env.NODE_ENV === 'test';
|
|
18
|
+
const isDisabled = process.env.DISABLE_TELEMETRY === 'true' || isTestEnvironment;
|
|
19
|
+
|
|
20
|
+
// Default connection string (can be overridden via env var)
|
|
21
|
+
const DEFAULT_CONNECTION_STRING = 'InstrumentationKey=39f4fbf1-d82f-42c3-b4ef-ea92a1fd82cb;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=7d4bb432-f2f5-4526-a5e6-31901e5a2db2';
|
|
22
|
+
|
|
23
|
+
let client = null;
|
|
24
|
+
|
|
25
|
+
if (!isDisabled) {
|
|
26
|
+
try {
|
|
27
|
+
// Get connection string from environment or use default
|
|
28
|
+
const connectionString = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || DEFAULT_CONNECTION_STRING;
|
|
29
|
+
|
|
30
|
+
// Setup and start Application Insights
|
|
31
|
+
appInsights.setup(connectionString)
|
|
32
|
+
.setAutoDependencyCorrelation(true)
|
|
33
|
+
.setAutoCollectRequests(true)
|
|
34
|
+
.setAutoCollectPerformance(true, true)
|
|
35
|
+
.setAutoCollectExceptions(true)
|
|
36
|
+
.setAutoCollectDependencies(true)
|
|
37
|
+
.setAutoCollectConsole(false) // Disable console tracking to avoid noise
|
|
38
|
+
.setUseDiskRetryCaching(true)
|
|
39
|
+
.setSendLiveMetrics(false) // Disable live metrics for local dev tool
|
|
40
|
+
.setDistributedTracingMode(appInsights.DistributedTracingModes.AI_AND_W3C)
|
|
41
|
+
.start();
|
|
42
|
+
|
|
43
|
+
client = appInsights.defaultClient;
|
|
44
|
+
|
|
45
|
+
// Set context properties
|
|
46
|
+
client.context.tags[client.context.keys.cloudRole] = 'copilot-session-viewer';
|
|
47
|
+
client.context.tags[client.context.keys.cloudRoleInstance] = require('os').hostname();
|
|
48
|
+
|
|
49
|
+
console.log('✅ Application Insights telemetry initialized');
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('❌ Failed to initialize Application Insights:', error.message);
|
|
52
|
+
// Continue without telemetry rather than crashing
|
|
53
|
+
client = createNoOpClient();
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// Return no-op client for test environment or when disabled
|
|
57
|
+
client = createNoOpClient();
|
|
58
|
+
|
|
59
|
+
if (isTestEnvironment) {
|
|
60
|
+
console.log('📊 Telemetry disabled (test environment)');
|
|
61
|
+
} else {
|
|
62
|
+
console.log('📊 Telemetry disabled (DISABLE_TELEMETRY=true)');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a no-op client that safely ignores all telemetry calls
|
|
68
|
+
* Used when telemetry is disabled or in test environments
|
|
69
|
+
*/
|
|
70
|
+
function createNoOpClient() {
|
|
71
|
+
return {
|
|
72
|
+
trackEvent: () => {},
|
|
73
|
+
trackMetric: () => {},
|
|
74
|
+
trackException: () => {},
|
|
75
|
+
trackTrace: () => {},
|
|
76
|
+
trackDependency: () => {},
|
|
77
|
+
trackRequest: () => {},
|
|
78
|
+
flush: (callback) => {
|
|
79
|
+
if (callback) callback();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Track a custom event
|
|
86
|
+
* @param {string} name - Event name
|
|
87
|
+
* @param {Object} properties - Event properties
|
|
88
|
+
*/
|
|
89
|
+
function trackEvent(name, properties = {}) {
|
|
90
|
+
if (client && client.trackEvent) {
|
|
91
|
+
client.trackEvent({
|
|
92
|
+
name,
|
|
93
|
+
properties
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Track a custom metric
|
|
100
|
+
* @param {string} name - Metric name
|
|
101
|
+
* @param {number} value - Metric value
|
|
102
|
+
* @param {Object} properties - Additional properties
|
|
103
|
+
*/
|
|
104
|
+
function trackMetric(name, value, properties = {}) {
|
|
105
|
+
if (client && client.trackMetric) {
|
|
106
|
+
client.trackMetric({
|
|
107
|
+
name,
|
|
108
|
+
value,
|
|
109
|
+
properties
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Track an exception
|
|
116
|
+
* @param {Error} error - Error object
|
|
117
|
+
* @param {Object} properties - Additional properties
|
|
118
|
+
*/
|
|
119
|
+
function trackException(error, properties = {}) {
|
|
120
|
+
if (client && client.trackException) {
|
|
121
|
+
client.trackException({
|
|
122
|
+
exception: error,
|
|
123
|
+
properties
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Flush telemetry data (useful for short-lived processes)
|
|
130
|
+
* @returns {Promise<void>}
|
|
131
|
+
*/
|
|
132
|
+
function flush() {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
if (client && client.flush) {
|
|
135
|
+
client.flush({
|
|
136
|
+
callback: () => resolve()
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
resolve();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Export the client and helper functions
|
|
145
|
+
module.exports = {
|
|
146
|
+
client,
|
|
147
|
+
trackEvent,
|
|
148
|
+
trackMetric,
|
|
149
|
+
trackException,
|
|
150
|
+
flush,
|
|
151
|
+
isEnabled: !isDisabled
|
|
152
|
+
};
|