@peopl-health/nexus 4.3.3 → 4.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/lib/adapters/TwilioProvider.js +21 -1
- package/lib/controllers/conversationController.js +1 -1
- package/lib/controllers/prescriptionController.js +2 -1
- package/lib/helpers/messageStatusHelper.js +4 -2
- package/lib/helpers/threadHelper.js +2 -2
- package/lib/helpers/twilioMediaHelper.js +1 -1
- package/lib/memory/MemoryExtractor.js +5 -0
- package/lib/memory/SessionManager.js +3 -0
- package/lib/models/messageModel.js +1 -1
- package/lib/observability/index.js +36 -22
- package/lib/services/airtableService.js +17 -3
- package/lib/services/patientService.js +2 -2
- package/lib/utils/benchModeHelper.js +31 -0
- package/lib/utils/tracingDecorator.js +6 -1
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ const { sanitizeMediaFilename } = require('../utils/sanitizerUtils');
|
|
|
10
10
|
const { validateMedia, getMediaType, MEDIA_LIMITS, STICKER_DIMENSIONS } = require('../utils/mediaValidator');
|
|
11
11
|
const { logger } = require('../utils/logger');
|
|
12
12
|
const { calculateDelay } = require('../utils/scheduleUtils');
|
|
13
|
+
const { isBenchMode } = require('../utils/benchModeHelper');
|
|
13
14
|
|
|
14
15
|
const { ScheduledMessage } = require('../models/agendaMessageModel');
|
|
15
16
|
|
|
@@ -49,6 +50,24 @@ class TwilioProvider extends MessageProvider {
|
|
|
49
50
|
throw new Error('Twilio provider not initialized');
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if (isBenchMode(messageData?.code)) {
|
|
54
|
+
const benchSid = `bench_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
55
|
+
logger.info('[TwilioProvider] bench.suppressed_send', {
|
|
56
|
+
event: 'bench.suppressed_send',
|
|
57
|
+
code: messageData.code,
|
|
58
|
+
body: messageData.body || null,
|
|
59
|
+
contentSid: messageData.contentSid || null
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
messageId: benchSid,
|
|
64
|
+
provider: 'twilio',
|
|
65
|
+
status: 'bench_suppressed',
|
|
66
|
+
result: { sid: benchSid, status: 'bench_suppressed' },
|
|
67
|
+
finalize: { sid: benchSid, status: 'bench_suppressed' }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
const { code, body, fileUrl, fileType, variables, contentSid } = messageData;
|
|
53
72
|
|
|
54
73
|
const formattedFrom = ensureWhatsAppFormat(this.whatsappNumber);
|
|
@@ -236,7 +255,8 @@ class TwilioProvider extends MessageProvider {
|
|
|
236
255
|
});
|
|
237
256
|
const response = await sender(payload);
|
|
238
257
|
const messageId = response?.result?.sid || null;
|
|
239
|
-
|
|
258
|
+
const persistedStatus = response?.status === 'bench_suppressed' ? 'bench_suppressed' : 'sent';
|
|
259
|
+
await updateStatus(persistedStatus, messageId);
|
|
240
260
|
} catch (error) {
|
|
241
261
|
await updateStatus('failed', null, error);
|
|
242
262
|
logger.error(`Scheduled message failed: ${error.message}`);
|
|
@@ -441,7 +441,7 @@ const markMessagesAsReadController = async (req, res) => {
|
|
|
441
441
|
.catch(err => logger.error('[markMessagesAsRead] Failed to reset thread unreadCount', { phoneNumber, error: err.message }));
|
|
442
442
|
|
|
443
443
|
if (modifiedCount > 0) {
|
|
444
|
-
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${phoneNumber}"`, { read: true })
|
|
444
|
+
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${phoneNumber}"`, { read: true }, phoneNumber)
|
|
445
445
|
.catch(err => logger.error('[markMessagesAsRead] Failed to update message_monitor', { phoneNumber, error: err.message }));
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -29,7 +29,8 @@ const createPrescriptionController = async (req, res) => {
|
|
|
29
29
|
referenceTable: 'estado_general',
|
|
30
30
|
referenceFilter: `{whatsapp_id}='${whatsappId}'`,
|
|
31
31
|
linkFieldName: 'patient_id'
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
whatsappId
|
|
33
34
|
);
|
|
34
35
|
|
|
35
36
|
logger.info('[PrescriptionController] Prescription created', { code: whatsappId });
|
|
@@ -61,14 +61,16 @@ async function updateMessageStatus(messageSid, status, errorCode = null, errorMe
|
|
|
61
61
|
referenceTable: 'message_monitor',
|
|
62
62
|
referenceFilter: `{whatsapp_id}="${updated.numero}"`,
|
|
63
63
|
linkFieldName: 'message_monitor'
|
|
64
|
-
}
|
|
64
|
+
},
|
|
65
|
+
updated.numero
|
|
65
66
|
).catch(err => logger.error('[MessageStatus] Failed to create undelivered_messages record', { messageSid, error: err.message }));
|
|
66
67
|
} else {
|
|
67
68
|
updateRecordByFilter(
|
|
68
69
|
Monitoreo_ID,
|
|
69
70
|
'undelivered_messages',
|
|
70
71
|
`{message_id}="${updated.message_id}"`,
|
|
71
|
-
{ status }
|
|
72
|
+
{ status },
|
|
73
|
+
updated.numero
|
|
72
74
|
).catch(err => logger.error('[MessageStatus] Failed to update undelivered_messages record', { messageSid, error: err.message }));
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -36,7 +36,7 @@ const getThreadInfo = async (code) => {
|
|
|
36
36
|
const switchThreadStoppedStatus = async (code, stopped) => {
|
|
37
37
|
await Thread.updateOne({ code }, { active: true, stopped });
|
|
38
38
|
|
|
39
|
-
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${code}"`, { stopped })
|
|
39
|
+
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${code}"`, { stopped }, code)
|
|
40
40
|
.catch(err => logger.error('[switchThreadStoppedStatus] Failed to update message_monitor', { code, error: err.message }));
|
|
41
41
|
};
|
|
42
42
|
|
|
@@ -45,7 +45,7 @@ const setThreadPromptId = async (code, promptId) => {
|
|
|
45
45
|
Thread.setAssistantId(updateFields, promptId);
|
|
46
46
|
await Thread.updateOne({ code: code }, { $set: updateFields });
|
|
47
47
|
|
|
48
|
-
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${code}"`, { prompt_id: promptId })
|
|
48
|
+
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${code}"`, { prompt_id: promptId }, code)
|
|
49
49
|
.catch(err => logger.error('[setThreadPromptId] Failed to update message_monitor', { code, error: err.message }));
|
|
50
50
|
};
|
|
51
51
|
|
|
@@ -31,7 +31,7 @@ async function uploadMediaToAirtable(whatsappId, mediaUrl, baseID, tableName) {
|
|
|
31
31
|
referenceTable: 'estado_general',
|
|
32
32
|
referenceFilter: `whatsapp_id = "${whatsappId}"`,
|
|
33
33
|
linkFieldName: 'patient_id'
|
|
34
|
-
});
|
|
34
|
+
}, whatsappId);
|
|
35
35
|
} catch (error) {
|
|
36
36
|
logger.warn('[uploadMediaToAirtable] Failed', { whatsappId, error: error.message });
|
|
37
37
|
throw error;
|
|
@@ -2,6 +2,7 @@ const { OpenAI } = require('openai');
|
|
|
2
2
|
|
|
3
3
|
const { logger } = require('../utils/logger');
|
|
4
4
|
const { retryWithBackoff } = require('../utils/retryUtils');
|
|
5
|
+
const { isBenchMode } = require('../utils/benchModeHelper');
|
|
5
6
|
|
|
6
7
|
const { getPatientMemory } = require('../models/patientMemoryModel');
|
|
7
8
|
const { getConversationSummary } = require('../models/conversationSummaryModel');
|
|
@@ -69,6 +70,10 @@ class MemoryExtractor {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
async processSessionEnd(numero, sessionMessages, sessionId) {
|
|
73
|
+
if (isBenchMode(numero)) {
|
|
74
|
+
logger.info('[MemoryExtractor] bench.suppressed_extraction', { event: 'bench.suppressed_extraction', numero, sessionId });
|
|
75
|
+
return { summary: null, memoriesCreated: 0, memoriesReinforced: 0 };
|
|
76
|
+
}
|
|
72
77
|
if (!sessionMessages?.length) {
|
|
73
78
|
logger.info('[MemoryExtractor] No messages to process', { numero, sessionId });
|
|
74
79
|
return { summary: null, memoriesCreated: 0, memoriesReinforced: 0 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
|
+
const { isBenchMode } = require('../utils/benchModeHelper');
|
|
2
3
|
|
|
3
4
|
const { Message } = require('../models/messageModel');
|
|
4
5
|
|
|
@@ -13,6 +14,7 @@ class SessionManager {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
recordActivity(numero) {
|
|
17
|
+
if (isBenchMode(numero)) return;
|
|
16
18
|
const now = Date.now();
|
|
17
19
|
const existing = this.activeSessions.get(numero);
|
|
18
20
|
|
|
@@ -58,6 +60,7 @@ class SessionManager {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
async processSessionEndForNumero(numero) {
|
|
63
|
+
if (isBenchMode(numero)) return null;
|
|
61
64
|
const session = this.activeSessions.get(numero);
|
|
62
65
|
const sessionId = session?.sessionId || this._generateSessionId(numero);
|
|
63
66
|
const sessionStart = session?.sessionStart || Date.now() - this.idleTimeoutMs;
|
|
@@ -169,7 +169,7 @@ async function insertMessage(values) {
|
|
|
169
169
|
updateRecordByFilter(Monitoreo_ID, 'message_monitor', `{whatsapp_id} = "${values.numero}"`, {
|
|
170
170
|
...(values.from_me ? { last_message_bot: values.body } : { last_message_patient: values.body, read: false }),
|
|
171
171
|
...(values.from_me ? { last_message_bot_time: values.timestamp } : { last_message_patient_time: values.timestamp })
|
|
172
|
-
}).catch(err => logger.error('[MongoStorage] Failed to update message_monitor table', { numero: values.numero, error: err.message }));
|
|
172
|
+
}, values.numero).catch(err => logger.error('[MongoStorage] Failed to update message_monitor table', { numero: values.numero, error: err.message }));
|
|
173
173
|
|
|
174
174
|
logger.info('[MongoStorage] Message inserted or updated successfully');
|
|
175
175
|
return { isNew, doc };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { trace, metrics } = require('@opentelemetry/api');
|
|
2
2
|
|
|
3
|
+
const { hasBenchAttribute } = require('../utils/benchModeHelper');
|
|
3
4
|
const { initTelemetry, shutdownTelemetry } = require('../observability/telemetry');
|
|
4
5
|
|
|
5
6
|
const tracer = trace.getTracer('nexus-assistant');
|
|
@@ -51,33 +52,39 @@ const s3UploadDuration = meter.createHistogram('nexus_s3_upload_duration', {
|
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
async function traceOperation(name, operation, attributes = {}) {
|
|
54
|
-
const
|
|
55
|
+
const isBench = hasBenchAttribute(attributes);
|
|
56
|
+
const spanAttributes = isBench ? { ...attributes, bench: true } : attributes;
|
|
57
|
+
const span = tracer.startSpan(name, { attributes: spanAttributes });
|
|
55
58
|
const startTime = Date.now();
|
|
56
|
-
|
|
59
|
+
|
|
57
60
|
try {
|
|
58
|
-
activeOperationsGauge.add(1, attributes);
|
|
59
|
-
|
|
61
|
+
if (!isBench) activeOperationsGauge.add(1, attributes);
|
|
62
|
+
|
|
60
63
|
const result = await operation(span);
|
|
61
|
-
|
|
64
|
+
|
|
62
65
|
const duration = Date.now() - startTime;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
if (!isBench) {
|
|
68
|
+
responseTimeHistogram.record(duration, attributes);
|
|
69
|
+
operationCounter.add(1, { ...attributes, success: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
span.setAttributes({
|
|
68
73
|
'operation.duration_ms': duration,
|
|
69
74
|
'operation.success': true,
|
|
70
75
|
});
|
|
71
|
-
|
|
76
|
+
|
|
72
77
|
span.setStatus({ code: 1 });
|
|
73
78
|
return result;
|
|
74
|
-
|
|
79
|
+
|
|
75
80
|
} catch (error) {
|
|
76
81
|
const duration = Date.now() - startTime;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
if (!isBench) {
|
|
84
|
+
operationCounter.add(1, { ...attributes, success: false });
|
|
85
|
+
responseTimeHistogram.record(duration, { ...attributes, error: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
span.recordException(error);
|
|
82
89
|
span.setStatus({ code: 2, message: error.message });
|
|
83
90
|
span.setAttributes({
|
|
@@ -85,16 +92,18 @@ async function traceOperation(name, operation, attributes = {}) {
|
|
|
85
92
|
'operation.success': false,
|
|
86
93
|
'error.message': error.message,
|
|
87
94
|
});
|
|
88
|
-
|
|
95
|
+
|
|
89
96
|
throw error;
|
|
90
97
|
} finally {
|
|
91
|
-
activeOperationsGauge.add(-1, attributes);
|
|
98
|
+
if (!isBench) activeOperationsGauge.add(-1, attributes);
|
|
92
99
|
span.end();
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
function createSpan(name, attributes = {}) {
|
|
97
|
-
|
|
104
|
+
const isBench = hasBenchAttribute(attributes);
|
|
105
|
+
const spanAttributes = isBench ? { ...attributes, bench: true } : attributes;
|
|
106
|
+
return tracer.startSpan(name, { attributes: spanAttributes });
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
function init(config = {}) {
|
|
@@ -103,46 +112,51 @@ function init(config = {}) {
|
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
function traceAssistantReply(operation, attributes = {}) {
|
|
115
|
+
const isBench = hasBenchAttribute(attributes);
|
|
106
116
|
return traceOperation('assistant_reply', async (span) => {
|
|
107
117
|
const startTime = Date.now();
|
|
108
118
|
try {
|
|
109
119
|
const result = await operation(span);
|
|
110
120
|
const duration = Date.now() - startTime;
|
|
111
|
-
assistantReplyDuration.record(duration, attributes);
|
|
121
|
+
if (!isBench) assistantReplyDuration.record(duration, attributes);
|
|
112
122
|
return result;
|
|
113
123
|
} catch (error) {
|
|
114
124
|
const duration = Date.now() - startTime;
|
|
115
|
-
assistantReplyDuration.record(duration, { ...attributes, error: true });
|
|
125
|
+
if (!isBench) assistantReplyDuration.record(duration, { ...attributes, error: true });
|
|
116
126
|
throw error;
|
|
117
127
|
}
|
|
118
128
|
}, attributes);
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
function traceAssistantInstruction(operation, attributes = {}) {
|
|
132
|
+
const isBench = hasBenchAttribute(attributes);
|
|
122
133
|
return traceOperation('assistant_instruction', async (span) => {
|
|
123
134
|
const startTime = Date.now();
|
|
124
135
|
try {
|
|
125
136
|
const result = await operation(span);
|
|
126
137
|
const duration = Date.now() - startTime;
|
|
127
|
-
assistantInstructionDuration.record(duration, attributes);
|
|
138
|
+
if (!isBench) assistantInstructionDuration.record(duration, attributes);
|
|
128
139
|
return result;
|
|
129
140
|
} catch (error) {
|
|
130
141
|
const duration = Date.now() - startTime;
|
|
131
|
-
assistantInstructionDuration.record(duration, { ...attributes, error: true });
|
|
142
|
+
if (!isBench) assistantInstructionDuration.record(duration, { ...attributes, error: true });
|
|
132
143
|
throw error;
|
|
133
144
|
}
|
|
134
145
|
}, attributes);
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
function recordAssistantRetry(attributes = {}) {
|
|
149
|
+
if (hasBenchAttribute(attributes)) return;
|
|
138
150
|
assistantRetryCounter.add(1, attributes);
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
function recordThreadOperation(operationType, attributes = {}) {
|
|
154
|
+
if (hasBenchAttribute(attributes)) return;
|
|
142
155
|
threadOperationsCounter.add(1, { ...attributes, operation_type: operationType });
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
function recordFileOperation(operationType, attributes = {}) {
|
|
159
|
+
if (hasBenchAttribute(attributes)) return;
|
|
146
160
|
fileOperationsCounter.add(1, { ...attributes, operation_type: operationType });
|
|
147
161
|
}
|
|
148
162
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { airtable } = require('../config/airtableConfig');
|
|
2
2
|
|
|
3
3
|
const { logger } = require('../utils/logger');
|
|
4
|
+
const { isBenchMode } = require('../utils/benchModeHelper');
|
|
4
5
|
|
|
5
6
|
let evalMode = false;
|
|
6
7
|
|
|
@@ -23,7 +24,11 @@ async function collectRecords(query, mapper = r => r.fields) {
|
|
|
23
24
|
return records;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
async function addRecord(baseID, tableName, fields) {
|
|
27
|
+
async function addRecord(baseID, tableName, fields, context = null) {
|
|
28
|
+
if (isBenchMode(context)) {
|
|
29
|
+
logger.info('[addRecord:bench] Suppressed', { tableName, code: context });
|
|
30
|
+
return { id: 'bench_mock_record', fields: Array.isArray(fields) ? fields[0]?.fields || {} : fields };
|
|
31
|
+
}
|
|
27
32
|
if (evalMode) {
|
|
28
33
|
logger.info('[addRecord:eval] Muted', { tableName });
|
|
29
34
|
return { id: 'eval_mock_record', fields: Array.isArray(fields) ? fields[0]?.fields || {} : fields };
|
|
@@ -60,7 +65,11 @@ async function getRecordByFilter(baseID, tableName, filter, view = 'Grid view',
|
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
async function updateRecordByFilter(baseID, tableName, filter, updateFields) {
|
|
68
|
+
async function updateRecordByFilter(baseID, tableName, filter, updateFields, context = null) {
|
|
69
|
+
if (isBenchMode(context)) {
|
|
70
|
+
logger.info('[updateRecordByFilter:bench] Suppressed', { tableName, filter, code: context });
|
|
71
|
+
return [{ id: 'bench_mock_record', fields: updateFields }];
|
|
72
|
+
}
|
|
64
73
|
if (evalMode) {
|
|
65
74
|
logger.info('[updateRecordByFilter:eval] Muted', { tableName, filter });
|
|
66
75
|
return [{ id: 'eval_mock_record', fields: updateFields }];
|
|
@@ -85,9 +94,14 @@ async function updateRecordByFilter(baseID, tableName, filter, updateFields) {
|
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
async function addLinkedRecord(baseID, targetTable, fields, linkConfig) {
|
|
97
|
+
async function addLinkedRecord(baseID, targetTable, fields, linkConfig, context = null) {
|
|
89
98
|
if (!baseID) throw new Error('[addLinkedRecord] Base ID is required');
|
|
90
99
|
|
|
100
|
+
if (isBenchMode(context)) {
|
|
101
|
+
logger.info('[addLinkedRecord:bench] Suppressed', { targetTable, code: context });
|
|
102
|
+
return { id: 'bench_mock_record', fields };
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
try {
|
|
92
106
|
if (linkConfig) {
|
|
93
107
|
const { referenceTable, referenceFilter, linkFieldName } = linkConfig;
|
|
@@ -55,13 +55,13 @@ const updatePatientInformation = async (code, fields, auditContext = {}) => {
|
|
|
55
55
|
.map(([key, value]) => [key.replace(/^updt_/, ''), value])
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
const result = await updateRecordByFilter(Estado_General_ID, 'estado_general', filter, fields);
|
|
58
|
+
const result = await updateRecordByFilter(Estado_General_ID, 'estado_general', filter, fields, code);
|
|
59
59
|
|
|
60
60
|
let failedFields = [];
|
|
61
61
|
if (!result) {
|
|
62
62
|
logger.warn('[updatePatientInformation] Bulk update failed, retrying fields individually', { code });
|
|
63
63
|
for (const [key, value] of Object.entries(fields)) {
|
|
64
|
-
const fieldResult = await updateRecordByFilter(Estado_General_ID, 'estado_general', filter, { [key]: value });
|
|
64
|
+
const fieldResult = await updateRecordByFilter(Estado_General_ID, 'estado_general', filter, { [key]: value }, code);
|
|
65
65
|
if (!fieldResult) {
|
|
66
66
|
failedFields.push({ key, value });
|
|
67
67
|
const cleanKey = key.replace(/^updt_/, '');
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const BENCH_CODE_PREFIX = 'bench:';
|
|
2
|
+
|
|
3
|
+
function extractCode(codeOrThread) {
|
|
4
|
+
if (!codeOrThread) return null;
|
|
5
|
+
if (typeof codeOrThread === 'string') return codeOrThread;
|
|
6
|
+
if (typeof codeOrThread === 'object') {
|
|
7
|
+
return codeOrThread.code || codeOrThread.numero || codeOrThread.thread_code || null;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isBenchMode(codeOrThread) {
|
|
13
|
+
const code = extractCode(codeOrThread);
|
|
14
|
+
return typeof code === 'string' && code.startsWith(BENCH_CODE_PREFIX);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasBenchAttribute(attributes) {
|
|
18
|
+
if (!attributes || typeof attributes !== 'object') return false;
|
|
19
|
+
if (attributes.bench === true) return true;
|
|
20
|
+
for (const key of Object.keys(attributes)) {
|
|
21
|
+
const value = attributes[key];
|
|
22
|
+
if (typeof value === 'string' && value.startsWith(BENCH_CODE_PREFIX)) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
BENCH_CODE_PREFIX,
|
|
29
|
+
isBenchMode,
|
|
30
|
+
hasBenchAttribute
|
|
31
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { SpanStatusCode } = require('@opentelemetry/api');
|
|
2
2
|
|
|
3
3
|
const { createSpan } = require('../observability');
|
|
4
|
+
const { hasBenchAttribute } = require('./benchModeHelper');
|
|
4
5
|
|
|
5
6
|
const withTracing = (fn, spanName, attributeMapper = null, options = {}) => {
|
|
6
7
|
return async function (...args) {
|
|
@@ -9,7 +10,11 @@ const withTracing = (fn, spanName, attributeMapper = null, options = {}) => {
|
|
|
9
10
|
|
|
10
11
|
try {
|
|
11
12
|
if (typeof attributeMapper === 'function') {
|
|
12
|
-
|
|
13
|
+
const attributes = attributeMapper(...args);
|
|
14
|
+
span.setAttributes(attributes);
|
|
15
|
+
if (hasBenchAttribute(attributes)) {
|
|
16
|
+
span.setAttribute('bench', true);
|
|
17
|
+
}
|
|
13
18
|
}
|
|
14
19
|
const result = await fn.apply(this, args);
|
|
15
20
|
span.setStatus({ code: SpanStatusCode.OK });
|