@spfn/monitor 0.1.0-beta.2 → 0.1.0-beta.21
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/LICENSE +1 -1
- package/README.md +348 -126
- package/dist/config/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/nextjs/client.js.map +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +39 -29
- package/dist/server.js.map +1 -1
- package/migrations/0000_slow_mandrill.sql +20 -15
- package/migrations/0001_bumpy_dark_beast.sql +5 -0
- package/migrations/meta/0001_snapshot.json +448 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +10 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nextjs/components/monitor-dashboard.tsx","../../src/nextjs/components/stats-overview.tsx","../../src/nextjs/components/error-list-view.tsx","../../src/nextjs/components/error-detail-view.tsx","../../src/nextjs/components/log-viewer.tsx"],"sourcesContent":["/**\n * @spfn/monitor - Monitor Dashboard Component\n *\n * Main entry point combining StatsOverview, ErrorListView, and LogViewer in tabs\n */\n\nimport { useState } from 'react';\nimport { StatsOverview } from './stats-overview';\nimport { ErrorListView } from './error-list-view';\nimport { ErrorDetailView } from './error-detail-view';\nimport { LogViewer } from './log-viewer';\n\ntype Tab = 'errors' | 'logs';\n\nexport function MonitorDashboard()\n{\n const [tab, setTab] = useState<Tab>('errors');\n const [selectedErrorId, setSelectedErrorId] = useState<number | null>(null);\n\n return (\n <div className=\"space-y-6\">\n {/* Stats */}\n <StatsOverview />\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b border-neutral-200 dark:border-neutral-800\">\n <TabButton active={tab === 'errors'} onClick={() => { setTab('errors'); setSelectedErrorId(null); }}>\n Errors\n </TabButton>\n <TabButton active={tab === 'logs'} onClick={() => setTab('logs')}>\n Logs\n </TabButton>\n </div>\n\n {/* Content */}\n {tab === 'errors' && !selectedErrorId && (\n <ErrorListView onSelect={setSelectedErrorId} />\n )}\n {tab === 'errors' && selectedErrorId && (\n <ErrorDetailView\n errorId={selectedErrorId}\n onBack={() => setSelectedErrorId(null)}\n />\n )}\n {tab === 'logs' && (\n <LogViewer />\n )}\n </div>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n children,\n}: {\n active: boolean;\n onClick: () => void;\n children: React.ReactNode;\n})\n{\n return (\n <button\n onClick={onClick}\n className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${\n active\n ? 'border-neutral-900 dark:border-neutral-100 text-neutral-900 dark:text-neutral-100'\n : 'border-transparent text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300'\n }`}\n >\n {children}\n </button>\n );\n}\n","/**\n * @spfn/monitor - Stats Overview Component\n *\n * Displays error/log counts and trend indicators\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { MonitorStats } from '@spfn/monitor';\n\nexport function StatsOverview()\n{\n const [stats, setStats] = useState<MonitorStats | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n const fetchStats = useCallback(async () =>\n {\n try\n {\n const data = await monitorApi.getStats.call({});\n setStats(data as MonitorStats);\n }\n finally\n {\n setIsLoading(false);\n }\n }, []);\n\n useEffect(() =>\n {\n fetchStats();\n const interval = setInterval(fetchStats, 30_000);\n return () => clearInterval(interval);\n }, [fetchStats]);\n\n if (isLoading || !stats)\n {\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 animate-pulse\">\n <div className=\"h-4 w-20 bg-neutral-200 dark:bg-neutral-700 rounded mb-2\" />\n <div className=\"h-8 w-12 bg-neutral-200 dark:bg-neutral-700 rounded\" />\n </div>\n ))}\n </div>\n );\n }\n\n const cards = [\n { label: 'Active Errors', value: stats.errors.active, color: 'text-red-600 dark:text-red-400' },\n { label: 'Resolved', value: stats.errors.resolved, color: 'text-green-600 dark:text-green-400' },\n { label: 'Ignored', value: stats.errors.ignored, color: 'text-neutral-500 dark:text-neutral-400' },\n { label: 'Errors (24h)', value: stats.trends.errorsLast24h, color: 'text-orange-600 dark:text-orange-400' },\n ];\n\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {cards.map((card) => (\n <div\n key={card.label}\n className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4\"\n >\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400\">{card.label}</p>\n <p className={`text-2xl font-semibold mt-1 ${card.color}`}>{card.value}</p>\n </div>\n ))}\n </div>\n );\n}\n","/**\n * @spfn/monitor - Error List View Component\n *\n * Displays error groups in a filterable table with status badges\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorListViewProps\n{\n onSelect?: (id: number) => void;\n}\n\nconst STATUS_BADGE: Record<ErrorGroupStatus, string> = {\n active: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n resolved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',\n ignored: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n};\n\nexport function ErrorListView({ onSelect }: ErrorListViewProps)\n{\n const [status, setStatus] = useState<string>('');\n const [search, setSearch] = useState('');\n const [errors, setErrors] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listErrors.call({\n query: {\n ...(status ? { status } : {}),\n ...(search ? { search } : {}),\n limit: 50,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setErrors(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => { cancelled = true; };\n }, [status, search]);\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"active\">Active</option>\n <option value=\"resolved\">Resolved</option>\n <option value=\"ignored\">Ignored</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search errors...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Table */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : errors.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No errors found</div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-neutral-200 dark:border-neutral-800 text-left text-neutral-500\">\n <th className=\"py-2 pr-4\">Status</th>\n <th className=\"py-2 pr-4\">Error</th>\n <th className=\"py-2 pr-4\">Path</th>\n <th className=\"py-2 pr-4 text-right\">Count</th>\n <th className=\"py-2 text-right\">Last Seen</th>\n </tr>\n </thead>\n <tbody>\n {errors.map((group: any) => (\n <tr\n key={group.id}\n onClick={() => onSelect?.(group.id)}\n className=\"border-b border-neutral-100 dark:border-neutral-800/50 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer\"\n >\n <td className=\"py-2 pr-4\">\n <span className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${STATUS_BADGE[group.status as ErrorGroupStatus]}`}>\n {group.status}\n </span>\n </td>\n <td className=\"py-2 pr-4\">\n <div className=\"font-medium text-neutral-900 dark:text-neutral-100\">{group.name}</div>\n <div className=\"text-neutral-500 truncate max-w-xs\">{group.message}</div>\n </td>\n <td className=\"py-2 pr-4 font-mono text-xs text-neutral-600 dark:text-neutral-400\">\n {group.method} {group.path}\n </td>\n <td className=\"py-2 pr-4 text-right font-mono\">{group.count}</td>\n <td className=\"py-2 text-right text-neutral-500\">\n {formatRelativeTime(group.lastSeenAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n}\n\nfunction formatRelativeTime(date: string | Date): string\n{\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = Date.now();\n const diff = now - d.getTime();\n const mins = Math.floor(diff / 60_000);\n\n if (mins < 1)\n {\n return 'just now';\n }\n\n if (mins < 60)\n {\n return `${mins}m ago`;\n }\n\n const hours = Math.floor(mins / 60);\n if (hours < 24)\n {\n return `${hours}h ago`;\n }\n\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n","/**\n * @spfn/monitor - Error Detail View Component\n *\n * Shows error group details with event timeline and status change buttons\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorDetailViewProps\n{\n errorId: number;\n onBack?: () => void;\n}\n\nconst STATUS_ACTIONS: Record<ErrorGroupStatus, { label: string; target: ErrorGroupStatus }[]> = {\n active: [\n { label: 'Resolve', target: 'resolved' },\n { label: 'Ignore', target: 'ignored' },\n ],\n resolved: [\n { label: 'Reopen', target: 'active' },\n ],\n ignored: [\n { label: 'Reopen', target: 'active' },\n { label: 'Resolve', target: 'resolved' },\n ],\n};\n\nexport function ErrorDetailView({ errorId, onBack }: ErrorDetailViewProps)\n{\n const [data, setData] = useState<any>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [isMutating, setIsMutating] = useState(false);\n\n const fetchDetail = useCallback(async () =>\n {\n setIsLoading(true);\n try\n {\n const result = await monitorApi.getErrorDetail.call({ params: { id: errorId } });\n setData(result);\n }\n finally\n {\n setIsLoading(false);\n }\n }, [errorId]);\n\n useEffect(() =>\n {\n fetchDetail();\n }, [fetchDetail]);\n\n async function handleStatusChange(status: string)\n {\n setIsMutating(true);\n try\n {\n await monitorApi.updateErrorStatus.call({\n params: { id: errorId },\n body: { status },\n });\n await fetchDetail();\n }\n finally\n {\n setIsMutating(false);\n }\n }\n\n if (isLoading || !data)\n {\n return <div className=\"text-sm text-neutral-500\">Loading...</div>;\n }\n\n const { group, events } = data;\n const actions = STATUS_ACTIONS[group.status as ErrorGroupStatus] ?? [];\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n {onBack && (\n <button\n onClick={onBack}\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300\"\n >\n ← Back\n </button>\n )}\n <h2 className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-100\">\n {group.name} — {group.statusCode}\n </h2>\n </div>\n\n {/* Error Info */}\n <div className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 space-y-3\">\n <p className=\"text-neutral-700 dark:text-neutral-300\">{group.message}</p>\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 text-sm\">\n <div>\n <span className=\"text-neutral-500\">Method</span>\n <p className=\"font-mono\">{group.method}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Path</span>\n <p className=\"font-mono\">{group.path}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Count</span>\n <p className=\"font-mono\">{group.count}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Status</span>\n <p className=\"font-medium\">{group.status}</p>\n </div>\n </div>\n\n {/* Status Actions */}\n {actions.length > 0 && (\n <div className=\"flex gap-2 pt-2\">\n {actions.map((action) => (\n <button\n key={action.target}\n onClick={() => handleStatusChange(action.target)}\n disabled={isMutating}\n className=\"px-3 py-1.5 text-sm rounded border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 disabled:opacity-50\"\n >\n {action.label}\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/* Event Timeline */}\n <div>\n <h3 className=\"text-sm font-medium text-neutral-500 mb-3\">Recent Events</h3>\n <div className=\"space-y-2\">\n {(events as any[]).map((event: any) => (\n <div\n key={event.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 p-3 text-sm\"\n >\n <div className=\"flex justify-between items-start\">\n <div className=\"space-y-1\">\n <div className=\"flex gap-3 text-neutral-500\">\n <span>User: {event.userId ?? '(anonymous)'}</span>\n <span>Request: {event.requestId ?? '(none)'}</span>\n </div>\n {event.stackTrace && (\n <pre className=\"text-xs text-neutral-600 dark:text-neutral-400 overflow-x-auto whitespace-pre-wrap mt-2 bg-neutral-50 dark:bg-neutral-900 p-2 rounded\">\n {event.stackTrace.split('\\n').slice(0, 5).join('\\n')}\n </pre>\n )}\n </div>\n <span className=\"text-xs text-neutral-400 whitespace-nowrap ml-4\">\n {new Date(event.createdAt).toLocaleString()}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * @spfn/monitor - Log Viewer Component\n *\n * Searchable, filterable log list with expandable metadata\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\n\nconst LEVEL_BADGE: Record<string, string> = {\n debug: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n info: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',\n warn: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',\n error: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n fatal: 'bg-red-200 text-red-800 dark:bg-red-900/50 dark:text-red-300',\n};\n\nexport function LogViewer()\n{\n const [level, setLevel] = useState('');\n const [search, setSearch] = useState('');\n const [logs, setLogs] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [expanded, setExpanded] = useState<Set<number>>(new Set());\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listLogs.call({\n query: {\n ...(level ? { level } : {}),\n ...(search ? { search } : {}),\n limit: 100,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setLogs(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => { cancelled = true; };\n }, [level, search]);\n\n function toggleExpand(id: number)\n {\n setExpanded((prev) =>\n {\n const next = new Set(prev);\n if (next.has(id))\n {\n next.delete(id);\n }\n else\n {\n next.add(id);\n }\n\n return next;\n });\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={level}\n onChange={(e) => setLevel(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All levels</option>\n <option value=\"debug\">Debug</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warn</option>\n <option value=\"error\">Error</option>\n <option value=\"fatal\">Fatal</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search logs...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Log List */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : logs.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No logs found</div>\n ) : (\n <div className=\"space-y-1\">\n {logs.map((log: any) => (\n <div\n key={log.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 text-sm\"\n >\n <div\n onClick={() => log.metadata && toggleExpand(log.id)}\n className={`flex items-start gap-3 p-2 ${log.metadata ? 'cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800/50' : ''}`}\n >\n <span className={`inline-block px-1.5 py-0.5 rounded text-xs font-medium ${LEVEL_BADGE[log.level] ?? ''}`}>\n {log.level}\n </span>\n <span className=\"flex-1 text-neutral-800 dark:text-neutral-200\">\n {log.message}\n </span>\n {log.source && (\n <span className=\"text-xs text-neutral-400 font-mono\">{log.source}</span>\n )}\n <span className=\"text-xs text-neutral-400 whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleTimeString()}\n </span>\n </div>\n {expanded.has(log.id) && log.metadata && (\n <pre className=\"px-3 py-2 text-xs bg-neutral-50 dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-800 overflow-x-auto\">\n {JSON.stringify(log.metadata, null, 2)}\n </pre>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useState","useEffect","monitorApi","jsx","jsxs","useState","useEffect","useCallback","monitorApi","jsx","jsxs","useState","useEffect","monitorApi","jsx","jsxs","jsx","jsxs","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/components/monitor-dashboard.tsx","../../src/nextjs/components/stats-overview.tsx","../../src/nextjs/components/error-list-view.tsx","../../src/nextjs/components/error-detail-view.tsx","../../src/nextjs/components/log-viewer.tsx"],"sourcesContent":["/**\n * @spfn/monitor - Monitor Dashboard Component\n *\n * Main entry point combining StatsOverview, ErrorListView, and LogViewer in tabs\n */\n\nimport { useState } from 'react';\nimport { StatsOverview } from './stats-overview';\nimport { ErrorListView } from './error-list-view';\nimport { ErrorDetailView } from './error-detail-view';\nimport { LogViewer } from './log-viewer';\n\ntype Tab = 'errors' | 'logs';\n\nexport function MonitorDashboard()\n{\n const [tab, setTab] = useState<Tab>('errors');\n const [selectedErrorId, setSelectedErrorId] = useState<number | null>(null);\n\n return (\n <div className=\"space-y-6\">\n {/* Stats */}\n <StatsOverview />\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b border-neutral-200 dark:border-neutral-800\">\n <TabButton active={tab === 'errors'} onClick={() => \n {\n setTab('errors'); setSelectedErrorId(null); \n }}>\n Errors\n </TabButton>\n <TabButton active={tab === 'logs'} onClick={() => setTab('logs')}>\n Logs\n </TabButton>\n </div>\n\n {/* Content */}\n {tab === 'errors' && !selectedErrorId && (\n <ErrorListView onSelect={setSelectedErrorId} />\n )}\n {tab === 'errors' && selectedErrorId && (\n <ErrorDetailView\n errorId={selectedErrorId}\n onBack={() => setSelectedErrorId(null)}\n />\n )}\n {tab === 'logs' && (\n <LogViewer />\n )}\n </div>\n );\n}\n\nfunction TabButton({\n active,\n onClick,\n children,\n}: {\n active: boolean;\n onClick: () => void;\n children: React.ReactNode;\n})\n{\n return (\n <button\n onClick={onClick}\n className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${\n active\n ? 'border-neutral-900 dark:border-neutral-100 text-neutral-900 dark:text-neutral-100'\n : 'border-transparent text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300'\n }`}\n >\n {children}\n </button>\n );\n}\n","/**\n * @spfn/monitor - Stats Overview Component\n *\n * Displays error/log counts and trend indicators\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { MonitorStats } from '@spfn/monitor';\n\nexport function StatsOverview()\n{\n const [stats, setStats] = useState<MonitorStats | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n\n const fetchStats = useCallback(async () =>\n {\n try\n {\n const data = await monitorApi.getStats.call({});\n setStats(data as MonitorStats);\n }\n finally\n {\n setIsLoading(false);\n }\n }, []);\n\n useEffect(() =>\n {\n fetchStats();\n const interval = setInterval(fetchStats, 30_000);\n\n return () => clearInterval(interval);\n }, [fetchStats]);\n\n if (isLoading || !stats)\n {\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 animate-pulse\">\n <div className=\"h-4 w-20 bg-neutral-200 dark:bg-neutral-700 rounded mb-2\" />\n <div className=\"h-8 w-12 bg-neutral-200 dark:bg-neutral-700 rounded\" />\n </div>\n ))}\n </div>\n );\n }\n\n const cards = [\n { label: 'Active Errors', value: stats.errors.active, color: 'text-red-600 dark:text-red-400' },\n { label: 'Resolved', value: stats.errors.resolved, color: 'text-green-600 dark:text-green-400' },\n { label: 'Ignored', value: stats.errors.ignored, color: 'text-neutral-500 dark:text-neutral-400' },\n { label: 'Errors (24h)', value: stats.trends.errorsLast24h, color: 'text-orange-600 dark:text-orange-400' },\n ];\n\n return (\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {cards.map((card) => (\n <div\n key={card.label}\n className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4\"\n >\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400\">{card.label}</p>\n <p className={`text-2xl font-semibold mt-1 ${card.color}`}>{card.value}</p>\n </div>\n ))}\n </div>\n );\n}\n","/**\n * @spfn/monitor - Error List View Component\n *\n * Displays error groups in a filterable table with status badges\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorListViewProps\n{\n onSelect?: (id: number) => void;\n}\n\nconst STATUS_BADGE: Record<ErrorGroupStatus, string> = {\n active: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n resolved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',\n ignored: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n};\n\nexport function ErrorListView({ onSelect }: ErrorListViewProps)\n{\n const [status, setStatus] = useState<string>('');\n const [search, setSearch] = useState('');\n const [errors, setErrors] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listErrors.call({\n query: {\n ...(status ? { status } : {}),\n ...(search ? { search } : {}),\n limit: 50,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setErrors(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => \n {\n cancelled = true; \n };\n }, [status, search]);\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All statuses</option>\n <option value=\"active\">Active</option>\n <option value=\"resolved\">Resolved</option>\n <option value=\"ignored\">Ignored</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search errors...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Table */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : errors.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No errors found</div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-neutral-200 dark:border-neutral-800 text-left text-neutral-500\">\n <th className=\"py-2 pr-4\">Status</th>\n <th className=\"py-2 pr-4\">Error</th>\n <th className=\"py-2 pr-4\">Path</th>\n <th className=\"py-2 pr-4 text-right\">Count</th>\n <th className=\"py-2 text-right\">Last Seen</th>\n </tr>\n </thead>\n <tbody>\n {errors.map((group: any) => (\n <tr\n key={group.id}\n onClick={() => onSelect?.(group.id)}\n className=\"border-b border-neutral-100 dark:border-neutral-800/50 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer\"\n >\n <td className=\"py-2 pr-4\">\n <span className={`inline-block px-2 py-0.5 rounded text-xs font-medium ${STATUS_BADGE[group.status as ErrorGroupStatus]}`}>\n {group.status}\n </span>\n </td>\n <td className=\"py-2 pr-4\">\n <div className=\"font-medium text-neutral-900 dark:text-neutral-100\">{group.name}</div>\n <div className=\"text-neutral-500 truncate max-w-xs\">{group.message}</div>\n </td>\n <td className=\"py-2 pr-4 font-mono text-xs text-neutral-600 dark:text-neutral-400\">\n {group.method} {group.path}\n </td>\n <td className=\"py-2 pr-4 text-right font-mono\">{group.count}</td>\n <td className=\"py-2 text-right text-neutral-500\">\n {formatRelativeTime(group.lastSeenAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n );\n}\n\nfunction formatRelativeTime(date: string | Date): string\n{\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = Date.now();\n const diff = now - d.getTime();\n const mins = Math.floor(diff / 60_000);\n\n if (mins < 1)\n {\n return 'just now';\n }\n\n if (mins < 60)\n {\n return `${mins}m ago`;\n }\n\n const hours = Math.floor(mins / 60);\n if (hours < 24)\n {\n return `${hours}h ago`;\n }\n\n const days = Math.floor(hours / 24);\n\n return `${days}d ago`;\n}\n","/**\n * @spfn/monitor - Error Detail View Component\n *\n * Shows error group details with event timeline and status change buttons\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { monitorApi } from '@spfn/monitor';\nimport type { ErrorGroupStatus } from '@spfn/monitor';\n\ninterface ErrorDetailViewProps\n{\n errorId: number;\n onBack?: () => void;\n}\n\nconst STATUS_ACTIONS: Record<ErrorGroupStatus, { label: string; target: ErrorGroupStatus }[]> = {\n active: [\n { label: 'Resolve', target: 'resolved' },\n { label: 'Ignore', target: 'ignored' },\n ],\n resolved: [\n { label: 'Reopen', target: 'active' },\n ],\n ignored: [\n { label: 'Reopen', target: 'active' },\n { label: 'Resolve', target: 'resolved' },\n ],\n};\n\nexport function ErrorDetailView({ errorId, onBack }: ErrorDetailViewProps)\n{\n const [data, setData] = useState<any>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [isMutating, setIsMutating] = useState(false);\n\n const fetchDetail = useCallback(async () =>\n {\n setIsLoading(true);\n try\n {\n const result = await monitorApi.getErrorDetail.call({ params: { id: errorId } });\n setData(result);\n }\n finally\n {\n setIsLoading(false);\n }\n }, [errorId]);\n\n useEffect(() =>\n {\n fetchDetail();\n }, [fetchDetail]);\n\n async function handleStatusChange(status: string)\n {\n setIsMutating(true);\n try\n {\n await monitorApi.updateErrorStatus.call({\n params: { id: errorId },\n body: { status },\n });\n await fetchDetail();\n }\n finally\n {\n setIsMutating(false);\n }\n }\n\n if (isLoading || !data)\n {\n return <div className=\"text-sm text-neutral-500\">Loading...</div>;\n }\n\n const { group, events } = data;\n const actions = STATUS_ACTIONS[group.status as ErrorGroupStatus] ?? [];\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n {onBack && (\n <button\n onClick={onBack}\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300\"\n >\n ← Back\n </button>\n )}\n <h2 className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-100\">\n {group.name} — {group.statusCode}\n </h2>\n </div>\n\n {/* Error Info */}\n <div className=\"rounded-lg border border-neutral-200 dark:border-neutral-800 p-4 space-y-3\">\n <p className=\"text-neutral-700 dark:text-neutral-300\">{group.message}</p>\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 text-sm\">\n <div>\n <span className=\"text-neutral-500\">Method</span>\n <p className=\"font-mono\">{group.method}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Path</span>\n <p className=\"font-mono\">{group.path}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Count</span>\n <p className=\"font-mono\">{group.count}</p>\n </div>\n <div>\n <span className=\"text-neutral-500\">Status</span>\n <p className=\"font-medium\">{group.status}</p>\n </div>\n </div>\n\n {/* Status Actions */}\n {actions.length > 0 && (\n <div className=\"flex gap-2 pt-2\">\n {actions.map((action) => (\n <button\n key={action.target}\n onClick={() => handleStatusChange(action.target)}\n disabled={isMutating}\n className=\"px-3 py-1.5 text-sm rounded border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 disabled:opacity-50\"\n >\n {action.label}\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/* Event Timeline */}\n <div>\n <h3 className=\"text-sm font-medium text-neutral-500 mb-3\">Recent Events</h3>\n <div className=\"space-y-2\">\n {(events as any[]).map((event: any) => (\n <div\n key={event.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 p-3 text-sm\"\n >\n <div className=\"flex justify-between items-start\">\n <div className=\"space-y-1\">\n <div className=\"flex gap-3 text-neutral-500\">\n <span>User: {event.userId ?? '(anonymous)'}</span>\n <span>Request: {event.requestId ?? '(none)'}</span>\n </div>\n {event.stackTrace && (\n <pre className=\"text-xs text-neutral-600 dark:text-neutral-400 overflow-x-auto whitespace-pre-wrap mt-2 bg-neutral-50 dark:bg-neutral-900 p-2 rounded\">\n {event.stackTrace.split('\\n').slice(0, 5).join('\\n')}\n </pre>\n )}\n </div>\n <span className=\"text-xs text-neutral-400 whitespace-nowrap ml-4\">\n {new Date(event.createdAt).toLocaleString()}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * @spfn/monitor - Log Viewer Component\n *\n * Searchable, filterable log list with expandable metadata\n */\n\nimport { useState, useEffect } from 'react';\nimport { monitorApi } from '@spfn/monitor';\n\nconst LEVEL_BADGE: Record<string, string> = {\n debug: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n info: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',\n warn: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',\n error: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n fatal: 'bg-red-200 text-red-800 dark:bg-red-900/50 dark:text-red-300',\n};\n\nexport function LogViewer()\n{\n const [level, setLevel] = useState('');\n const [search, setSearch] = useState('');\n const [logs, setLogs] = useState<any[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [expanded, setExpanded] = useState<Set<number>>(new Set());\n\n useEffect(() =>\n {\n let cancelled = false;\n setIsLoading(true);\n\n monitorApi.listLogs.call({\n query: {\n ...(level ? { level } : {}),\n ...(search ? { search } : {}),\n limit: 100,\n },\n }).then((data) =>\n {\n if (!cancelled)\n {\n setLogs(data as any[]);\n setIsLoading(false);\n }\n }).catch(() =>\n {\n if (!cancelled)\n {\n setIsLoading(false);\n }\n });\n\n return () => \n {\n cancelled = true; \n };\n }, [level, search]);\n\n function toggleExpand(id: number)\n {\n setExpanded((prev) =>\n {\n const next = new Set(prev);\n if (next.has(id))\n {\n next.delete(id);\n }\n else\n {\n next.add(id);\n }\n\n return next;\n });\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex gap-3\">\n <select\n value={level}\n onChange={(e) => setLevel(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm\"\n >\n <option value=\"\">All levels</option>\n <option value=\"debug\">Debug</option>\n <option value=\"info\">Info</option>\n <option value=\"warn\">Warn</option>\n <option value=\"error\">Error</option>\n <option value=\"fatal\">Fatal</option>\n </select>\n <input\n type=\"text\"\n placeholder=\"Search logs...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"rounded border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-1.5 text-sm flex-1\"\n />\n </div>\n\n {/* Log List */}\n {isLoading ? (\n <div className=\"text-sm text-neutral-500\">Loading...</div>\n ) : logs.length === 0 ? (\n <div className=\"text-sm text-neutral-500 py-8 text-center\">No logs found</div>\n ) : (\n <div className=\"space-y-1\">\n {logs.map((log: any) => (\n <div\n key={log.id}\n className=\"rounded border border-neutral-200 dark:border-neutral-800 text-sm\"\n >\n <div\n onClick={() => log.metadata && toggleExpand(log.id)}\n className={`flex items-start gap-3 p-2 ${log.metadata ? 'cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800/50' : ''}`}\n >\n <span className={`inline-block px-1.5 py-0.5 rounded text-xs font-medium ${LEVEL_BADGE[log.level] ?? ''}`}>\n {log.level}\n </span>\n <span className=\"flex-1 text-neutral-800 dark:text-neutral-200\">\n {log.message}\n </span>\n {log.source && (\n <span className=\"text-xs text-neutral-400 font-mono\">{log.source}</span>\n )}\n <span className=\"text-xs text-neutral-400 whitespace-nowrap\">\n {new Date(log.createdAt).toLocaleTimeString()}\n </span>\n </div>\n {expanded.has(log.id) && log.metadata && (\n <pre className=\"px-3 py-2 text-xs bg-neutral-50 dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-800 overflow-x-auto\">\n {JSON.stringify(log.metadata, null, 2)}\n </pre>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useState","useEffect","monitorApi","jsx","jsxs","useState","useEffect","useCallback","monitorApi","jsx","jsxs","useState","useEffect","monitorApi","jsx","jsxs","jsx","jsxs","useState"]}
|
package/dist/server.d.ts
CHANGED
|
@@ -85,11 +85,11 @@ declare const errorEvents: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
85
85
|
name: `${string}_id`;
|
|
86
86
|
tableName: "error_events";
|
|
87
87
|
dataType: "number";
|
|
88
|
-
columnType: "
|
|
88
|
+
columnType: "PgBigInt53";
|
|
89
89
|
data: number;
|
|
90
|
-
driverParam: number;
|
|
90
|
+
driverParam: string | number;
|
|
91
91
|
notNull: true;
|
|
92
|
-
hasDefault:
|
|
92
|
+
hasDefault: false;
|
|
93
93
|
isPrimaryKey: false;
|
|
94
94
|
isAutoincrement: false;
|
|
95
95
|
hasRuntimeDefault: false;
|
package/dist/server.js
CHANGED
|
@@ -963,14 +963,14 @@ function Number2(options) {
|
|
|
963
963
|
}
|
|
964
964
|
|
|
965
965
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/string/string.mjs
|
|
966
|
-
function
|
|
966
|
+
function String2(options) {
|
|
967
967
|
return CreateType({ [Kind]: "String", type: "string" }, options);
|
|
968
968
|
}
|
|
969
969
|
|
|
970
970
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/template-literal/syntax.mjs
|
|
971
971
|
function* FromUnion(syntax) {
|
|
972
972
|
const trim = syntax.trim().replace(/"|'/g, "");
|
|
973
|
-
return trim === "boolean" ? yield Boolean() : trim === "number" ? yield Number2() : trim === "bigint" ? yield BigInt() : trim === "string" ? yield
|
|
973
|
+
return trim === "boolean" ? yield Boolean() : trim === "number" ? yield Number2() : trim === "bigint" ? yield BigInt() : trim === "string" ? yield String2() : yield (() => {
|
|
974
974
|
const literals = trim.split("|").map((literal) => Literal(literal.trim()));
|
|
975
975
|
return literals.length === 0 ? Never() : literals.length === 1 ? literals[0] : UnionEvaluated(literals);
|
|
976
976
|
})();
|
|
@@ -1687,7 +1687,7 @@ function FromPromise2(left, right) {
|
|
|
1687
1687
|
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) && IsObjectPromiseLike(right) ? ExtendsResult.True : !type_exports.IsPromise(right) ? ExtendsResult.False : IntoBooleanResult(Visit3(left.item, right.item));
|
|
1688
1688
|
}
|
|
1689
1689
|
function RecordKey(schema) {
|
|
1690
|
-
return PatternNumberExact in schema.patternProperties ? Number2() : PatternStringExact in schema.patternProperties ?
|
|
1690
|
+
return PatternNumberExact in schema.patternProperties ? Number2() : PatternStringExact in schema.patternProperties ? String2() : Throw("Unknown record key pattern");
|
|
1691
1691
|
}
|
|
1692
1692
|
function RecordValue(schema) {
|
|
1693
1693
|
return PatternNumberExact in schema.patternProperties ? schema.patternProperties[PatternNumberExact] : PatternStringExact in schema.patternProperties ? schema.patternProperties[PatternStringExact] : Throw("Unable to get record value schema");
|
|
@@ -1707,8 +1707,8 @@ function FromRecord(left, right) {
|
|
|
1707
1707
|
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : !type_exports.IsRecord(right) ? ExtendsResult.False : Visit3(RecordValue(left), RecordValue(right));
|
|
1708
1708
|
}
|
|
1709
1709
|
function FromRegExp(left, right) {
|
|
1710
|
-
const L = type_exports.IsRegExp(left) ?
|
|
1711
|
-
const R = type_exports.IsRegExp(right) ?
|
|
1710
|
+
const L = type_exports.IsRegExp(left) ? String2() : left;
|
|
1711
|
+
const R = type_exports.IsRegExp(right) ? String2() : right;
|
|
1712
1712
|
return Visit3(L, R);
|
|
1713
1713
|
}
|
|
1714
1714
|
function FromStringRight(left, right) {
|
|
@@ -1937,7 +1937,7 @@ function RecordPattern(record) {
|
|
|
1937
1937
|
}
|
|
1938
1938
|
function RecordKey2(type) {
|
|
1939
1939
|
const pattern = RecordPattern(type);
|
|
1940
|
-
return pattern === PatternStringExact ?
|
|
1940
|
+
return pattern === PatternStringExact ? String2() : pattern === PatternNumberExact ? Number2() : String2({ pattern });
|
|
1941
1941
|
}
|
|
1942
1942
|
function RecordValue2(type) {
|
|
1943
1943
|
return type.patternProperties[RecordPattern(type)];
|
|
@@ -2606,7 +2606,7 @@ __export(type_exports2, {
|
|
|
2606
2606
|
Required: () => Required,
|
|
2607
2607
|
Rest: () => Rest,
|
|
2608
2608
|
ReturnType: () => ReturnType,
|
|
2609
|
-
String: () =>
|
|
2609
|
+
String: () => String2,
|
|
2610
2610
|
Symbol: () => Symbol2,
|
|
2611
2611
|
TemplateLiteral: () => TemplateLiteral,
|
|
2612
2612
|
Transform: () => Transform,
|
|
@@ -3045,26 +3045,30 @@ async function trackError(err, ctx, metadata) {
|
|
|
3045
3045
|
firstSeenAt: now,
|
|
3046
3046
|
lastSeenAt: now
|
|
3047
3047
|
});
|
|
3048
|
-
const event = await
|
|
3048
|
+
const event = await safeCreateEvent(group.id, err, ctx, metadata);
|
|
3049
3049
|
logger2.info("New error group tracked", { fingerprint, groupId: group.id });
|
|
3050
|
-
|
|
3050
|
+
if (event) {
|
|
3051
|
+
notifyErrorToSlack(group, event, "new", ctx.environment).catch((e) => logger2.warn("Slack notification failed", e));
|
|
3052
|
+
}
|
|
3051
3053
|
return;
|
|
3052
3054
|
}
|
|
3053
3055
|
if (existing.status === "resolved") {
|
|
3054
3056
|
await errorGroupsRepository.updateStatus(existing.id, "active");
|
|
3055
3057
|
await errorGroupsRepository.incrementCount(existing.id);
|
|
3056
|
-
const event = await
|
|
3058
|
+
const event = await safeCreateEvent(existing.id, err, ctx, metadata);
|
|
3057
3059
|
logger2.info("Error group reopened", { fingerprint, groupId: existing.id });
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3060
|
+
if (event) {
|
|
3061
|
+
notifyErrorToSlack(
|
|
3062
|
+
{ ...existing, status: "active", count: existing.count + 1 },
|
|
3063
|
+
event,
|
|
3064
|
+
"reopened",
|
|
3065
|
+
ctx.environment
|
|
3066
|
+
).catch((e) => logger2.warn("Slack notification failed", e));
|
|
3067
|
+
}
|
|
3064
3068
|
return;
|
|
3065
3069
|
}
|
|
3066
3070
|
await errorGroupsRepository.incrementCount(existing.id);
|
|
3067
|
-
await
|
|
3071
|
+
await safeCreateEvent(existing.id, err, ctx, metadata);
|
|
3068
3072
|
}
|
|
3069
3073
|
async function updateErrorGroupStatus(groupId, newStatus) {
|
|
3070
3074
|
const group = await errorGroupsRepository.findById(groupId);
|
|
@@ -3082,17 +3086,23 @@ async function updateErrorGroupStatus(groupId, newStatus) {
|
|
|
3082
3086
|
});
|
|
3083
3087
|
return updated;
|
|
3084
3088
|
}
|
|
3085
|
-
async function
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3089
|
+
async function safeCreateEvent(groupId, err, ctx, metadata) {
|
|
3090
|
+
try {
|
|
3091
|
+
return await errorEventsRepository.create({
|
|
3092
|
+
groupId,
|
|
3093
|
+
requestId: ctx.requestId,
|
|
3094
|
+
userId: ctx.userId,
|
|
3095
|
+
statusCode: ctx.statusCode,
|
|
3096
|
+
headers: ctx.headers,
|
|
3097
|
+
query: ctx.query,
|
|
3098
|
+
stackTrace: err.stack,
|
|
3099
|
+
metadata
|
|
3100
|
+
});
|
|
3101
|
+
} catch (e) {
|
|
3102
|
+
const cause = e instanceof Error && e.cause instanceof Error ? e.cause : e;
|
|
3103
|
+
logger2.warn("Failed to create error event", cause, { groupId });
|
|
3104
|
+
return null;
|
|
3105
|
+
}
|
|
3096
3106
|
}
|
|
3097
3107
|
|
|
3098
3108
|
// src/server/services/log.service.ts
|
|
@@ -3292,7 +3302,7 @@ function createMonitorErrorHandler(options = {}) {
|
|
|
3292
3302
|
path: ctx.path,
|
|
3293
3303
|
method: ctx.method,
|
|
3294
3304
|
requestId: ctx.requestId,
|
|
3295
|
-
userId: ctx.userId,
|
|
3305
|
+
userId: ctx.userId != null ? String(ctx.userId) : void 0,
|
|
3296
3306
|
headers: ctx.request.headers,
|
|
3297
3307
|
query: ctx.request.query,
|
|
3298
3308
|
environment: options.environment
|