Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Projects have Observability: their requests, logs, traces, metrics and errors, from apps on g1t.page with nothing to set up, and from software anywhere else over OpenTelemetry. | 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 | */ | |
| 6 | import { ChevronRight } from "lucide-react"; | |
| 7 | import { useState } from "react"; | |
| 8 | import { Link } from "react-router"; | |
| 9 | ||
| 10 | import type { TelemetryLog } from "@g1t/contracts"; | |
| 11 | ||
| 12 | import { cn } from "../lib/cn"; | |
| 13 | import { clock } from "../lib/observability"; | |
| 14 | import { LevelBadge } from "./observability"; | |
| 15 | ||
| 16 | export 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 | ||
| 26 | function 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 className={cn("group", log.severity === "error" || log.severity === "fatal" ? "bg-danger/5" : log.severity === "warn" ? "bg-warn/5" : "", fresh && "animate-[g1t-fresh_1.2s_ease-out]")}> | |
| 32 | <button | |
| 33 | type="button" | |
| 34 | onClick={() => setOpen(!open)} | |
| 35 | aria-expanded={open} | |
| 36 | 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)]" | |
| 37 | > | |
| 38 | <ChevronRight size={12} className={cn("mt-0.5 text-faint transition-transform", open && "rotate-90")} /> | |
| 39 | <span className="text-faint tabular-nums">{clock(log.time_us, true).slice(6)}</span> | |
| 40 | <LevelBadge level={log.severity} /> | |
| 41 | <span className="hidden truncate text-muted sm:block">{log.service || "—"}</span> | |
| 42 | <span className={cn("min-w-0 text-fg", open || !multiline ? "break-words whitespace-pre-wrap" : "truncate")}>{open ? log.body : log.body.split("\n")[0]}</span> | |
| 43 | </button> | |
| 44 | {open && ( | |
| 45 | <div className="space-y-2 px-3 pt-1 pb-3 pl-9"> | |
| 46 | <dl className="grid grid-cols-[minmax(8rem,auto)_1fr] gap-x-4 gap-y-1"> | |
| 47 | <dt className="text-faint">time</dt> | |
| 48 | <dd className="tabular-nums">{clock(log.time_us, true)} UTC</dd> | |
| 49 | {log.environment && ( | |
| 50 | <> | |
| 51 | <dt className="text-faint">environment</dt> | |
| 52 | <dd>{log.environment}</dd> | |
| 53 | </> | |
| 54 | )} | |
| 55 | {log.deployment_id && ( | |
| 56 | <> | |
| 57 | <dt className="text-faint">deployment</dt> | |
| 58 | <dd> | |
| 59 | <Link to={`${base}/deployments/${log.deployment_id}`} className="text-accent hover:underline"> | |
| 60 | {log.deployment_id} | |
| 61 | </Link> | |
| 62 | </dd> | |
| 63 | </> | |
| 64 | )} | |
| 65 | {showRequest && log.request_id && ( | |
| 66 | <> | |
| 67 | <dt className="text-faint">request</dt> | |
| 68 | <dd> | |
| 69 | <Link to={`${base}/observability/requests/${log.request_id}`} className="text-accent hover:underline"> | |
| 70 | {log.request_id} | |
| 71 | </Link> | |
| 72 | </dd> | |
| 73 | </> | |
| 74 | )} | |
| 75 | {log.trace_id && ( | |
| 76 | <> | |
| 77 | <dt className="text-faint">trace</dt> | |
| 78 | <dd> | |
| 79 | <Link to={`${base}/observability/traces/${log.trace_id}`} className="text-accent hover:underline"> | |
| 80 | {log.trace_id} | |
| 81 | </Link> | |
| 82 | </dd> | |
| 83 | </> | |
| 84 | )} | |
| 85 | {attributes.map(([key, value]) => ( | |
| 86 | <div key={key} className="contents"> | |
| 87 | <dt className="truncate text-faint">{key}</dt> | |
| 88 | <dd className="break-all">{typeof value === "string" ? value : JSON.stringify(value)}</dd> | |
| 89 | </div> | |
| 90 | ))} | |
| 91 | </dl> | |
| 92 | </div> | |
| 93 | )} | |
| 94 | </li> | |
| 95 | ); | |
| 96 | } |