About
A searchable, scrollable log tail panel with level filters, line-wrap toggle, and an auto-follow mode that pauses when you scroll up and offers a jump-to-latest button.
Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Log Viewer to your project and fits it to your theme.
Code
How Log Viewer is used: the demo's code. The component's own source comes with a free account.
Usage.tsx
"use client";
import {
LogViewer,
type LogViewerLine,
type LogViewerLevel,
} from "@/components/ui/log-viewer";
import * as React from "react";
const SOURCES = ["api", "auth", "billing", "scheduler", "worker"] as const;
function rid() {
return Math.random().toString(36).slice(2, 6);
}
const TEMPLATES: Array<{ level: LogViewerLevel; text: () => string }> = [
{
level: "info",
text: () =>
`POST /v1/payments 201 in ${120 + Math.floor(Math.random() * 600)}ms`,
},
{
level: "info",
text: () =>
`GET /v1/invoices/${rid()} 200 in ${80 + Math.floor(Math.random() * 400)}ms`,
},
{ level: "info", text: () => `user ${rid()} signed in with SSO` },
{
level: "info",
text: () =>
`job invoice.render completed in ${90 + Math.floor(Math.random() * 500)}ms`,
},
{ level: "debug", text: () => `cache hit for key session:${rid()}` },
{
level: "debug",
text: () =>
`acquired lock queue:refunds after ${50 + Math.floor(Math.random() * 300)}ms`,
},
{
level: "warn",
text: () =>
`slow query on invoices_by_customer took ${300 + Math.floor(Math.random() * 300)}ms`,
},
{
level: "warn",
text: () =>
`retrying webhook delivery (attempt ${1 + Math.floor(Math.random() * 4)} of 5)`,
},
{
level: "error",
text: () => `payment_intent ${rid()} failed: card_declined`,
},
{
level: "error",
text: () => "connection reset by peer while reading from redis:6379",
},
];
function makeLine(id: number, at: number): LogViewerLine {
const template = TEMPLATES[Math.floor(Math.random() * TEMPLATES.length)];
const source = SOURCES[Math.floor(Math.random() * SOURCES.length)];
return {
id,
timestamp: at,
level: template.level,
source,
message: template.text(),
};
}
export default function LogViewerDemo() {
const next = React.useRef(0);
const [lines, setLines] = React.useState<LogViewerLine[]>(() => {
const now = Date.now();
return Array.from({ length: 60 }, (_, index) =>
makeLine(next.current++, now - (60 - index) * 1000),
);
});
React.useEffect(() => {
const timer = setInterval(() => {
setLines((current) => {
const grown = [...current, makeLine(next.current++, Date.now())];
return grown.length > 400 ? grown.slice(grown.length - 400) : grown;
});
}, 1500);
return () => clearInterval(timer);
}, []);
return (
<div className="flex w-full items-center justify-center bg-background p-6">
<div className="w-full max-w-2xl">
<LogViewer lines={lines} label="Deploy logs" height={360} />
</div>
</div>
);
}








