@zintrust/trace 0.4.94 → 0.4.96
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/dist/build-manifest.json +40 -28
- package/dist/config.js +83 -6
- package/dist/dashboard/ui.js +40 -13
- package/dist/storage/DebuggerStorage.d.ts +13 -0
- package/dist/storage/DebuggerStorage.js +195 -0
- package/dist/storage/TraceContentBudget.js +67 -15
- package/dist/types.d.ts +13 -1
- package/dist/utils/entryFilter.js +20 -0
- package/dist/watchers/HttpClientWatcher.d.ts +1 -1
- package/dist/watchers/HttpClientWatcher.js +95 -18
- package/dist/watchers/HttpWatcher.js +7 -1
- package/dist/watchers/MiddlewareWatcher.js +5 -0
- package/dist/watchers/ModelWatcher.js +5 -0
- package/package.json +2 -2
- package/src/config.ts +136 -6
- package/src/dashboard/ui.ts +40 -13
- package/src/storage/TraceContentBudget.ts +98 -17
- package/src/types.ts +15 -1
- package/src/utils/entryFilter.ts +23 -0
- package/src/watchers/HttpClientWatcher.ts +125 -19
- package/src/watchers/HttpWatcher.ts +8 -1
- package/src/watchers/MiddlewareWatcher.ts +9 -0
- package/src/watchers/ModelWatcher.ts +9 -0
package/src/dashboard/ui.ts
CHANGED
|
@@ -38,6 +38,7 @@ const SUN_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke
|
|
|
38
38
|
const MOON_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path></svg>`;
|
|
39
39
|
|
|
40
40
|
const COPY_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
|
41
|
+
const DISCLOSURE_ICON = `<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4l6 6-6 6"></path></svg>`;
|
|
41
42
|
|
|
42
43
|
const JSON_HIGHLIGHT_PATTERN = String.raw`("(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*")(?=\s*:)|(\s*:)|("(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*")|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?`;
|
|
43
44
|
const SQL_HIGHLIGHT_PATTERN = String.raw`(\/\*[\s\S]*?\*\/|--.*$|'(?:''|[^'])*'|\x60[^\x60]+\x60|\b(?:select|from|where|insert|into|values|update|delete|join|left|right|inner|outer|on|and|or|limit|order|by|group|having|as|distinct|null|is|in|like|set|case|when|then|else|end|returning|union|all)\b|-?\d+(?:\.\d+)?)`;
|
|
@@ -137,6 +138,10 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
137
138
|
const SUN_ICON = __TRACE_SUN_ICON__;
|
|
138
139
|
const MOON_ICON = __TRACE_MOON_ICON__;
|
|
139
140
|
const COPY_ICON = __TRACE_COPY_ICON__;
|
|
141
|
+
const DISCLOSURE_ICON = __TRACE_DISCLOSURE_ICON__;
|
|
142
|
+
.panel{border-radius:var(--radius);border:1px solid var(--line);background:var(--surface);box-shadow:var(--shadow);backdrop-filter:blur(16px)}.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px;margin-bottom:18px}.stat-card{padding:20px;position:relative;overflow:hidden}.stat-card::after{content:'';position:absolute;right:-18px;bottom:-26px;width:92px;height:92px;border-radius:28px;background:linear-gradient(135deg,rgba(56,189,248,.16),rgba(34,197,94,.08));transform:rotate(18deg)}.stat-label{font-size:.74rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-weight:800;margin-bottom:12px}.stat-value{font-size:2.25rem;font-weight:800;line-height:1}.stat-meta{margin-top:10px;color:var(--muted);font-size:.9rem}.content-grid{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(320px,.95fr);gap:18px}.side-stack{display:grid;gap:18px}
|
|
143
|
+
.section-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:22px 24px 16px}.section-head h3{margin:0;font-size:1.04rem}.section-head p{margin:6px 0 0;color:var(--muted);font-size:.92rem}.toolbar{display:flex;flex-wrap:wrap;gap:10px;padding:0 24px 18px}.control,.toolbar input,.toolbar select{height:44px;border-radius:13px;border:1px solid var(--line);background:var(--surface-strong);color:var(--text);padding:0 14px;min-width:0}.toolbar input,.toolbar select{flex:1 1 180px}.toolbar input::placeholder{color:var(--muted)}.btn{height:44px;border:none;border-radius:13px;padding:0 16px;cursor:pointer;font-weight:800}.btn-primary{background:linear-gradient(135deg,var(--accent-strong),var(--accent));color:#fff}.btn-danger{background:rgba(239,68,68,.12);color:var(--danger);border:1px solid rgba(239,68,68,.18)}.btn-ghost{background:var(--surface-soft);color:var(--text);border:1px solid var(--line)}
|
|
144
|
+
.activity-list{list-style:none;margin:0;padding:0 24px 24px}.activity-item{padding:14px 0;border-top:1px solid var(--line)}.activity-item:first-child{border-top:none}.activity-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.activity-time{color:var(--muted);font-size:.85rem}.activity-summary{margin-top:8px;color:var(--text);line-height:1.48}.back-link{display:inline-flex;align-items:center;gap:8px;margin:0 0 14px;color:var(--accent);font-weight:800;cursor:pointer}.detail-card{padding:24px}.detail-meta{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0 20px;color:var(--muted);font-size:.9rem;overflow-wrap:anywhere}.detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}.detail-stack{display:grid;gap:16px;margin-top:18px}.detail-box{padding:16px;border-radius:16px;background:var(--surface-soft);border:1px solid var(--line)}.detail-box h4{margin:0 0 10px;font-size:.92rem}.detail-box dl{margin:0;display:grid;gap:8px}.detail-box dt{font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:800}.detail-box dd{margin:0;color:var(--text);line-height:1.45;overflow-wrap:anywhere}.trace-tabs{display:flex;gap:10px;flex-wrap:wrap;margin:20px 0 16px}.trace-tab{border:none;border-radius:12px;padding:10px 12px;background:transparent;color:var(--muted);cursor:pointer;box-shadow:inset 0 0 0 1px var(--line);font-weight:800}.trace-tab.active{background:rgba(56,189,248,.12);color:var(--text);box-shadow:inset 0 0 0 1px rgba(56,189,248,.28)}.trace-panel{display:grid;gap:14px}.trace-item{padding:18px;border-radius:16px;background:var(--surface-soft);border:1px solid var(--line)}.trace-item-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.trace-item-summary{margin-top:10px;display:grid;gap:10px}.trace-note{color:var(--muted);line-height:1.6}.trace-disclosure{padding:0;overflow:hidden}.trace-disclosure[open]{padding-bottom:18px}.trace-disclosure .trace-item-summary{margin-top:0}.trace-disclosure-body{display:grid;gap:12px;padding:0 18px}.trace-summary{list-style:none;cursor:pointer;padding:18px}.trace-summary::-webkit-details-marker{display:none}.trace-summary-main{display:grid;gap:10px;min-width:0;flex:1}.trace-summary-copy{display:grid;gap:6px;min-width:0}.trace-summary-copy .summary,.trace-summary-copy .summary-sub{display:block;overflow-wrap:anywhere}.trace-disclosure-body .summary-sub{overflow-wrap:anywhere}.trace-summary-icon{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted);flex:none;transition:transform .16s ease,color .16s ease}.trace-summary-icon svg{width:14px;height:14px;display:block}.trace-disclosure[open] .trace-summary-icon{transform:rotate(90deg);color:var(--accent)}
|
|
140
145
|
const JSON_HIGHLIGHT_PATTERN = new RegExp(__TRACE_JSON_REGEX__, 'g');
|
|
141
146
|
const SQL_HIGHLIGHT_PATTERN = new RegExp(__TRACE_SQL_REGEX__, 'gim');
|
|
142
147
|
const ENTRY_TYPES = ['request','query','exception','log','job','cache','schedule','mail','auth','event','model','notification','redis','gate','middleware','command','batch','dump','view','client_request'];
|
|
@@ -221,6 +226,19 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
221
226
|
return ' method-other';
|
|
222
227
|
};
|
|
223
228
|
|
|
229
|
+
const normalizeMethodLabel = (value) => {
|
|
230
|
+
const method = String(value || '').trim().toUpperCase();
|
|
231
|
+
if (method === '') return 'Request';
|
|
232
|
+
return method.charAt(0) + method.slice(1).toLowerCase();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const entryTypeLabel = (entry) => {
|
|
236
|
+
if (entry && entry.type === 'request') {
|
|
237
|
+
return normalizeMethodLabel(entry.content && entry.content.method);
|
|
238
|
+
}
|
|
239
|
+
return String(entry && entry.type || '');
|
|
240
|
+
};
|
|
241
|
+
|
|
224
242
|
const typeClass = (entryOrType, maybeEntry) => {
|
|
225
243
|
const entry = maybeEntry || (typeof entryOrType === 'object' ? entryOrType : null);
|
|
226
244
|
const type = entry && entry.type ? entry.type : entryOrType;
|
|
@@ -433,7 +451,7 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
433
451
|
|
|
434
452
|
const entrySummaryText = (entry) => {
|
|
435
453
|
const content = entry && entry.content ? entry.content : {};
|
|
436
|
-
if (entry.type === 'request') return [content.responseStatus || '', content.
|
|
454
|
+
if (entry.type === 'request') return [content.responseStatus || '', content.uri || ''].filter(Boolean).join(' ');
|
|
437
455
|
if (entry.type === 'query') return String(content.sql || '').slice(0, 160);
|
|
438
456
|
if (entry.type === 'exception') return [content.class || '', content.message || ''].filter(Boolean).join(': ');
|
|
439
457
|
if (entry.type === 'log') return '[' + String(content.level || 'log') + '] ' + String(content.message || '').slice(0, 160);
|
|
@@ -630,16 +648,17 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
630
648
|
const renderTraceItems = (entries, options = {}) => {
|
|
631
649
|
if (entries.length === 0) return '<p class="trace-note">No related entries captured.</p>';
|
|
632
650
|
|
|
633
|
-
const collapsible = options.collapsible
|
|
634
|
-
const isInitiallyOpen = options.collapsed
|
|
651
|
+
const collapsible = options.collapsible !== false;
|
|
652
|
+
const isInitiallyOpen = options.collapsed === false;
|
|
635
653
|
|
|
636
654
|
return '<div class="trace-panel">' + entries.map((entry) => {
|
|
637
655
|
if (collapsible) {
|
|
638
656
|
return [
|
|
639
657
|
'<details class="trace-item trace-disclosure"' + (isInitiallyOpen ? ' open' : '') + '>',
|
|
640
658
|
'<summary class="trace-item-head trace-summary">',
|
|
659
|
+
'<span class="trace-summary-icon">' + DISCLOSURE_ICON + '</span>',
|
|
641
660
|
'<span class="trace-summary-main">',
|
|
642
|
-
'<span><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
661
|
+
'<span><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></span>',
|
|
643
662
|
'<span class="trace-summary-copy">' + entrySummaryInlineHtml(entry) + '</span>',
|
|
644
663
|
'</span>',
|
|
645
664
|
'<span class="activity-head">' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></span>',
|
|
@@ -656,7 +675,7 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
656
675
|
'<section class="trace-item">',
|
|
657
676
|
'<div class="trace-item-head">',
|
|
658
677
|
'<div>',
|
|
659
|
-
'<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
678
|
+
'<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span>',
|
|
660
679
|
'</div>',
|
|
661
680
|
'<div class="activity-head">' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></div>',
|
|
662
681
|
'</div>',
|
|
@@ -679,14 +698,16 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
679
698
|
{ id: 'headers', label: 'Headers' },
|
|
680
699
|
{ id: 'response', label: 'Response' },
|
|
681
700
|
{ id: 'queries', label: 'Queries', count: batchEntriesByType('query').length },
|
|
701
|
+
{ id: 'middleware', label: 'Middleware', count: batchEntriesByType('middleware').length },
|
|
702
|
+
{ id: 'models', label: 'Models', count: batchEntriesByType('model').length },
|
|
682
703
|
{ id: 'logs', label: 'Logs', count: batchEntriesByType('log').length },
|
|
683
704
|
{ id: 'exceptions', label: 'Exceptions', count: batchEntriesByType('exception').length },
|
|
684
705
|
{ id: 'http', label: 'HTTP', count: batchEntriesByType('client_request').length },
|
|
685
706
|
{ id: 'cache', label: 'Cache', count: batchEntriesByType('cache').length },
|
|
686
|
-
{ id: 'other', label: 'Other', count: batchEntries().filter((item) => !['request','query','log','exception','client_request','cache'].includes(item.type)).length }
|
|
707
|
+
{ id: 'other', label: 'Other', count: batchEntries().filter((item) => !['request','query','middleware','model','log','exception','client_request','cache'].includes(item.type)).length }
|
|
687
708
|
];
|
|
688
709
|
const currentTab = traceTabs.some((tab) => tab.id === state.detailTab) ? state.detailTab : 'summary';
|
|
689
|
-
const otherEntries = batchEntries().filter((item) => !['request','query','log','exception','client_request','cache'].includes(item.type));
|
|
710
|
+
const otherEntries = batchEntries().filter((item) => !['request','query','middleware','model','log','exception','client_request','cache'].includes(item.type));
|
|
690
711
|
const panels = {
|
|
691
712
|
summary: [
|
|
692
713
|
'<div class="detail-grid">',
|
|
@@ -705,12 +726,17 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
705
726
|
renderMetricBox('Tags', [
|
|
706
727
|
{ label: 'Values', value: tagsHtml(entry.tags) || '<span class="activity-time">-</span>' }
|
|
707
728
|
]),
|
|
729
|
+
renderMetricBox('Route middleware', [
|
|
730
|
+
{ label: 'Attached', value: escapeHtml(Array.isArray(content.middleware) && content.middleware.length > 0 ? content.middleware.join(', ') : 'None') }
|
|
731
|
+
]),
|
|
708
732
|
'</div>'
|
|
709
733
|
].join(''),
|
|
710
734
|
payload: detailJson(content.payload || {}, 'Payload Json'),
|
|
711
735
|
headers: '<div class="detail-stack">' + detailJson(content.headers || {}, 'Request Header Json') + detailJson(content.responseHeaders || {}, 'Response Header Json') + '</div>',
|
|
712
736
|
response: '<div class="detail-stack"><div class="detail-grid">' + renderMetricBox('Status', [{ label: 'Response status', value: escapeHtml(content.responseStatus || '') }, { label: 'Duration', value: escapeHtml(formatDuration(getEntryDuration(entry))) }]) + '</div>' + (content.responseBody === undefined ? '<p class="trace-note">No response body was captured for this request.</p>' : detailJson(content.responseBody, 'Response Body Json')) + detailJson(content.responseHeaders || {}, 'Response Header Json') + '</div>',
|
|
713
|
-
queries: renderTraceItems(batchEntriesByType('query')
|
|
737
|
+
queries: renderTraceItems(batchEntriesByType('query')),
|
|
738
|
+
middleware: renderTraceItems(batchEntriesByType('middleware')),
|
|
739
|
+
models: renderTraceItems(batchEntriesByType('model')),
|
|
714
740
|
logs: renderTraceItems(batchEntriesByType('log')),
|
|
715
741
|
exceptions: renderTraceItems(batchEntriesByType('exception')),
|
|
716
742
|
http: renderTraceItems(batchEntriesByType('client_request')),
|
|
@@ -722,8 +748,8 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
722
748
|
'<span class="back-link" data-action="close-detail"><- Back to entries</span>',
|
|
723
749
|
'<section class="panel detail-card">',
|
|
724
750
|
'<div>' + (entry.type === 'request'
|
|
725
|
-
? '<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
726
|
-
: '<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
751
|
+
? '<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span> <span class="' + typeClass(entry) + '">' + escapeHtml(content.responseStatus || '') + '</span> <span class="mono">' + escapeHtml(content.uri || '') + '</span> ' + tagsHtml(entry.tags)
|
|
752
|
+
: '<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span> ' + tagsHtml(entry.tags)) + '</div>',
|
|
727
753
|
'<div class="detail-meta"><span>UUID <span class="mono">' + escapeHtml(entry.uuid) + '</span></span><span>Batch <span class="mono">' + escapeHtml(entry.batchId || '-') + '</span></span><span>' + durationHtml(entry) + '</span><span>' + escapeHtml(new Date(Number(entry.createdAt)).toISOString()) + '</span></div>',
|
|
728
754
|
'<div class="trace-tabs">',
|
|
729
755
|
traceTabs.map((tab) => '<button type="button" class="trace-tab' + (tab.id === currentTab ? ' active' : '') + '" data-action="detail-tab" data-tab="' + escapeHtml(tab.id) + '">' + escapeHtml(tab.label) + (tab.count !== undefined ? ' (' + escapeHtml(tab.count) + ')' : '') + '</button>').join(''),
|
|
@@ -749,10 +775,10 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
749
775
|
const recentRows = recent.data || [];
|
|
750
776
|
const recentTable = recentRows.length === 0
|
|
751
777
|
? '<div class="empty">No trace entries recorded.</div>'
|
|
752
|
-
: '<div class="table-wrap"><table><thead><tr><th>Type</th><th>Summary</th><th>Tags</th><th>Duration</th><th>Happened</th></tr></thead><tbody>' + recentRows.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
778
|
+
: '<div class="table-wrap"><table><thead><tr><th>Type</th><th>Summary</th><th>Tags</th><th>Duration</th><th>Happened</th></tr></thead><tbody>' + recentRows.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></td><td>' + entrySummaryHtml(entry) + '</td><td>' + tagsHtml(entry.tags) + '</td><td>' + durationHtml(entry) + '</td><td class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</td></tr>').join('') + '</tbody></table></div>';
|
|
753
779
|
const activityList = recentRows.length === 0
|
|
754
780
|
? '<div class="empty">No recent activity.</div>'
|
|
755
|
-
: '<ul class="activity-list">' + recentRows.slice(0, 5).map((entry) => '<li class="activity-item"><div class="activity-head"><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
781
|
+
: '<ul class="activity-list">' + recentRows.slice(0, 5).map((entry) => '<li class="activity-item"><div class="activity-head"><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span>' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></div><div class="activity-summary">' + escapeHtml(entrySummaryText(entry)) + '</div></li>').join('') + '</ul>';
|
|
756
782
|
|
|
757
783
|
main.innerHTML = [
|
|
758
784
|
statsCardsHtml(stats),
|
|
@@ -796,7 +822,7 @@ const DASHBOARD_DOCUMENT = String.raw`<!DOCTYPE html>
|
|
|
796
822
|
const total = Number(response.total || 0);
|
|
797
823
|
const perPage = Number(response.perPage || 50);
|
|
798
824
|
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
799
|
-
const rows = data.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
825
|
+
const rows = data.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></td><td>' + entrySummaryHtml(entry) + '</td><td>' + tagsHtml(entry.tags) + '</td><td>' + durationHtml(entry) + '</td><td class="mono">' + batchSnippet(entry.batchId) + '</td><td class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</td></tr>').join('');
|
|
800
826
|
|
|
801
827
|
main.innerHTML = [
|
|
802
828
|
'<section class="panel">',
|
|
@@ -1063,6 +1089,7 @@ const buildDashboardHtml = (basePath: string, projectName?: string): string => {
|
|
|
1063
1089
|
.replace('__TRACE_SUN_ICON__', JSON.stringify(SUN_ICON))
|
|
1064
1090
|
.replace('__TRACE_MOON_ICON__', JSON.stringify(MOON_ICON))
|
|
1065
1091
|
.replace('__TRACE_COPY_ICON__', JSON.stringify(COPY_ICON))
|
|
1092
|
+
.replace('__TRACE_DISCLOSURE_ICON__', JSON.stringify(DISCLOSURE_ICON))
|
|
1066
1093
|
.replace('__TRACE_JSON_REGEX__', JSON.stringify(JSON_HIGHLIGHT_PATTERN))
|
|
1067
1094
|
.replace('__TRACE_SQL_REGEX__', JSON.stringify(SQL_HIGHLIGHT_PATTERN))
|
|
1068
1095
|
.replace('__TRACE_BASE_PATH_LABEL__', basePath)
|
|
@@ -27,6 +27,87 @@ const describeValueType = (value: unknown): string => {
|
|
|
27
27
|
return typeof value;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
type TracePathSegment = string | number;
|
|
31
|
+
|
|
32
|
+
type TracePathCandidate = {
|
|
33
|
+
path: TracePathSegment[];
|
|
34
|
+
size: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const chooseLargerCandidate = (
|
|
38
|
+
left: TracePathCandidate | null,
|
|
39
|
+
right: TracePathCandidate | null
|
|
40
|
+
): TracePathCandidate | null => {
|
|
41
|
+
if (left === null) return right;
|
|
42
|
+
if (right === null) return left;
|
|
43
|
+
return right.size > left.size ? right : left;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const fallbackCandidate = (value: unknown, path: TracePathSegment[]): TracePathCandidate | null => {
|
|
47
|
+
return path.length === 0 ? null : { path, size: serializedSize(value) };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const findLargestDroppablePathInArray = (
|
|
51
|
+
value: unknown[],
|
|
52
|
+
path: TracePathSegment[]
|
|
53
|
+
): TracePathCandidate | null => {
|
|
54
|
+
let best: TracePathCandidate | null = null;
|
|
55
|
+
|
|
56
|
+
for (const [index, item] of value.entries()) {
|
|
57
|
+
best = chooseLargerCandidate(best, findLargestDroppablePath(item, [...path, index]));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return best ?? fallbackCandidate(value, path);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const findLargestDroppablePathInObject = (
|
|
64
|
+
value: Record<string, unknown>,
|
|
65
|
+
path: TracePathSegment[]
|
|
66
|
+
): TracePathCandidate | null => {
|
|
67
|
+
let best: TracePathCandidate | null = null;
|
|
68
|
+
|
|
69
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
70
|
+
if (key === '__traceNotice') continue;
|
|
71
|
+
best = chooseLargerCandidate(best, findLargestDroppablePath(entryValue, [...path, key]));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return best ?? fallbackCandidate(value, path);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const findLargestDroppablePath = (
|
|
78
|
+
value: unknown,
|
|
79
|
+
path: TracePathSegment[] = []
|
|
80
|
+
): TracePathCandidate | null => {
|
|
81
|
+
if (Array.isArray(value)) return findLargestDroppablePathInArray(value, path);
|
|
82
|
+
if (typeof value === 'object' && value !== null) {
|
|
83
|
+
return findLargestDroppablePathInObject(value as Record<string, unknown>, path);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return fallbackCandidate(value, path);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const replaceAtPath = (value: unknown, path: TracePathSegment[], replacement: unknown): unknown => {
|
|
90
|
+
if (path.length === 0) return replacement;
|
|
91
|
+
|
|
92
|
+
const [segment, ...rest] = path;
|
|
93
|
+
|
|
94
|
+
if (Array.isArray(value) && typeof segment === 'number') {
|
|
95
|
+
const next = value.slice();
|
|
96
|
+
next[segment] = replaceAtPath(next[segment], rest, replacement);
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof value === 'object' && value !== null && typeof segment === 'string') {
|
|
101
|
+
const current = value as Record<string, unknown>;
|
|
102
|
+
return {
|
|
103
|
+
...current,
|
|
104
|
+
[segment]: replaceAtPath(current[segment], rest, replacement),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return value;
|
|
109
|
+
};
|
|
110
|
+
|
|
30
111
|
const compactValue = (value: unknown, depth: number): unknown => {
|
|
31
112
|
if (depth >= DEFAULT_MAX_DEPTH) {
|
|
32
113
|
return DROPPED_FIELD_MESSAGE;
|
|
@@ -69,19 +150,19 @@ const compactValue = (value: unknown, depth: number): unknown => {
|
|
|
69
150
|
return Object.fromEntries(compactedEntries);
|
|
70
151
|
};
|
|
71
152
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
compacted
|
|
153
|
+
const compactStructuredValueToBudget = (value: unknown): unknown => {
|
|
154
|
+
let compacted: unknown =
|
|
155
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
156
|
+
? {
|
|
157
|
+
...(value as Record<string, unknown>),
|
|
158
|
+
__traceNotice: COMPACTED_CONTENT_MESSAGE,
|
|
159
|
+
}
|
|
160
|
+
: value;
|
|
161
|
+
|
|
162
|
+
while (serializedSize(compacted) > DEFAULT_MAX_ENTRY_BYTES) {
|
|
163
|
+
const candidate = findLargestDroppablePath(compacted);
|
|
164
|
+
if (candidate === null) break;
|
|
165
|
+
compacted = replaceAtPath(compacted, candidate.path, DROPPED_FIELD_MESSAGE);
|
|
85
166
|
}
|
|
86
167
|
|
|
87
168
|
return compacted;
|
|
@@ -97,10 +178,10 @@ const fitContentToBudget = (content: unknown): unknown => {
|
|
|
97
178
|
return compacted;
|
|
98
179
|
}
|
|
99
180
|
|
|
100
|
-
if (typeof compacted === 'object' && compacted !== null
|
|
101
|
-
const
|
|
102
|
-
if (serializedSize(
|
|
103
|
-
return
|
|
181
|
+
if (typeof compacted === 'object' && compacted !== null) {
|
|
182
|
+
const budgetCompacted = compactStructuredValueToBudget(compacted);
|
|
183
|
+
if (serializedSize(budgetCompacted) <= DEFAULT_MAX_ENTRY_BYTES) {
|
|
184
|
+
return budgetCompacted;
|
|
104
185
|
}
|
|
105
186
|
}
|
|
106
187
|
|
package/src/types.ts
CHANGED
|
@@ -209,6 +209,7 @@ export interface ViewContent {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
export interface ClientRequestContent {
|
|
212
|
+
source?: string;
|
|
212
213
|
method: string;
|
|
213
214
|
url: string;
|
|
214
215
|
requestHeaders: Record<string, string>;
|
|
@@ -222,6 +223,7 @@ export interface ClientRequestContent {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
export interface ClientRequestTraceInput {
|
|
226
|
+
source?: string;
|
|
225
227
|
method: string;
|
|
226
228
|
url: string;
|
|
227
229
|
requestHeaders: Record<string, string>;
|
|
@@ -315,6 +317,13 @@ export type TraceFilterRule = {
|
|
|
315
317
|
exclude?: string[];
|
|
316
318
|
};
|
|
317
319
|
|
|
320
|
+
export type TraceClientRequestCaptureRule = TraceFilterRule & {
|
|
321
|
+
requestHeaders?: boolean;
|
|
322
|
+
requestBody?: boolean;
|
|
323
|
+
responseHeaders?: boolean;
|
|
324
|
+
responseBody?: boolean;
|
|
325
|
+
};
|
|
326
|
+
|
|
318
327
|
export type TraceRequestWatcherConfig = TraceFilterRule & {
|
|
319
328
|
all?: TraceFilterRule;
|
|
320
329
|
get?: TraceFilterRule;
|
|
@@ -324,8 +333,13 @@ export type TraceRequestWatcherConfig = TraceFilterRule & {
|
|
|
324
333
|
delete?: TraceFilterRule;
|
|
325
334
|
};
|
|
326
335
|
|
|
336
|
+
export type TraceClientRequestWatcherConfig = TraceClientRequestCaptureRule & {
|
|
337
|
+
sources?: Record<string, TraceClientRequestCaptureRule>;
|
|
338
|
+
};
|
|
339
|
+
|
|
327
340
|
export type TraceWatcherToggle = boolean | TraceFilterRule;
|
|
328
341
|
export type TraceRequestWatcherToggle = boolean | TraceRequestWatcherConfig;
|
|
342
|
+
export type TraceClientRequestWatcherToggle = boolean | TraceClientRequestWatcherConfig;
|
|
329
343
|
|
|
330
344
|
export type WatcherToggles = {
|
|
331
345
|
request?: TraceRequestWatcherToggle;
|
|
@@ -347,7 +361,7 @@ export type WatcherToggles = {
|
|
|
347
361
|
batch?: TraceWatcherToggle;
|
|
348
362
|
dump?: TraceWatcherToggle;
|
|
349
363
|
view?: TraceWatcherToggle;
|
|
350
|
-
clientRequest?:
|
|
364
|
+
clientRequest?: TraceClientRequestWatcherToggle;
|
|
351
365
|
};
|
|
352
366
|
|
|
353
367
|
export interface ITraceConfig {
|
package/src/utils/entryFilter.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ITraceConfig,
|
|
3
3
|
ITraceEntry,
|
|
4
|
+
TraceClientRequestWatcherConfig,
|
|
4
5
|
TraceFilterRule,
|
|
5
6
|
TraceRequestWatcherConfig,
|
|
6
7
|
WatcherToggles,
|
|
@@ -22,6 +23,7 @@ const normalizeTerms = (terms?: string[]): string[] => {
|
|
|
22
23
|
|
|
23
24
|
const matchesRule = (haystack: string, rule?: TraceFilterRule): boolean => {
|
|
24
25
|
if (!rule) return true;
|
|
26
|
+
if (rule.enabled === false) return false;
|
|
25
27
|
|
|
26
28
|
const include = normalizeTerms(rule.include);
|
|
27
29
|
const exclude = normalizeTerms(rule.exclude);
|
|
@@ -86,6 +88,20 @@ const getRequestMethodRule = (
|
|
|
86
88
|
return watcher.all;
|
|
87
89
|
};
|
|
88
90
|
|
|
91
|
+
const getClientRequestSourceRule = (
|
|
92
|
+
watcher: TraceClientRequestWatcherConfig,
|
|
93
|
+
entry: ITraceEntry
|
|
94
|
+
): TraceFilterRule | undefined => {
|
|
95
|
+
if (entry.type !== EntryType.CLIENT_REQUEST) return undefined;
|
|
96
|
+
|
|
97
|
+
const content = isObjectValue(entry.content) ? entry.content : undefined;
|
|
98
|
+
const sourceValue = content?.['source'];
|
|
99
|
+
const source = typeof sourceValue === 'string' ? sourceValue.trim().toLowerCase() : '';
|
|
100
|
+
|
|
101
|
+
if (source === '') return undefined;
|
|
102
|
+
return watcher.sources?.[source];
|
|
103
|
+
};
|
|
104
|
+
|
|
89
105
|
export const TraceEntryFilter = Object.freeze({
|
|
90
106
|
shouldCapture(entry: ITraceEntry, config: ITraceConfig): boolean {
|
|
91
107
|
const watcherKey = watcherKeyByEntryType[entry.type];
|
|
@@ -103,6 +119,13 @@ export const TraceEntryFilter = Object.freeze({
|
|
|
103
119
|
if (!matchesRule(haystack, methodRule)) return false;
|
|
104
120
|
}
|
|
105
121
|
|
|
122
|
+
if (watcherKey === 'clientRequest') {
|
|
123
|
+
const clientRequestWatcher = watcher as TraceClientRequestWatcherConfig;
|
|
124
|
+
const sourceRule = getClientRequestSourceRule(clientRequestWatcher, entry);
|
|
125
|
+
if (sourceRule?.enabled === false) return false;
|
|
126
|
+
if (!matchesRule(haystack, sourceRule)) return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
return true;
|
|
107
130
|
},
|
|
108
131
|
});
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
ClientRequestTraceInput,
|
|
5
5
|
ITraceWatcher,
|
|
6
6
|
ITraceWatcherConfig,
|
|
7
|
+
TraceClientRequestCaptureRule,
|
|
8
|
+
TraceClientRequestWatcherConfig,
|
|
7
9
|
} from '../types';
|
|
8
10
|
import { EntryType } from '../types';
|
|
9
11
|
import { AuthTag } from '../utils/authTag';
|
|
@@ -14,8 +16,105 @@ let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
|
14
16
|
let _redactHeaderNames: string[] = [];
|
|
15
17
|
let _redactBodyFields: string[] = [];
|
|
16
18
|
let _ignoreRoutes: string[] = [];
|
|
19
|
+
let _clientRequestWatcher: TraceClientRequestWatcherConfig | undefined;
|
|
20
|
+
|
|
21
|
+
const isObjectValue = (value: unknown): value is Record<string, unknown> => {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const resolveSource = (value: unknown): string | undefined => {
|
|
26
|
+
if (typeof value !== 'string') return undefined;
|
|
27
|
+
const normalized = value.trim().toLowerCase();
|
|
28
|
+
return normalized === '' ? undefined : normalized;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const resolveSourceRule = (
|
|
32
|
+
source: string | undefined
|
|
33
|
+
): TraceClientRequestCaptureRule | undefined => {
|
|
34
|
+
if (source === undefined) return undefined;
|
|
35
|
+
return _clientRequestWatcher?.sources?.[source];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const shouldCaptureField = (
|
|
39
|
+
field: keyof Pick<
|
|
40
|
+
TraceClientRequestCaptureRule,
|
|
41
|
+
'requestHeaders' | 'requestBody' | 'responseHeaders' | 'responseBody'
|
|
42
|
+
>,
|
|
43
|
+
sourceRule: TraceClientRequestCaptureRule | undefined
|
|
44
|
+
): boolean => {
|
|
45
|
+
const scoped = sourceRule?.[field];
|
|
46
|
+
if (typeof scoped === 'boolean') return scoped;
|
|
47
|
+
const global = _clientRequestWatcher?.[field];
|
|
48
|
+
if (typeof global === 'boolean') return global;
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const buildRequestHeaders = (
|
|
53
|
+
requestHeaders: Record<string, string>,
|
|
54
|
+
sourceRule: TraceClientRequestCaptureRule | undefined
|
|
55
|
+
): Pick<ClientRequestContent, 'requestHeaders'> => {
|
|
56
|
+
return shouldCaptureField('requestHeaders', sourceRule)
|
|
57
|
+
? { requestHeaders: redactHeaders(requestHeaders, _redactHeaderNames) }
|
|
58
|
+
: { requestHeaders: {} };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const buildRequestBody = (
|
|
62
|
+
requestBody: unknown,
|
|
63
|
+
sourceRule: TraceClientRequestCaptureRule | undefined
|
|
64
|
+
): Partial<Pick<ClientRequestContent, 'requestBody'>> => {
|
|
65
|
+
if (requestBody === undefined) return {};
|
|
66
|
+
if (!shouldCaptureField('requestBody', sourceRule)) return {};
|
|
67
|
+
return { requestBody: redactUnknown(requestBody, _redactBodyFields) };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const buildResponseHeaders = (
|
|
71
|
+
responseHeaders: Record<string, string> | undefined,
|
|
72
|
+
sourceRule: TraceClientRequestCaptureRule | undefined
|
|
73
|
+
): Partial<Pick<ClientRequestContent, 'responseHeaders'>> => {
|
|
74
|
+
if (responseHeaders === undefined) return {};
|
|
75
|
+
if (!shouldCaptureField('responseHeaders', sourceRule)) return {};
|
|
76
|
+
return { responseHeaders: redactHeaders(responseHeaders, _redactHeaderNames) };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildResponseBody = (
|
|
80
|
+
responseBody: unknown,
|
|
81
|
+
sourceRule: TraceClientRequestCaptureRule | undefined
|
|
82
|
+
): Partial<Pick<ClientRequestContent, 'responseBody'>> => {
|
|
83
|
+
if (responseBody === undefined) return {};
|
|
84
|
+
if (!shouldCaptureField('responseBody', sourceRule)) return {};
|
|
85
|
+
return { responseBody: redactUnknown(responseBody, _redactBodyFields) };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const buildClientRequestContent = (
|
|
89
|
+
input: ClientRequestTraceInput,
|
|
90
|
+
sourceRule: TraceClientRequestCaptureRule | undefined,
|
|
91
|
+
normalizedSource: string | undefined
|
|
92
|
+
): ClientRequestContent => {
|
|
93
|
+
return {
|
|
94
|
+
...(normalizedSource === undefined ? {} : { source: normalizedSource }),
|
|
95
|
+
method: input.method.toUpperCase(),
|
|
96
|
+
url: input.url,
|
|
97
|
+
...buildRequestHeaders(input.requestHeaders, sourceRule),
|
|
98
|
+
...buildRequestBody(input.requestBody, sourceRule),
|
|
99
|
+
...(input.responseStatus === undefined ? {} : { responseStatus: input.responseStatus }),
|
|
100
|
+
...buildResponseHeaders(input.responseHeaders, sourceRule),
|
|
101
|
+
...buildResponseBody(input.responseBody, sourceRule),
|
|
102
|
+
...(typeof input.error === 'string' && input.error !== '' ? { error: input.error } : {}),
|
|
103
|
+
duration: input.duration,
|
|
104
|
+
hostname: TraceContext.getHostname(),
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const isWatcherEnabled = (
|
|
109
|
+
value: ITraceWatcherConfig['config']['watchers']['clientRequest']
|
|
110
|
+
): boolean => {
|
|
111
|
+
if (value === false) return false;
|
|
112
|
+
if (isObjectValue(value) && value.enabled === false) return false;
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
17
115
|
|
|
18
116
|
const emit = ({
|
|
117
|
+
source,
|
|
19
118
|
method,
|
|
20
119
|
url,
|
|
21
120
|
requestHeaders,
|
|
@@ -28,26 +127,28 @@ const emit = ({
|
|
|
28
127
|
}: ClientRequestTraceInput): void => {
|
|
29
128
|
if (!_storage) return;
|
|
30
129
|
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes)) return;
|
|
130
|
+
const normalizedSource = resolveSource(source);
|
|
131
|
+
const sourceRule = resolveSourceRule(normalizedSource);
|
|
132
|
+
if (sourceRule?.enabled === false) return;
|
|
31
133
|
const tags = AuthTag.append([method.toUpperCase()]);
|
|
32
134
|
if ((responseStatus ?? 0) >= 400 || error) tags.push('failed');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
135
|
+
if (normalizedSource !== undefined) tags.push(normalizedSource);
|
|
136
|
+
const content = buildClientRequestContent(
|
|
137
|
+
{
|
|
138
|
+
source,
|
|
139
|
+
method,
|
|
140
|
+
url,
|
|
141
|
+
requestHeaders,
|
|
142
|
+
responseStatus,
|
|
143
|
+
duration,
|
|
144
|
+
requestBody,
|
|
145
|
+
responseHeaders,
|
|
146
|
+
responseBody,
|
|
147
|
+
error,
|
|
148
|
+
},
|
|
149
|
+
sourceRule,
|
|
150
|
+
normalizedSource
|
|
151
|
+
);
|
|
51
152
|
_storage
|
|
52
153
|
.writeEntry({
|
|
53
154
|
uuid: crypto.randomUUID(),
|
|
@@ -64,13 +165,18 @@ const emit = ({
|
|
|
64
165
|
export const HttpClientWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
|
|
65
166
|
emit,
|
|
66
167
|
register({ storage, config }: ITraceWatcherConfig): () => void {
|
|
67
|
-
if (config.watchers.clientRequest
|
|
168
|
+
if (!isWatcherEnabled(config.watchers.clientRequest)) return () => undefined;
|
|
68
169
|
_storage = storage;
|
|
170
|
+
_clientRequestWatcher =
|
|
171
|
+
typeof config.watchers.clientRequest === 'object' && config.watchers.clientRequest !== null
|
|
172
|
+
? (config.watchers.clientRequest as TraceClientRequestWatcherConfig)
|
|
173
|
+
: undefined;
|
|
69
174
|
_redactHeaderNames = [...(config.redaction?.keys ?? []), ...(config.redaction?.headers ?? [])];
|
|
70
175
|
_redactBodyFields = [...(config.redaction?.keys ?? []), ...(config.redaction?.body ?? [])];
|
|
71
176
|
_ignoreRoutes = config.ignoreRoutes;
|
|
72
177
|
return () => {
|
|
73
178
|
_storage = null;
|
|
179
|
+
_clientRequestWatcher = undefined;
|
|
74
180
|
_redactBodyFields = [];
|
|
75
181
|
_ignoreRoutes = [];
|
|
76
182
|
};
|
|
@@ -26,6 +26,13 @@ const normalizeHeaderValue = (value: string | string[]): string => {
|
|
|
26
26
|
return Array.isArray(value) ? value.join(', ') : value;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const resolveRouteMiddleware = (req: IRequest): string[] => {
|
|
30
|
+
const middleware = req.context?.['traceRouteMiddleware'];
|
|
31
|
+
return Array.isArray(middleware)
|
|
32
|
+
? middleware.filter((value): value is string => typeof value === 'string')
|
|
33
|
+
: [];
|
|
34
|
+
};
|
|
35
|
+
|
|
29
36
|
const resolveRequestPayload = (req: IRequest, config: ITraceConfig): unknown => {
|
|
30
37
|
const redactFields = [...config.redaction.keys, ...config.redaction.body];
|
|
31
38
|
const requestBody = typeof req.getBody === 'function' ? req.getBody() : req.body;
|
|
@@ -150,7 +157,7 @@ const buildEntry = (
|
|
|
150
157
|
responseBody: responseCapture.body,
|
|
151
158
|
duration: Date.now() - start,
|
|
152
159
|
memory: TraceContext.getMemory(),
|
|
153
|
-
middleware:
|
|
160
|
+
middleware: resolveRouteMiddleware(req),
|
|
154
161
|
hostname: TraceContext.getHostname(),
|
|
155
162
|
userId: TraceContext.getUserId(),
|
|
156
163
|
};
|
|
@@ -6,6 +6,10 @@ import { RequestFilter } from '../utils/requestFilter';
|
|
|
6
6
|
let _storage: ITraceWatcherConfig['storage'] | null = null;
|
|
7
7
|
let _ignoreRoutes: string[] = [];
|
|
8
8
|
|
|
9
|
+
type GlobalMiddlewareTraceState = {
|
|
10
|
+
__zintrust_trace_middleware_emit__?: typeof emit;
|
|
11
|
+
};
|
|
12
|
+
|
|
9
13
|
const emit = (name: string, event: MiddlewareContent['event'], duration?: number): void => {
|
|
10
14
|
if (!_storage) return;
|
|
11
15
|
if (RequestFilter.shouldIgnoreCurrentRequest(_ignoreRoutes)) return;
|
|
@@ -34,7 +38,12 @@ export const MiddlewareWatcher: ITraceWatcher & { emit: typeof emit } = Object.f
|
|
|
34
38
|
if (config.watchers.middleware === false) return () => undefined;
|
|
35
39
|
_storage = storage;
|
|
36
40
|
_ignoreRoutes = config.ignoreRoutes;
|
|
41
|
+
(globalThis as unknown as GlobalMiddlewareTraceState).__zintrust_trace_middleware_emit__ = emit;
|
|
37
42
|
return () => {
|
|
43
|
+
const globalState = globalThis as unknown as GlobalMiddlewareTraceState;
|
|
44
|
+
if (globalState.__zintrust_trace_middleware_emit__ === emit) {
|
|
45
|
+
delete globalState.__zintrust_trace_middleware_emit__;
|
|
46
|
+
}
|
|
38
47
|
_storage = null;
|
|
39
48
|
_ignoreRoutes = [];
|
|
40
49
|
};
|