Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

104 linesCodeBlameRaw
1/**
2 * Log lines, as the Logs page, a request's page and a trace show them:
3 * one row each (time, level, service, the line), opened for its
4 * attributes and links to the request and trace it was written under.
5 */
6import { ChevronRight } from "lucide-react";
7import { useState } from "react";
8import { Link } from "react-router";
9
10import type { TelemetryLog } from "@g1t/contracts";
11
12import { cn } from "../lib/cn";
13import { clock } from "../lib/observability";
14import { LevelBadge } from "./observability";
15
16export function LogLines({ logs, base, showRequest = true, fresh }: { logs: TelemetryLog[]; base: string; showRequest?: boolean; fresh?: Set<string> }) {
17 return (
18 <ol className="divide-y divide-line font-mono text-xs" aria-label="Log lines">
19 {logs.map((log) => (
20 <LogLine key={log.id} log={log} base={base} showRequest={showRequest} fresh={fresh?.has(log.id) ?? false} />
21 ))}
22 </ol>
23 );
24}
25
26function LogLine({ log, base, showRequest, fresh }: { log: TelemetryLog; base: string; showRequest: boolean; fresh: boolean }) {
27 const [open, setOpen] = useState(false);
28 const attributes = Object.entries(log.attributes);
29 const multiline = log.body.includes("\n");
30 return (
31 <li
32 className={cn(
33 "group",
34 log.severity === "error" || log.severity === "fatal" ? "bg-danger/5" : log.severity === "warn" ? "bg-warn/5" : "",
35 fresh && "animate-[g1t-fresh_1.2s_ease-out]",
36 )}
37 >
38 <button
39 type="button"
40 onClick={() => setOpen(!open)}
41 aria-expanded={open}
42 className="grid w-full grid-cols-[1rem_8.5rem_3.25rem_minmax(0,1fr)] items-start gap-2 px-3 py-1.5 text-left hover:bg-raised/60 sm:grid-cols-[1rem_10.5rem_3.25rem_7rem_minmax(0,1fr)]"
43 >
44 <ChevronRight size={12} className={cn("mt-0.5 text-faint transition-transform", open && "rotate-90")} />
45 <span className="text-faint tabular-nums">{clock(log.time_us, true).slice(6)}</span>
46 <LevelBadge level={log.severity} />
47 <span className="hidden truncate text-muted sm:block">{log.service || "—"}</span>
48 <span className={cn("min-w-0 text-fg", open || !multiline ? "wrap-break-word whitespace-pre-wrap" : "truncate")}>
49 {open ? log.body : log.body.split("\n")[0]}
50 </span>
51 </button>
52 {open && (
53 <div className="space-y-2 px-3 pt-1 pb-3 pl-9">
54 <dl className="grid grid-cols-[minmax(8rem,auto)_1fr] gap-x-4 gap-y-1">
55 <dt className="text-faint">time</dt>
56 <dd className="tabular-nums">{clock(log.time_us, true)} UTC</dd>
57 {log.environment && (
58 <>
59 <dt className="text-faint">environment</dt>
60 <dd>{log.environment}</dd>
61 </>
62 )}
63 {log.deployment_id && (
64 <>
65 <dt className="text-faint">deployment</dt>
66 <dd>
67 <Link to={`${base}/deployments/${log.deployment_id}`} className="text-accent hover:underline">
68 {log.deployment_id}
69 </Link>
70 </dd>
71 </>
72 )}
73 {showRequest && log.request_id && (
74 <>
75 <dt className="text-faint">request</dt>
76 <dd>
77 <Link to={`${base}/observability/requests/${log.request_id}`} className="text-accent hover:underline">
78 {log.request_id}
79 </Link>
80 </dd>
81 </>
82 )}
83 {log.trace_id && (
84 <>
85 <dt className="text-faint">trace</dt>
86 <dd>
87 <Link to={`${base}/observability/traces/${log.trace_id}`} className="text-accent hover:underline">
88 {log.trace_id}
89 </Link>
90 </dd>
91 </>
92 )}
93 {attributes.map(([key, value]) => (
94 <div key={key} className="contents">
95 <dt className="truncate text-faint">{key}</dt>
96 <dd className="break-all">{typeof value === "string" ? value : JSON.stringify(value)}</dd>
97 </div>
98 ))}
99 </dl>
100 </div>
101 )}
102 </li>
103 );
104}