Skip to content

Commit d0ceaa2

Browse files
authored
Merge pull request #241 from sanmipaul/feature/request-tracing
feat: implement comprehensive request logging and tracing system
2 parents 5e5c18c + 792f598 commit d0ceaa2

File tree

12 files changed

+308
-59
lines changed

12 files changed

+308
-59
lines changed

backend/.eslintrc.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
"eslint:recommended",
99
"plugin:@typescript-eslint/recommended"
1010
],
11-
"@typescript-eslint/no-explicit-any": "off",
12-
"@typescript-eslint/no-unused-vars": "off",
13-
"no-case-declarations": "off",
14-
"@typescript-eslint/ban-ts-comment": "off",
15-
"@typescript-eslint/no-var-requires": "off",
16-
"@typescript-eslint/no-duplicate-enum-values": "off"
17-
},
18-
"env": {
19-
"node": true,
20-
"es2022": true
11+
"env": {
12+
"node": true,
13+
"es2022": true
14+
},
15+
"rules": {
16+
"@typescript-eslint/no-explicit-any": "off",
17+
"@typescript-eslint/no-unused-vars": "off",
18+
"no-case-declarations": "off",
19+
"@typescript-eslint/ban-ts-comment": "off",
20+
"@typescript-eslint/no-var-requires": "off",
21+
"@typescript-eslint/no-duplicate-enum-values": "off"
22+
}
2123
}
22-
}

backend/src/api/routes/rateLimitAdmin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export async function rateLimitAdminRoutes(server: FastifyInstance) {
115115
await rateLimitService.resetRateLimit(identifier, type, endpoint);
116116

117117
logger.info(
118-
{ type, identifier, endpoint, adminKey: request.headers["x-api-key"] },
118+
{ type, identifier, endpoint },
119119
"Rate limit reset by admin"
120120
);
121121

@@ -173,7 +173,7 @@ export async function rateLimitAdminRoutes(server: FastifyInstance) {
173173
await rateLimitService.updateRateLimit(tier as any, newLimits);
174174

175175
logger.info(
176-
{ tier, newLimits, adminKey: request.headers["x-api-key"] },
176+
{ tier, newLimits },
177177
"Rate limits updated by admin"
178178
);
179179

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import { Job } from "bullmq";
22
import { PriceService } from "../services/price.service.js";
33
import { logger } from "../utils/logger.js";
4+
import { SUPPORTED_ASSETS } from "../config/index.js";
45

56
const priceService = new PriceService();
67

78
export async function processPriceCollection(job: Job) {
89
logger.info({ jobId: job.id }, "Starting price collection job");
9-
10-
const _assetsToFetch = SUPPORTED_ASSETS.filter(a => a.code !== "XLM"); // XLM is usually the base or handled differently if needed, but let's fetch all for completeness if they are in SUPPORTED_ASSETS.
11-
// Actually, the service handles USDC as 1.
12-
13-
for (const asset of testAssets) {
10+
11+
for (const asset of SUPPORTED_ASSETS) {
1412
try {
15-
await priceService.getAggregatedPrice(asset);
16-
logger.debug({ asset }, "Fetched aggregated price");
13+
await priceService.getAggregatedPrice(asset.code);
14+
logger.debug({ asset: asset.code }, "Fetched aggregated price");
1715
} catch (error) {
18-
logger.error({ asset, error }, "Failed to fetch aggregated price in background job");
19-
// Don't throw here to allow other assets to be fetched
16+
logger.error({ asset: asset.code, error }, "Failed to fetch aggregated price in background job");
2017
}
2118
}
2219
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
interface Alert {
2+
id: string;
3+
type: string;
4+
severity: "info" | "warning" | "critical";
5+
message: string;
6+
createdAt: string;
7+
}
8+
9+
interface AlertConfigSectionProps {
10+
alerts: Alert[] | null | undefined;
11+
isLoading: boolean;
12+
}
13+
14+
const SEVERITY_STYLES: Record<Alert["severity"], string> = {
15+
info: "bg-blue-500/20 text-blue-400",
16+
warning: "bg-yellow-500/20 text-yellow-400",
17+
critical: "bg-red-500/20 text-red-400",
18+
};
19+
20+
export default function AlertConfigSection({ alerts, isLoading }: AlertConfigSectionProps) {
21+
if (isLoading) {
22+
return (
23+
<div className="bg-stellar-card border border-stellar-border rounded-xl p-6">
24+
<h3 className="text-lg font-semibold text-white mb-4">Alerts</h3>
25+
<div className="space-y-3">
26+
{[1, 2, 3].map((i) => (
27+
<div key={i} className="h-12 bg-stellar-border rounded animate-pulse" />
28+
))}
29+
</div>
30+
</div>
31+
);
32+
}
33+
34+
return (
35+
<div className="bg-stellar-card border border-stellar-border rounded-xl p-6">
36+
<h3 className="text-lg font-semibold text-white mb-4">Alerts</h3>
37+
{alerts && alerts.length > 0 ? (
38+
<div className="space-y-3">
39+
{alerts.map((alert) => (
40+
<div
41+
key={alert.id}
42+
className="flex items-start gap-3 p-3 rounded-lg bg-stellar-dark border border-stellar-border"
43+
>
44+
<span
45+
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium shrink-0 ${SEVERITY_STYLES[alert.severity]}`}
46+
>
47+
{alert.severity}
48+
</span>
49+
<div className="flex-1 min-w-0">
50+
<p className="text-sm text-white">{alert.message}</p>
51+
<p className="text-xs text-stellar-text-secondary mt-1">
52+
{new Date(alert.createdAt).toLocaleString()}
53+
</p>
54+
</div>
55+
</div>
56+
))}
57+
</div>
58+
) : (
59+
<p className="text-stellar-text-secondary text-sm">No active alerts.</p>
60+
)}
61+
</div>
62+
);
63+
}

frontend/src/components/AssetHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export default function AssetHeader({
114114
<h1 className="text-2xl font-bold text-white">{symbol}</h1>
115115
{assetInfo && (
116116
<span className="px-2.5 py-0.5 rounded-full text-xs font-medium bg-stellar-border text-stellar-text-secondary">
117-
{TYPE_LABELS[assetInfo.type] || assetInfo.type}
117+
{(assetInfo.type && TYPE_LABELS[assetInfo.type]) || assetInfo.type}
118118
</span>
119119
)}
120120
{status && (

frontend/src/components/PriceChart.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,63 @@ import {
1818
usePriceComparison,
1919
} from "../hooks/usePriceComparison";
2020
import { SkeletonChart } from "./Skeleton";
21+
import type { PriceTimeframe } from "../types";
22+
23+
// --- local helpers ---
24+
25+
const ALL_SOURCES: PriceSourceId[] = ["stellar_dex", "circle", "coinbase", "stellar_amm"];
26+
27+
function msForRange(rangeId: string, customStartIso: string, customEndIso: string): number {
28+
if (rangeId === "custom" && customStartIso && customEndIso) {
29+
return Math.max(0, Date.parse(customEndIso) - Date.parse(customStartIso));
30+
}
31+
const RANGE_MS: Record<string, number> = {
32+
"1h": 60 * 60 * 1_000,
33+
"24h": 24 * 60 * 60 * 1_000,
34+
"7d": 7 * 24 * 60 * 60 * 1_000,
35+
"30d": 30 * 24 * 60 * 60 * 1_000,
36+
};
37+
return RANGE_MS[rangeId] ?? RANGE_MS["24h"];
38+
}
39+
40+
function stellarVarRgb(varName: string, fallbackRgb: string): string {
41+
if (typeof document === "undefined") return fallbackRgb;
42+
const val = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
43+
return val || fallbackRgb;
44+
}
45+
46+
function formatPrice(value: number | null | undefined): string {
47+
if (value == null || !Number.isFinite(value)) return "—";
48+
return `$${value.toFixed(4)}`;
49+
}
50+
51+
function formatPct(value: number | null | undefined): string {
52+
if (value == null || !Number.isFinite(value)) return "—";
53+
return `${(value * 100).toFixed(2)}%`;
54+
}
55+
56+
function tooltipLabelFromIso(iso: string | undefined): string {
57+
if (!iso) return "";
58+
const d = new Date(iso);
59+
return isNaN(d.getTime()) ? iso : d.toLocaleString();
60+
}
61+
62+
// --- end helpers ---
2163

2264
interface PriceChartProps {
2365
symbol: string;
2466
}
2567

68+
interface PriceDataPoint {
69+
source: string;
70+
price: number;
71+
timestamp: string;
72+
}
73+
2674
interface EnhancedPriceChartProps extends PriceChartProps {
75+
data: PriceDataPoint[];
76+
isLoading: boolean;
77+
sources?: PriceDataPoint[];
2778
timeframe: PriceTimeframe;
2879
onTimeframeChange: (tf: PriceTimeframe) => void;
2980
}
@@ -60,11 +111,13 @@ function getTimeframeTickFormatter(timeframe: PriceTimeframe) {
60111
month: "short",
61112
day: "numeric",
62113
});
114+
default:
115+
return val;
63116
}
64117
};
65118
}
66119

67-
export default function PriceChart({ symbol, data, isLoading }: PriceChartProps) {
120+
export default function PriceChart({ symbol }: PriceChartProps) {
68121
const titleId = `price-chart-title-${symbol}`;
69122
const descId = `price-chart-desc-${symbol}`;
70123
const containerRef = useRef<HTMLDivElement | null>(null);

frontend/src/components/PriceSourceTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ function getStatusStyle(status: PriceSource["status"]): string {
1414
return "bg-yellow-500/20 text-yellow-400";
1515
case "offline":
1616
return "bg-red-500/20 text-red-400";
17+
default:
18+
return "bg-stellar-border text-stellar-text-secondary";
1719
}
1820
}
1921

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
interface VolumeData {
2+
volume24h?: number;
3+
volume7d?: number;
4+
volume30d?: number;
5+
}
6+
7+
interface VolumeAnalyticsProps {
8+
data: VolumeData | null | undefined;
9+
isLoading: boolean;
10+
}
11+
12+
export default function VolumeAnalytics({ data, isLoading }: VolumeAnalyticsProps) {
13+
if (isLoading) {
14+
return (
15+
<div className="bg-stellar-card border border-stellar-border rounded-xl p-6">
16+
<h3 className="text-lg font-semibold text-white mb-4">Volume Analytics</h3>
17+
<div className="space-y-3">
18+
{[1, 2, 3].map((i) => (
19+
<div key={i} className="h-10 bg-stellar-border rounded animate-pulse" />
20+
))}
21+
</div>
22+
</div>
23+
);
24+
}
25+
26+
return (
27+
<div className="bg-stellar-card border border-stellar-border rounded-xl p-6">
28+
<h3 className="text-lg font-semibold text-white mb-4">Volume Analytics</h3>
29+
{data ? (
30+
<div className="grid grid-cols-3 gap-4">
31+
{data.volume24h !== undefined && (
32+
<div>
33+
<p className="text-xs text-stellar-text-secondary">24H Volume</p>
34+
<p className="text-lg font-semibold text-white">${data.volume24h.toLocaleString()}</p>
35+
</div>
36+
)}
37+
{data.volume7d !== undefined && (
38+
<div>
39+
<p className="text-xs text-stellar-text-secondary">7D Volume</p>
40+
<p className="text-lg font-semibold text-white">${data.volume7d.toLocaleString()}</p>
41+
</div>
42+
)}
43+
{data.volume30d !== undefined && (
44+
<div>
45+
<p className="text-xs text-stellar-text-secondary">30D Volume</p>
46+
<p className="text-lg font-semibold text-white">${data.volume30d.toLocaleString()}</p>
47+
</div>
48+
)}
49+
</div>
50+
) : (
51+
<p className="text-stellar-text-secondary text-sm">No volume data available.</p>
52+
)}
53+
</div>
54+
);
55+
}

frontend/src/hooks/useAssetDetail.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
getAssetAlerts,
1313
} from "../services/api";
1414
import { useWebSocket } from "./useWebSocket";
15-
import type { PriceTimeframe, HealthScore } from "../types";
15+
import type { HealthScore, PriceTimeframe } from "../types";
1616

1717
export function useAssetDetail(symbol: string) {
1818
const [timeframe, setTimeframe] = useState<PriceTimeframe>("24H");

0 commit comments

Comments
 (0)