Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 17 additions & 24 deletions apps/webapp/app/components/LogLevelTooltipInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BookOpenIcon } from "@heroicons/react/20/solid";
import { LinkButton } from "./primitives/Buttons";
import { Header3 } from "./primitives/Headers";
import { Paragraph } from "./primitives/Paragraph";
import { LogLevel } from "./logs/LogLevel";

export function LogLevelTooltipInfo() {
return (
Expand All @@ -13,51 +12,45 @@ export function LogLevelTooltipInfo() {
</Paragraph>
</div>
<div>
<div className="mb-0.5">
<Header3 className="text-blue-400">Info</Header3>
<div className="mb-1">
<LogLevel level="TRACE" />
</div>
<Paragraph variant="small" className="text-text-dimmed">
Traces and spans representing the execution flow of your tasks.
</Paragraph>
</div>
<div>
<div className="mb-1">
<LogLevel level="INFO" />
</div>
<Paragraph variant="small" className="text-text-dimmed">
General informational messages about task execution.
</Paragraph>
</div>
<div>
<div className="mb-0.5">
<Header3 className="text-warning">Warn</Header3>
<div className="mb-1">
<LogLevel level="WARN" />
</div>
<Paragraph variant="small" className="text-text-dimmed">
Warning messages indicating potential issues that don't prevent execution.
</Paragraph>
</div>
<div>
<div className="mb-0.5">
<Header3 className="text-error">Error</Header3>
<div className="mb-1">
<LogLevel level="ERROR" />
</div>
<Paragraph variant="small" className="text-text-dimmed">
Error messages for failures and exceptions during task execution.
</Paragraph>
</div>
<div>
<div className="mb-0.5">
<Header3 className="text-charcoal-400">Debug</Header3>
<div className="mb-1">
<LogLevel level="DEBUG" />
</div>
<Paragraph variant="small" className="text-text-dimmed">
Detailed diagnostic information for development and debugging.
</Paragraph>
</div>
<div className="border-t border-charcoal-700 pt-4">
<Header3>Tracing & Spans</Header3>
<Paragraph variant="small" className="text-text-dimmed">
Automatically track the flow of your code through task triggers, attempts, and HTTP
requests. Create custom traces to monitor specific operations.
</Paragraph>
</div>
<LinkButton
to="https://trigger.dev/docs/logging#tracing-and-spans"
variant="docs/small"
LeadingIcon={BookOpenIcon}
>
Read docs
</LinkButton>
</div>
);
}
16 changes: 16 additions & 0 deletions apps/webapp/app/components/logs/LogLevel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { cn } from "~/utils/cn";
import { getLevelColor } from "~/utils/logUtils";
import type { LogEntry } from "~/presenters/v3/LogsListPresenter.server";

export function LogLevel({ level }: { level: LogEntry["level"] }) {
return (
<span
className={cn(
"inline-flex items-center rounded border px-1 py-0.5 text-xxs font-medium uppercase tracking-wider",
getLevelColor(level)
)}
>
{level}
</span>
);
}
3 changes: 3 additions & 0 deletions apps/webapp/app/components/logs/LogsLevelFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { LogLevel } from "~/presenters/v3/LogsListPresenter.server";
import { cn } from "~/utils/cn";

const allLogLevels: { level: LogLevel; label: string; color: string }[] = [
{ level: "TRACE", label: "Trace", color: "text-purple-400" },
{ level: "INFO", label: "Info", color: "text-blue-400" },
{ level: "WARN", label: "Warning", color: "text-warning" },
{ level: "ERROR", label: "Error", color: "text-error" },
Expand All @@ -33,6 +34,8 @@ function getLevelBadgeColor(level: LogLevel): string {
return "text-error bg-error/10 border-error/20";
case "WARN":
return "text-warning bg-warning/10 border-warning/20";
case "TRACE":
return "text-purple-400 bg-purple-500/10 border-purple-500/20";
case "DEBUG":
return "text-charcoal-400 bg-charcoal-700 border-charcoal-600";
case "INFO":
Expand Down
62 changes: 26 additions & 36 deletions apps/webapp/app/components/logs/LogsSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,46 @@
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/20/solid";
import { useNavigate } from "@remix-run/react";
import { motion } from "framer-motion";
import { useCallback, useEffect, useRef, useState } from "react";
import { Input } from "~/components/primitives/Input";
import { ShortcutKey } from "~/components/primitives/ShortcutKey";
import { cn } from "~/utils/cn";
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
import { useSearchParams } from "~/hooks/useSearchParam";

export function LogsSearchInput() {
const location = useOptimisticLocation();
const navigate = useNavigate();
const inputRef = useRef<HTMLInputElement>(null);

const { value, replace, del } = useSearchParams();

// Get initial search value from URL
const searchParams = new URLSearchParams(location.search);
const initialSearch = searchParams.get("search") ?? "";
const initialSearch = value("search") ?? "";

const [text, setText] = useState(initialSearch);
const [isFocused, setIsFocused] = useState(false);

// Update text when URL search param changes (only when not focused to avoid overwriting user input)
useEffect(() => {
const params = new URLSearchParams(location.search);
const urlSearch = params.get("search") ?? "";
const urlSearch = value("search") ?? "";
if (urlSearch !== text && !isFocused) {
setText(urlSearch);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search]);
}, [value, text, isFocused]);

const handleSubmit = useCallback(() => {
const params = new URLSearchParams(location.search);
if (text.trim()) {
params.set("search", text.trim());
replace({ search: text.trim() });
} else {
params.delete("search");
del("search");
}
// Reset cursor when searching
params.delete("cursor");
params.delete("direction");
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
}, [text, location.pathname, location.search, navigate]);
}, [text, replace, del]);

const handleClear = useCallback(() => {
const handleClear = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setText("");
const params = new URLSearchParams(location.search);
params.delete("search");
params.delete("cursor");
params.delete("direction");
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
}, [location.pathname, location.search, navigate]);
del(["search", "cursor", "direction"]);
}, []);

return (
<div className="flex items-center gap-1">
Expand All @@ -71,7 +62,7 @@ export function LogsSearchInput() {
value={text}
onChange={(e) => setText(e.target.value)}
fullWidth
className={cn(isFocused && "placeholder:text-text-dimmed/70")}
className={cn("", isFocused && "placeholder:text-text-dimmed/70")}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
Expand All @@ -86,22 +77,21 @@ export function LogsSearchInput() {
icon={<MagnifyingGlassIcon className="size-4" />}
accessory={
text.length > 0 ? (
<ShortcutKey shortcut={{ key: "enter" }} variant="small" />
<div className="-mr-1 flex items-center gap-1">
<ShortcutKey shortcut={{ key: "enter" }} variant="small" />
<button
type="button"
onClick={handleClear}
className="flex size-4.5 items-center justify-center rounded-[2px] border border-text-dimmed/40 text-text-dimmed hover:bg-charcoal-700 hover:text-text-bright"
title="Clear search"
>
<XMarkIcon className="size-3" />
</button>
</div>
) : undefined
}
/>
</motion.div>

{text.length > 0 && (
<button
type="button"
onClick={handleClear}
className="flex size-6 items-center justify-center rounded text-text-dimmed hover:bg-charcoal-700 hover:text-text-bright"
title="Clear search"
>
<XMarkIcon className="size-4" />
</button>
)}
</div>
);
}
24 changes: 7 additions & 17 deletions apps/webapp/app/components/logs/LogsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import type { LogEntry } from "~/presenters/v3/LogsListPresenter.server";
import { getLevelColor, highlightSearchText } from "~/utils/logUtils";
import { highlightSearchText } from "~/utils/logUtils";
import { v3RunSpanPath } from "~/utils/pathBuilder";
import { DateTimeAccurate } from "../primitives/DateTime";
import { Paragraph } from "../primitives/Paragraph";
import { Spinner } from "../primitives/Spinner";
import { LogLevel } from "./LogLevel";
import { TruncatedCopyableValue } from "../primitives/TruncatedCopyableValue";
import { LogLevelTooltipInfo } from "~/components/LogLevelTooltipInfo";
import {
Expand Down Expand Up @@ -48,14 +49,14 @@ function getLevelBoxShadow(level: LogEntry["level"]): string {
return "inset 2px 0 0 0 rgb(234, 179, 8)";
case "INFO":
return "inset 2px 0 0 0 rgb(59, 130, 246)";
case "TRACE":
return "inset 2px 0 0 0 rgb(168, 85, 247)";
case "DEBUG":
default:
return "none";
}
}



export function LogsTable({
logs,
searchTerm,
Expand Down Expand Up @@ -162,7 +163,7 @@ export function LogsTable({
boxShadow: getLevelBoxShadow(log.level),
}}
>
<DateTimeAccurate date={log.triggeredTimestamp} />
<DateTimeAccurate date={log.triggeredTimestamp} hour12={false} />
</TableCell>
<TableCell className="min-w-24">
<TruncatedCopyableValue value={log.runId} />
Expand All @@ -171,14 +172,7 @@ export function LogsTable({
<span className="font-mono text-xs">{log.taskIdentifier}</span>
</TableCell>
<TableCell onClick={handleRowClick} hasAction>
<span
className={cn(
"inline-flex items-center rounded border px-1 py-0.5 text-xxs font-medium uppercase tracking-wider",
getLevelColor(log.level)
)}
>
{log.level}
</span>
<LogLevel level={log.level} />
</TableCell>
<TableCell className="max-w-0 truncate" onClick={handleRowClick} hasAction>
<span className="block truncate font-mono text-xs" title={log.message}>
Expand Down Expand Up @@ -233,11 +227,7 @@ function BlankState({ isLoading, onRefresh }: { isLoading?: boolean; onRefresh?:
No logs match your filters. Try refreshing or modifying your filters.
</Paragraph>
<div className="flex items-center gap-2">
<Button
LeadingIcon={ArrowPathIcon}
variant="tertiary/medium"
onClick={handleRefresh}
>
<Button LeadingIcon={ArrowPathIcon} variant="tertiary/medium" onClick={handleRefresh}>
Refresh
</Button>
</div>
Expand Down
25 changes: 13 additions & 12 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,18 +433,7 @@ export function SideMenu({
data-action="deployments"
isCollapsed={isCollapsed}
/>
{(user.admin || user.isImpersonating || featureFlags.hasLogsPageAccess) && (
<SideMenuItem
name="Logs"
icon={LogsIcon}
activeIconColor="text-logs"
inactiveIconColor="text-logs"
to={v3LogsPath(organization, project, environment)}
data-action="logs"
badge={<AlphaBadge />}
isCollapsed={isCollapsed}
/>
)}

<SideMenuItem
name="Test"
icon={BeakerIcon}
Expand All @@ -467,6 +456,18 @@ export function SideMenu({
)}
onCollapseToggle={handleSectionToggle("metrics")}
>
{(user.admin || user.isImpersonating || featureFlags.hasLogsPageAccess) && (
<SideMenuItem
name="Logs"
icon={LogsIcon}
activeIconColor="text-logs"
inactiveIconColor="text-logs"
to={v3LogsPath(organization, project, environment)}
data-action="logs"
badge={<AlphaBadge />}
isCollapsed={isCollapsed}
/>
)}
<SideMenuItem
name="Query"
icon={TableCellsIcon}
Expand Down
Loading