@fedify/debugger 2.5.0-dev.2287 → 2.5.0-dev.2304
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/mod.cjs +205 -19
- package/dist/mod.js +206 -20
- package/package.json +2 -2
package/dist/mod.cjs
CHANGED
|
@@ -361,6 +361,13 @@ const Layout = ({ title, pathPrefix, children }) => {
|
|
|
361
361
|
pre { background: #f6f8fa; padding: 1rem; overflow-x: auto; border-radius: 6px; font-size: 0.8125rem; }
|
|
362
362
|
.empty { color: #888; font-style: italic; }
|
|
363
363
|
nav a { margin-right: 0.5rem; }
|
|
364
|
+
.filter-form { border: 1px solid #ddd; border-radius: 6px; padding: 0.75rem 1rem; margin-bottom: 1rem; }
|
|
365
|
+
.filter-form fieldset { border: none; padding: 0; margin: 0; }
|
|
366
|
+
.filter-form legend { font-size: 0.8125rem; color: #666; padding: 0; margin-bottom: 0.5rem; }
|
|
367
|
+
.filter-form label { display: inline-flex; align-items: center; gap: 0.3rem; margin: 0 1rem 0.5rem 0; font-size: 0.875rem; }
|
|
368
|
+
.filter-form select, .filter-form input[type="text"] { font-size: 0.875rem; padding: 0.25rem 0.4rem; }
|
|
369
|
+
.filter-actions { display: flex; align-items: center; gap: 1rem; margin-top: 0.25rem; }
|
|
370
|
+
.filter-actions button { font-size: 0.8125rem; padding: 0.3rem 0.75rem; cursor: pointer; }
|
|
364
371
|
.log-table td { font-size: 0.8125rem; vertical-align: top; }
|
|
365
372
|
.log-table time { font-family: monospace; white-space: nowrap; }
|
|
366
373
|
.badge-debug { background: #e8e8e8; color: #666; }
|
|
@@ -384,6 +391,8 @@ const Layout = ({ title, pathPrefix, children }) => {
|
|
|
384
391
|
.detail-section h2 { border-bottom-color: #21262d; }
|
|
385
392
|
pre { background: #161b22; }
|
|
386
393
|
.empty { color: #9198a1; }
|
|
394
|
+
.filter-form { border-color: #30363d; }
|
|
395
|
+
.filter-form legend { color: #9198a1; }
|
|
387
396
|
.badge-debug { background: #21262d; color: #9198a1; }
|
|
388
397
|
.badge-info { background: #122d42; color: #58a6ff; }
|
|
389
398
|
.badge-warning { background: #2e2a1f; color: #d29922; }
|
|
@@ -541,7 +550,8 @@ function safeISOString(timestamp) {
|
|
|
541
550
|
/**
|
|
542
551
|
* The trace detail page of the debug dashboard.
|
|
543
552
|
*/
|
|
544
|
-
const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
553
|
+
const TraceDetailPage = ({ traceId, activities, logs, totalLogCount, availableCategories, selectedCategory, selectedLevel, selectedQuery, pathPrefix }) => {
|
|
554
|
+
const filtered = Boolean(selectedCategory) || Boolean(selectedLevel) || Boolean(selectedQuery);
|
|
545
555
|
return /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)(Layout, {
|
|
546
556
|
pathPrefix,
|
|
547
557
|
title: `Trace ${traceId.slice(0, 8)}`,
|
|
@@ -560,11 +570,20 @@ const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
|
560
570
|
" ",
|
|
561
571
|
"activit",
|
|
562
572
|
activities.length !== 1 ? "ies" : "y",
|
|
563
|
-
",",
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
573
|
+
", ",
|
|
574
|
+
filtered ? /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("span", { children: [
|
|
575
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("strong", { children: logs.length }),
|
|
576
|
+
" of ",
|
|
577
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("strong", { children: totalLogCount }),
|
|
578
|
+
" ",
|
|
579
|
+
"log record",
|
|
580
|
+
totalLogCount !== 1 ? "s" : ""
|
|
581
|
+
] }) : /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("span", { children: [
|
|
582
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("strong", { children: logs.length }),
|
|
583
|
+
" ",
|
|
584
|
+
"log record",
|
|
585
|
+
logs.length !== 1 ? "s" : ""
|
|
586
|
+
] })
|
|
568
587
|
] }),
|
|
569
588
|
activities.length === 0 ? /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("p", {
|
|
570
589
|
class: "empty",
|
|
@@ -610,9 +629,54 @@ const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
|
610
629
|
]
|
|
611
630
|
}, activity.spanId)),
|
|
612
631
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("h2", { children: "Logs" }),
|
|
632
|
+
availableCategories.length > 0 && /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("form", {
|
|
633
|
+
class: "filter-form",
|
|
634
|
+
method: "get",
|
|
635
|
+
action: `${pathPrefix}/traces/${traceId}`,
|
|
636
|
+
children: [
|
|
637
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("label", { children: ["Category", /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("select", {
|
|
638
|
+
name: "category",
|
|
639
|
+
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("option", {
|
|
640
|
+
value: "",
|
|
641
|
+
children: "All categories"
|
|
642
|
+
}), availableCategories.map((category) => /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("option", {
|
|
643
|
+
value: category,
|
|
644
|
+
selected: category === selectedCategory,
|
|
645
|
+
children: category
|
|
646
|
+
}, category))]
|
|
647
|
+
})] }),
|
|
648
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("label", { children: ["Level", /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("select", {
|
|
649
|
+
name: "level",
|
|
650
|
+
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("option", {
|
|
651
|
+
value: "",
|
|
652
|
+
children: "All levels"
|
|
653
|
+
}), (0, _logtape_logtape.getLogLevels)().map((level) => /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("option", {
|
|
654
|
+
value: level,
|
|
655
|
+
selected: level === selectedLevel,
|
|
656
|
+
children: level
|
|
657
|
+
}, level))]
|
|
658
|
+
})] }),
|
|
659
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("label", { children: ["Search", /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("input", {
|
|
660
|
+
type: "text",
|
|
661
|
+
name: "q",
|
|
662
|
+
value: selectedQuery ?? "",
|
|
663
|
+
placeholder: "Search message…"
|
|
664
|
+
})] }),
|
|
665
|
+
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("div", {
|
|
666
|
+
class: "filter-actions",
|
|
667
|
+
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("button", {
|
|
668
|
+
type: "submit",
|
|
669
|
+
children: "Filter"
|
|
670
|
+
}), filtered && /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("a", {
|
|
671
|
+
href: `${pathPrefix}/traces/${traceId}`,
|
|
672
|
+
children: "Clear filters"
|
|
673
|
+
})]
|
|
674
|
+
})
|
|
675
|
+
]
|
|
676
|
+
}),
|
|
613
677
|
logs.length === 0 ? /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("p", {
|
|
614
678
|
class: "empty",
|
|
615
|
-
children: "No logs captured for this trace."
|
|
679
|
+
children: filtered ? "No logs match the selected filters." : "No logs captured for this trace."
|
|
616
680
|
}) : /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("table", {
|
|
617
681
|
class: "log-table",
|
|
618
682
|
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("tr", { children: [
|
|
@@ -654,10 +718,31 @@ function formatJson(json) {
|
|
|
654
718
|
/**
|
|
655
719
|
* The traces list page of the debug dashboard.
|
|
656
720
|
*/
|
|
657
|
-
const TracesListPage = ({ traces, pathPrefix }) => {
|
|
721
|
+
const TracesListPage = ({ traces, availableTypes, selectedTypes, initialSnapshot, pathPrefix }) => {
|
|
722
|
+
const filtered = selectedTypes.length > 0;
|
|
658
723
|
return /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)(Layout, {
|
|
659
724
|
pathPrefix,
|
|
660
725
|
children: [
|
|
726
|
+
availableTypes.length > 0 && /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("form", {
|
|
727
|
+
class: "filter-form",
|
|
728
|
+
method: "get",
|
|
729
|
+
action: `${pathPrefix}/`,
|
|
730
|
+
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("fieldset", { children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("legend", { children: "Filter by activity type" }), availableTypes.map((type) => /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("input", {
|
|
731
|
+
type: "checkbox",
|
|
732
|
+
name: "type",
|
|
733
|
+
value: type,
|
|
734
|
+
checked: selectedTypes.includes(type)
|
|
735
|
+
}), type] }, type))] }), /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("div", {
|
|
736
|
+
class: "filter-actions",
|
|
737
|
+
children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("button", {
|
|
738
|
+
type: "submit",
|
|
739
|
+
children: "Filter"
|
|
740
|
+
}), filtered && /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("a", {
|
|
741
|
+
href: `${pathPrefix}/`,
|
|
742
|
+
children: "Clear filters"
|
|
743
|
+
})]
|
|
744
|
+
})]
|
|
745
|
+
}),
|
|
661
746
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("p", { children: [
|
|
662
747
|
"Showing ",
|
|
663
748
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("strong", { children: traces.length }),
|
|
@@ -668,7 +753,7 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
668
753
|
] }),
|
|
669
754
|
traces.length === 0 ? /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("p", {
|
|
670
755
|
class: "empty",
|
|
671
|
-
children: "No traces captured yet."
|
|
756
|
+
children: filtered ? "No traces match the selected filters." : "No traces captured yet."
|
|
672
757
|
}) : /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("table", { children: [/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsxs)("tr", { children: [
|
|
673
758
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("th", { children: "Trace ID" }),
|
|
674
759
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("th", { children: "Activity Types" }),
|
|
@@ -694,17 +779,40 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
694
779
|
] }, trace.traceId)) })] }),
|
|
695
780
|
/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)("script", { dangerouslySetInnerHTML: { __html: `
|
|
696
781
|
(function() {
|
|
782
|
+
var selectedTypes = new URLSearchParams(location.search).getAll("type");
|
|
783
|
+
function snapshotOf(data) {
|
|
784
|
+
var types = [];
|
|
785
|
+
var rows = [];
|
|
786
|
+
for (var i = 0; i < data.length; i++) {
|
|
787
|
+
var activityTypes = (data[i].activityTypes || []).slice().sort();
|
|
788
|
+
var matchesFilter = selectedTypes.length === 0;
|
|
789
|
+
for (var j = 0; j < activityTypes.length; j++) {
|
|
790
|
+
if (types.indexOf(activityTypes[j]) === -1) types.push(activityTypes[j]);
|
|
791
|
+
if (!matchesFilter && selectedTypes.indexOf(activityTypes[j]) !== -1) {
|
|
792
|
+
matchesFilter = true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (matchesFilter) {
|
|
796
|
+
rows.push(
|
|
797
|
+
data[i].traceId + ":" + activityTypes.join("+") + ":" +
|
|
798
|
+
data[i].activityCount,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
types.sort();
|
|
803
|
+
rows.sort();
|
|
804
|
+
return types.join(",") + "|" + rows.join(";");
|
|
805
|
+
}
|
|
806
|
+
var prevSnapshot = ${JSON.stringify(initialSnapshot).replace(/</g, "\\u003c")};
|
|
697
807
|
var interval = setInterval(function() {
|
|
698
808
|
fetch(${JSON.stringify(pathPrefix).replace(/</g, "\\u003c")} + "/api/traces")
|
|
699
809
|
.then(function(r) { return r.json(); })
|
|
700
810
|
.then(function(data) {
|
|
701
|
-
var
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
if (data.length !== current) {
|
|
705
|
-
location.reload();
|
|
706
|
-
}
|
|
811
|
+
var snapshot = snapshotOf(data);
|
|
812
|
+
if (snapshot !== prevSnapshot) {
|
|
813
|
+
location.reload();
|
|
707
814
|
}
|
|
815
|
+
prevSnapshot = snapshot;
|
|
708
816
|
})
|
|
709
817
|
.catch(function() {});
|
|
710
818
|
}, 3000);
|
|
@@ -718,6 +826,64 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
718
826
|
};
|
|
719
827
|
//#endregion
|
|
720
828
|
//#region src/routes.tsx
|
|
829
|
+
/** Collects the distinct activity types across all traces, sorted. */
|
|
830
|
+
function distinctActivityTypes(traces) {
|
|
831
|
+
const types = /* @__PURE__ */ new Set();
|
|
832
|
+
for (const trace of traces) for (const type of trace.activityTypes) types.add(type);
|
|
833
|
+
return [...types].sort();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Keeps only the traces whose `activityTypes` include at least one of the
|
|
837
|
+
* given `types`. An empty `types` list means "no filter"—all traces pass
|
|
838
|
+
* through unchanged.
|
|
839
|
+
*/
|
|
840
|
+
function filterTracesByTypes(traces, types) {
|
|
841
|
+
if (types.length === 0) return [...traces];
|
|
842
|
+
return traces.filter((trace) => trace.activityTypes.some((type) => types.includes(type)));
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Computes a string that changes whenever the trace corpus changes in a
|
|
846
|
+
* way the traces list page's live-poll script needs to react to. Rather
|
|
847
|
+
* than comparing a handful of hand-picked summary fields—which has missed
|
|
848
|
+
* a real change each time a new one turned up—this serializes every trace
|
|
849
|
+
* that currently matches `selectedTypes` as `id:types:activityCount`, so
|
|
850
|
+
* a trace being replaced, an existing trace's activity count going up, or
|
|
851
|
+
* its activity types growing are all covered by the same comparison. The
|
|
852
|
+
* distinct activity types across *all* traces are tracked separately,
|
|
853
|
+
* since a type that does not match the active filter still needs to make
|
|
854
|
+
* its checkbox appear. The page embeds this as the poll script's
|
|
855
|
+
* starting point, so the very first poll tick is compared against the
|
|
856
|
+
* data the page was actually rendered with, rather than treating
|
|
857
|
+
* whatever that first tick happens to see as the baseline.
|
|
858
|
+
*/
|
|
859
|
+
function snapshotOf(traces, selectedTypes) {
|
|
860
|
+
const types = distinctActivityTypes(traces);
|
|
861
|
+
const rows = filterTracesByTypes(traces, selectedTypes).map((trace) => `${trace.traceId}:${[...trace.activityTypes].sort().join("+")}:${trace.activityCount}`).sort();
|
|
862
|
+
return `${types.join(",")}|${rows.join(";")}`;
|
|
863
|
+
}
|
|
864
|
+
/** Collects the distinct dot-joined log categories, sorted. */
|
|
865
|
+
function distinctLogCategories(logs) {
|
|
866
|
+
const categories = /* @__PURE__ */ new Set();
|
|
867
|
+
for (const log of logs) categories.add(log.category.join("."));
|
|
868
|
+
return [...categories].sort();
|
|
869
|
+
}
|
|
870
|
+
/** Applies a {@link LogFilter} to a list of log records. */
|
|
871
|
+
function filterLogs(logs, filter) {
|
|
872
|
+
let filtered = logs;
|
|
873
|
+
if (filter.category) {
|
|
874
|
+
const category = filter.category;
|
|
875
|
+
filtered = filtered.filter((log) => log.category.join(".") === category);
|
|
876
|
+
}
|
|
877
|
+
if (filter.level) {
|
|
878
|
+
const level = filter.level;
|
|
879
|
+
filtered = filtered.filter((log) => log.level === level);
|
|
880
|
+
}
|
|
881
|
+
if (filter.q) {
|
|
882
|
+
const needle = filter.q.toLowerCase();
|
|
883
|
+
filtered = filtered.filter((log) => log.message.toLowerCase().includes(needle));
|
|
884
|
+
}
|
|
885
|
+
return filtered;
|
|
886
|
+
}
|
|
721
887
|
function createDebugApp(pathPrefix, exporter, logStore, auth) {
|
|
722
888
|
const app = new hono.Hono({ strict: false }).basePath(pathPrefix);
|
|
723
889
|
let hmacKeyPromise;
|
|
@@ -782,30 +948,50 @@ function createDebugApp(pathPrefix, exporter, logStore, auth) {
|
|
|
782
948
|
}
|
|
783
949
|
app.get("/api/traces", async (c) => {
|
|
784
950
|
const traces = await exporter.getRecentTraces();
|
|
785
|
-
|
|
951
|
+
const types = c.req.queries("type") ?? [];
|
|
952
|
+
return c.json(filterTracesByTypes(traces, types));
|
|
786
953
|
});
|
|
787
954
|
app.get("/api/logs/:traceId", async (c) => {
|
|
788
955
|
const traceId = c.req.param("traceId");
|
|
789
956
|
await logStore.flush();
|
|
790
957
|
const logs = await logStore.get(traceId);
|
|
791
|
-
|
|
958
|
+
const logFilter = {
|
|
959
|
+
category: c.req.query("category"),
|
|
960
|
+
level: c.req.query("level"),
|
|
961
|
+
q: c.req.query("q")
|
|
962
|
+
};
|
|
963
|
+
return c.json(filterLogs(logs, logFilter));
|
|
792
964
|
});
|
|
793
965
|
app.get("/traces/:traceId", async (c) => {
|
|
794
966
|
const traceId = c.req.param("traceId");
|
|
795
967
|
await logStore.flush();
|
|
796
968
|
const activities = await exporter.getActivitiesByTraceId(traceId);
|
|
797
969
|
const logs = await logStore.get(traceId);
|
|
970
|
+
const logFilter = {
|
|
971
|
+
category: c.req.query("category"),
|
|
972
|
+
level: c.req.query("level"),
|
|
973
|
+
q: c.req.query("q")
|
|
974
|
+
};
|
|
798
975
|
return c.html(/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)(TraceDetailPage, {
|
|
799
976
|
traceId,
|
|
800
977
|
activities,
|
|
801
|
-
logs,
|
|
978
|
+
logs: filterLogs(logs, logFilter),
|
|
979
|
+
totalLogCount: logs.length,
|
|
980
|
+
availableCategories: distinctLogCategories(logs),
|
|
981
|
+
selectedCategory: logFilter.category,
|
|
982
|
+
selectedLevel: logFilter.level,
|
|
983
|
+
selectedQuery: logFilter.q,
|
|
802
984
|
pathPrefix
|
|
803
985
|
}));
|
|
804
986
|
});
|
|
805
987
|
app.get("/", async (c) => {
|
|
806
988
|
const traces = await exporter.getRecentTraces();
|
|
989
|
+
const selectedTypes = c.req.queries("type") ?? [];
|
|
807
990
|
return c.html(/* @__PURE__ */ (0, hono_jsx_jsx_runtime.jsx)(TracesListPage, {
|
|
808
|
-
traces,
|
|
991
|
+
traces: filterTracesByTypes(traces, selectedTypes),
|
|
992
|
+
availableTypes: distinctActivityTypes(traces),
|
|
993
|
+
selectedTypes,
|
|
994
|
+
initialSnapshot: snapshotOf(traces, selectedTypes),
|
|
809
995
|
pathPrefix
|
|
810
996
|
}));
|
|
811
997
|
});
|
package/dist/mod.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { MemoryKvStore } from "@fedify/fedify/federation";
|
|
3
3
|
import { FedifySpanExporter } from "@fedify/fedify/otel";
|
|
4
|
-
import { configureSync, getConfig } from "@logtape/logtape";
|
|
4
|
+
import { configureSync, getConfig, getLogLevels } from "@logtape/logtape";
|
|
5
5
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
6
6
|
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
7
7
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
@@ -360,6 +360,13 @@ const Layout = ({ title, pathPrefix, children }) => {
|
|
|
360
360
|
pre { background: #f6f8fa; padding: 1rem; overflow-x: auto; border-radius: 6px; font-size: 0.8125rem; }
|
|
361
361
|
.empty { color: #888; font-style: italic; }
|
|
362
362
|
nav a { margin-right: 0.5rem; }
|
|
363
|
+
.filter-form { border: 1px solid #ddd; border-radius: 6px; padding: 0.75rem 1rem; margin-bottom: 1rem; }
|
|
364
|
+
.filter-form fieldset { border: none; padding: 0; margin: 0; }
|
|
365
|
+
.filter-form legend { font-size: 0.8125rem; color: #666; padding: 0; margin-bottom: 0.5rem; }
|
|
366
|
+
.filter-form label { display: inline-flex; align-items: center; gap: 0.3rem; margin: 0 1rem 0.5rem 0; font-size: 0.875rem; }
|
|
367
|
+
.filter-form select, .filter-form input[type="text"] { font-size: 0.875rem; padding: 0.25rem 0.4rem; }
|
|
368
|
+
.filter-actions { display: flex; align-items: center; gap: 1rem; margin-top: 0.25rem; }
|
|
369
|
+
.filter-actions button { font-size: 0.8125rem; padding: 0.3rem 0.75rem; cursor: pointer; }
|
|
363
370
|
.log-table td { font-size: 0.8125rem; vertical-align: top; }
|
|
364
371
|
.log-table time { font-family: monospace; white-space: nowrap; }
|
|
365
372
|
.badge-debug { background: #e8e8e8; color: #666; }
|
|
@@ -383,6 +390,8 @@ const Layout = ({ title, pathPrefix, children }) => {
|
|
|
383
390
|
.detail-section h2 { border-bottom-color: #21262d; }
|
|
384
391
|
pre { background: #161b22; }
|
|
385
392
|
.empty { color: #9198a1; }
|
|
393
|
+
.filter-form { border-color: #30363d; }
|
|
394
|
+
.filter-form legend { color: #9198a1; }
|
|
386
395
|
.badge-debug { background: #21262d; color: #9198a1; }
|
|
387
396
|
.badge-info { background: #122d42; color: #58a6ff; }
|
|
388
397
|
.badge-warning { background: #2e2a1f; color: #d29922; }
|
|
@@ -540,7 +549,8 @@ function safeISOString(timestamp) {
|
|
|
540
549
|
/**
|
|
541
550
|
* The trace detail page of the debug dashboard.
|
|
542
551
|
*/
|
|
543
|
-
const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
552
|
+
const TraceDetailPage = ({ traceId, activities, logs, totalLogCount, availableCategories, selectedCategory, selectedLevel, selectedQuery, pathPrefix }) => {
|
|
553
|
+
const filtered = Boolean(selectedCategory) || Boolean(selectedLevel) || Boolean(selectedQuery);
|
|
544
554
|
return /* @__PURE__ */ jsxs(Layout, {
|
|
545
555
|
pathPrefix,
|
|
546
556
|
title: `Trace ${traceId.slice(0, 8)}`,
|
|
@@ -559,11 +569,20 @@ const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
|
559
569
|
" ",
|
|
560
570
|
"activit",
|
|
561
571
|
activities.length !== 1 ? "ies" : "y",
|
|
562
|
-
",",
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
572
|
+
", ",
|
|
573
|
+
filtered ? /* @__PURE__ */ jsxs("span", { children: [
|
|
574
|
+
/* @__PURE__ */ jsx("strong", { children: logs.length }),
|
|
575
|
+
" of ",
|
|
576
|
+
/* @__PURE__ */ jsx("strong", { children: totalLogCount }),
|
|
577
|
+
" ",
|
|
578
|
+
"log record",
|
|
579
|
+
totalLogCount !== 1 ? "s" : ""
|
|
580
|
+
] }) : /* @__PURE__ */ jsxs("span", { children: [
|
|
581
|
+
/* @__PURE__ */ jsx("strong", { children: logs.length }),
|
|
582
|
+
" ",
|
|
583
|
+
"log record",
|
|
584
|
+
logs.length !== 1 ? "s" : ""
|
|
585
|
+
] })
|
|
567
586
|
] }),
|
|
568
587
|
activities.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
569
588
|
class: "empty",
|
|
@@ -609,9 +628,54 @@ const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
|
|
|
609
628
|
]
|
|
610
629
|
}, activity.spanId)),
|
|
611
630
|
/* @__PURE__ */ jsx("h2", { children: "Logs" }),
|
|
631
|
+
availableCategories.length > 0 && /* @__PURE__ */ jsxs("form", {
|
|
632
|
+
class: "filter-form",
|
|
633
|
+
method: "get",
|
|
634
|
+
action: `${pathPrefix}/traces/${traceId}`,
|
|
635
|
+
children: [
|
|
636
|
+
/* @__PURE__ */ jsxs("label", { children: ["Category", /* @__PURE__ */ jsxs("select", {
|
|
637
|
+
name: "category",
|
|
638
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
639
|
+
value: "",
|
|
640
|
+
children: "All categories"
|
|
641
|
+
}), availableCategories.map((category) => /* @__PURE__ */ jsx("option", {
|
|
642
|
+
value: category,
|
|
643
|
+
selected: category === selectedCategory,
|
|
644
|
+
children: category
|
|
645
|
+
}, category))]
|
|
646
|
+
})] }),
|
|
647
|
+
/* @__PURE__ */ jsxs("label", { children: ["Level", /* @__PURE__ */ jsxs("select", {
|
|
648
|
+
name: "level",
|
|
649
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
650
|
+
value: "",
|
|
651
|
+
children: "All levels"
|
|
652
|
+
}), getLogLevels().map((level) => /* @__PURE__ */ jsx("option", {
|
|
653
|
+
value: level,
|
|
654
|
+
selected: level === selectedLevel,
|
|
655
|
+
children: level
|
|
656
|
+
}, level))]
|
|
657
|
+
})] }),
|
|
658
|
+
/* @__PURE__ */ jsxs("label", { children: ["Search", /* @__PURE__ */ jsx("input", {
|
|
659
|
+
type: "text",
|
|
660
|
+
name: "q",
|
|
661
|
+
value: selectedQuery ?? "",
|
|
662
|
+
placeholder: "Search message…"
|
|
663
|
+
})] }),
|
|
664
|
+
/* @__PURE__ */ jsxs("div", {
|
|
665
|
+
class: "filter-actions",
|
|
666
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
667
|
+
type: "submit",
|
|
668
|
+
children: "Filter"
|
|
669
|
+
}), filtered && /* @__PURE__ */ jsx("a", {
|
|
670
|
+
href: `${pathPrefix}/traces/${traceId}`,
|
|
671
|
+
children: "Clear filters"
|
|
672
|
+
})]
|
|
673
|
+
})
|
|
674
|
+
]
|
|
675
|
+
}),
|
|
612
676
|
logs.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
613
677
|
class: "empty",
|
|
614
|
-
children: "No logs captured for this trace."
|
|
678
|
+
children: filtered ? "No logs match the selected filters." : "No logs captured for this trace."
|
|
615
679
|
}) : /* @__PURE__ */ jsxs("table", {
|
|
616
680
|
class: "log-table",
|
|
617
681
|
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
@@ -653,10 +717,31 @@ function formatJson(json) {
|
|
|
653
717
|
/**
|
|
654
718
|
* The traces list page of the debug dashboard.
|
|
655
719
|
*/
|
|
656
|
-
const TracesListPage = ({ traces, pathPrefix }) => {
|
|
720
|
+
const TracesListPage = ({ traces, availableTypes, selectedTypes, initialSnapshot, pathPrefix }) => {
|
|
721
|
+
const filtered = selectedTypes.length > 0;
|
|
657
722
|
return /* @__PURE__ */ jsxs(Layout, {
|
|
658
723
|
pathPrefix,
|
|
659
724
|
children: [
|
|
725
|
+
availableTypes.length > 0 && /* @__PURE__ */ jsxs("form", {
|
|
726
|
+
class: "filter-form",
|
|
727
|
+
method: "get",
|
|
728
|
+
action: `${pathPrefix}/`,
|
|
729
|
+
children: [/* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx("legend", { children: "Filter by activity type" }), availableTypes.map((type) => /* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
|
|
730
|
+
type: "checkbox",
|
|
731
|
+
name: "type",
|
|
732
|
+
value: type,
|
|
733
|
+
checked: selectedTypes.includes(type)
|
|
734
|
+
}), type] }, type))] }), /* @__PURE__ */ jsxs("div", {
|
|
735
|
+
class: "filter-actions",
|
|
736
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
737
|
+
type: "submit",
|
|
738
|
+
children: "Filter"
|
|
739
|
+
}), filtered && /* @__PURE__ */ jsx("a", {
|
|
740
|
+
href: `${pathPrefix}/`,
|
|
741
|
+
children: "Clear filters"
|
|
742
|
+
})]
|
|
743
|
+
})]
|
|
744
|
+
}),
|
|
660
745
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
661
746
|
"Showing ",
|
|
662
747
|
/* @__PURE__ */ jsx("strong", { children: traces.length }),
|
|
@@ -667,7 +752,7 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
667
752
|
] }),
|
|
668
753
|
traces.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
669
754
|
class: "empty",
|
|
670
|
-
children: "No traces captured yet."
|
|
755
|
+
children: filtered ? "No traces match the selected filters." : "No traces captured yet."
|
|
671
756
|
}) : /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
672
757
|
/* @__PURE__ */ jsx("th", { children: "Trace ID" }),
|
|
673
758
|
/* @__PURE__ */ jsx("th", { children: "Activity Types" }),
|
|
@@ -693,17 +778,40 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
693
778
|
] }, trace.traceId)) })] }),
|
|
694
779
|
/* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
|
|
695
780
|
(function() {
|
|
781
|
+
var selectedTypes = new URLSearchParams(location.search).getAll("type");
|
|
782
|
+
function snapshotOf(data) {
|
|
783
|
+
var types = [];
|
|
784
|
+
var rows = [];
|
|
785
|
+
for (var i = 0; i < data.length; i++) {
|
|
786
|
+
var activityTypes = (data[i].activityTypes || []).slice().sort();
|
|
787
|
+
var matchesFilter = selectedTypes.length === 0;
|
|
788
|
+
for (var j = 0; j < activityTypes.length; j++) {
|
|
789
|
+
if (types.indexOf(activityTypes[j]) === -1) types.push(activityTypes[j]);
|
|
790
|
+
if (!matchesFilter && selectedTypes.indexOf(activityTypes[j]) !== -1) {
|
|
791
|
+
matchesFilter = true;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (matchesFilter) {
|
|
795
|
+
rows.push(
|
|
796
|
+
data[i].traceId + ":" + activityTypes.join("+") + ":" +
|
|
797
|
+
data[i].activityCount,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
types.sort();
|
|
802
|
+
rows.sort();
|
|
803
|
+
return types.join(",") + "|" + rows.join(";");
|
|
804
|
+
}
|
|
805
|
+
var prevSnapshot = ${JSON.stringify(initialSnapshot).replace(/</g, "\\u003c")};
|
|
696
806
|
var interval = setInterval(function() {
|
|
697
807
|
fetch(${JSON.stringify(pathPrefix).replace(/</g, "\\u003c")} + "/api/traces")
|
|
698
808
|
.then(function(r) { return r.json(); })
|
|
699
809
|
.then(function(data) {
|
|
700
|
-
var
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
if (data.length !== current) {
|
|
704
|
-
location.reload();
|
|
705
|
-
}
|
|
810
|
+
var snapshot = snapshotOf(data);
|
|
811
|
+
if (snapshot !== prevSnapshot) {
|
|
812
|
+
location.reload();
|
|
706
813
|
}
|
|
814
|
+
prevSnapshot = snapshot;
|
|
707
815
|
})
|
|
708
816
|
.catch(function() {});
|
|
709
817
|
}, 3000);
|
|
@@ -717,6 +825,64 @@ const TracesListPage = ({ traces, pathPrefix }) => {
|
|
|
717
825
|
};
|
|
718
826
|
//#endregion
|
|
719
827
|
//#region src/routes.tsx
|
|
828
|
+
/** Collects the distinct activity types across all traces, sorted. */
|
|
829
|
+
function distinctActivityTypes(traces) {
|
|
830
|
+
const types = /* @__PURE__ */ new Set();
|
|
831
|
+
for (const trace of traces) for (const type of trace.activityTypes) types.add(type);
|
|
832
|
+
return [...types].sort();
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Keeps only the traces whose `activityTypes` include at least one of the
|
|
836
|
+
* given `types`. An empty `types` list means "no filter"—all traces pass
|
|
837
|
+
* through unchanged.
|
|
838
|
+
*/
|
|
839
|
+
function filterTracesByTypes(traces, types) {
|
|
840
|
+
if (types.length === 0) return [...traces];
|
|
841
|
+
return traces.filter((trace) => trace.activityTypes.some((type) => types.includes(type)));
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Computes a string that changes whenever the trace corpus changes in a
|
|
845
|
+
* way the traces list page's live-poll script needs to react to. Rather
|
|
846
|
+
* than comparing a handful of hand-picked summary fields—which has missed
|
|
847
|
+
* a real change each time a new one turned up—this serializes every trace
|
|
848
|
+
* that currently matches `selectedTypes` as `id:types:activityCount`, so
|
|
849
|
+
* a trace being replaced, an existing trace's activity count going up, or
|
|
850
|
+
* its activity types growing are all covered by the same comparison. The
|
|
851
|
+
* distinct activity types across *all* traces are tracked separately,
|
|
852
|
+
* since a type that does not match the active filter still needs to make
|
|
853
|
+
* its checkbox appear. The page embeds this as the poll script's
|
|
854
|
+
* starting point, so the very first poll tick is compared against the
|
|
855
|
+
* data the page was actually rendered with, rather than treating
|
|
856
|
+
* whatever that first tick happens to see as the baseline.
|
|
857
|
+
*/
|
|
858
|
+
function snapshotOf(traces, selectedTypes) {
|
|
859
|
+
const types = distinctActivityTypes(traces);
|
|
860
|
+
const rows = filterTracesByTypes(traces, selectedTypes).map((trace) => `${trace.traceId}:${[...trace.activityTypes].sort().join("+")}:${trace.activityCount}`).sort();
|
|
861
|
+
return `${types.join(",")}|${rows.join(";")}`;
|
|
862
|
+
}
|
|
863
|
+
/** Collects the distinct dot-joined log categories, sorted. */
|
|
864
|
+
function distinctLogCategories(logs) {
|
|
865
|
+
const categories = /* @__PURE__ */ new Set();
|
|
866
|
+
for (const log of logs) categories.add(log.category.join("."));
|
|
867
|
+
return [...categories].sort();
|
|
868
|
+
}
|
|
869
|
+
/** Applies a {@link LogFilter} to a list of log records. */
|
|
870
|
+
function filterLogs(logs, filter) {
|
|
871
|
+
let filtered = logs;
|
|
872
|
+
if (filter.category) {
|
|
873
|
+
const category = filter.category;
|
|
874
|
+
filtered = filtered.filter((log) => log.category.join(".") === category);
|
|
875
|
+
}
|
|
876
|
+
if (filter.level) {
|
|
877
|
+
const level = filter.level;
|
|
878
|
+
filtered = filtered.filter((log) => log.level === level);
|
|
879
|
+
}
|
|
880
|
+
if (filter.q) {
|
|
881
|
+
const needle = filter.q.toLowerCase();
|
|
882
|
+
filtered = filtered.filter((log) => log.message.toLowerCase().includes(needle));
|
|
883
|
+
}
|
|
884
|
+
return filtered;
|
|
885
|
+
}
|
|
720
886
|
function createDebugApp(pathPrefix, exporter, logStore, auth) {
|
|
721
887
|
const app = new Hono({ strict: false }).basePath(pathPrefix);
|
|
722
888
|
let hmacKeyPromise;
|
|
@@ -781,30 +947,50 @@ function createDebugApp(pathPrefix, exporter, logStore, auth) {
|
|
|
781
947
|
}
|
|
782
948
|
app.get("/api/traces", async (c) => {
|
|
783
949
|
const traces = await exporter.getRecentTraces();
|
|
784
|
-
|
|
950
|
+
const types = c.req.queries("type") ?? [];
|
|
951
|
+
return c.json(filterTracesByTypes(traces, types));
|
|
785
952
|
});
|
|
786
953
|
app.get("/api/logs/:traceId", async (c) => {
|
|
787
954
|
const traceId = c.req.param("traceId");
|
|
788
955
|
await logStore.flush();
|
|
789
956
|
const logs = await logStore.get(traceId);
|
|
790
|
-
|
|
957
|
+
const logFilter = {
|
|
958
|
+
category: c.req.query("category"),
|
|
959
|
+
level: c.req.query("level"),
|
|
960
|
+
q: c.req.query("q")
|
|
961
|
+
};
|
|
962
|
+
return c.json(filterLogs(logs, logFilter));
|
|
791
963
|
});
|
|
792
964
|
app.get("/traces/:traceId", async (c) => {
|
|
793
965
|
const traceId = c.req.param("traceId");
|
|
794
966
|
await logStore.flush();
|
|
795
967
|
const activities = await exporter.getActivitiesByTraceId(traceId);
|
|
796
968
|
const logs = await logStore.get(traceId);
|
|
969
|
+
const logFilter = {
|
|
970
|
+
category: c.req.query("category"),
|
|
971
|
+
level: c.req.query("level"),
|
|
972
|
+
q: c.req.query("q")
|
|
973
|
+
};
|
|
797
974
|
return c.html(/* @__PURE__ */ jsx(TraceDetailPage, {
|
|
798
975
|
traceId,
|
|
799
976
|
activities,
|
|
800
|
-
logs,
|
|
977
|
+
logs: filterLogs(logs, logFilter),
|
|
978
|
+
totalLogCount: logs.length,
|
|
979
|
+
availableCategories: distinctLogCategories(logs),
|
|
980
|
+
selectedCategory: logFilter.category,
|
|
981
|
+
selectedLevel: logFilter.level,
|
|
982
|
+
selectedQuery: logFilter.q,
|
|
801
983
|
pathPrefix
|
|
802
984
|
}));
|
|
803
985
|
});
|
|
804
986
|
app.get("/", async (c) => {
|
|
805
987
|
const traces = await exporter.getRecentTraces();
|
|
988
|
+
const selectedTypes = c.req.queries("type") ?? [];
|
|
806
989
|
return c.html(/* @__PURE__ */ jsx(TracesListPage, {
|
|
807
|
-
traces,
|
|
990
|
+
traces: filterTracesByTypes(traces, selectedTypes),
|
|
991
|
+
availableTypes: distinctActivityTypes(traces),
|
|
992
|
+
selectedTypes,
|
|
993
|
+
initialSnapshot: snapshotOf(traces, selectedTypes),
|
|
808
994
|
pathPrefix
|
|
809
995
|
}));
|
|
810
996
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/debugger",
|
|
3
|
-
"version": "2.5.0-dev.
|
|
3
|
+
"version": "2.5.0-dev.2304+3a599380",
|
|
4
4
|
"description": "Embedded ActivityPub debug dashboard for Fedify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/mod.cjs",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"directory": "packages/debugger"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@fedify/fedify": "^2.5.0-dev.
|
|
32
|
+
"@fedify/fedify": "^2.5.0-dev.2304+3a599380"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@logtape/logtape": "^2.2.0",
|