Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { PageContent } from "@/ui/layout/page-content";
import { PageWidthWrapper } from "@/ui/layout/page-width-wrapper";
import { getStatusCodeBadgeVariant } from "@/ui/logs/log-utils";
import { UserAvatar } from "@/ui/users/user-avatar";
import { CopyButton, StatusBadge, TimestampTooltip } from "@dub/ui";
import {
CopyButton,
LoadingSpinner,
StatusBadge,
TimestampTooltip,
} from "@dub/ui";
import { ChevronRight, StackY3 } from "@dub/ui/icons";
import { fetcher, formatDateTime } from "@dub/utils";
import Link from "next/link";
Expand All @@ -25,12 +30,22 @@ export function LogDetailPageClient({ logId }: { logId: string }) {
} = useSWR<EnrichedApiLog>(
workspaceId && `/api/logs/${logId}?workspaceId=${workspaceId}`,
fetcher,
{
onErrorRetry: (error, _key, _config, revalidate, { retryCount }) => {
if (error.status === 404 && retryCount < 8) {
setTimeout(() => revalidate({ retryCount }), 2000);
return;
}
},
},
);

const isRetrying = error?.status === 404 && !log;

return (
<PageContent
title={
isLoading ? (
isLoading || isRetrying ? (
<div className="h-7 w-48 animate-pulse rounded-md bg-neutral-200" />
) : (
<div className="flex items-center gap-1">
Expand All @@ -55,8 +70,16 @@ export function LogDetailPageClient({ logId }: { logId: string }) {
<PageWidthWrapper className="pb-10">
{log ? (
<LogDetailContent log={log} />
) : isLoading ? (
<LogDetailSkeleton />
) : isLoading || isRetrying ? (
<div className="flex flex-col gap-4">
{isRetrying && (
<div className="flex items-center justify-center gap-2 rounded-lg border border-neutral-200 bg-neutral-50 px-4 py-3 text-sm text-neutral-600">
<LoadingSpinner className="size-4" />
Retrieving log... this may take a few seconds
</div>
)}
<LogDetailSkeleton />
</div>
) : error ? (
<div className="flex flex-col items-center justify-center gap-2 py-16 text-center">
<p className="text-sm font-medium text-neutral-700">
Expand Down
3 changes: 3 additions & 0 deletions apps/web/lib/api-logs/capture-request-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function getRoutePattern(path: string): string {
}

export function captureRequestLog({
requestId,
req,
response,
workspace,
Expand All @@ -47,6 +48,7 @@ export function captureRequestLog({
requestHeaders,
startTime,
}: {
requestId?: string;
req: Request;
response: Response;
workspace: WorkspaceWithUsers;
Expand Down Expand Up @@ -83,6 +85,7 @@ export function captureRequestLog({
} catch {}

await recordApiLog({
requestId,
workspaceId: workspace.id,
method: req.method,
path: url.pathname,
Expand Down
6 changes: 5 additions & 1 deletion apps/web/lib/api-logs/record-api-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ingestionApiLogSchemaTB = apiLogSchemaTB.extend({
type ApiLogInput = z.infer<typeof ingestionApiLogSchemaTB>;

type RecordApiLogParams = {
requestId?: string;
workspaceId: string;
method: string;
path: string;
Expand All @@ -33,6 +34,7 @@ const recordApiLogTB = tb.buildIngestEndpoint({
});

export const recordApiLog = async ({
requestId,
workspaceId,
method,
path,
Expand All @@ -46,8 +48,10 @@ export const recordApiLog = async ({
userId,
requestType,
}: RecordApiLogParams) => {
requestId = requestId ?? createId({ prefix: "req_" });

const apiLog: ApiLogInput = {
id: createId({ prefix: "req_" }),
id: requestId,
timestamp: new Date().toISOString(),
workspace_id: workspaceId,
method,
Expand Down
9 changes: 9 additions & 0 deletions apps/web/lib/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const ErrorSchema = z.object({
description: "A URL to more information about the error code reported.",
example: "https://dub.co/docs/api-reference",
}),
request_log_url: z.string().optional().meta({
description: "A URL to the request log for this error.",
example:
"https://app.dub.co/acme/settings/logs/req_1KP2VXTJ5ZHJAMZYTYX1YS1RN",
}),
}),
});

Expand All @@ -43,19 +48,23 @@ export type ErrorCodes = z.infer<typeof ErrorCode>;
export class DubApiError extends Error {
public readonly code: z.infer<typeof ErrorCode>;
public readonly docUrl?: string;
public readonly requestLogUrl?: string;

constructor({
code,
message,
docUrl,
requestLogUrl,
}: {
code: z.infer<typeof ErrorCode>;
message: string;
docUrl?: string;
requestLogUrl?: string;
}) {
super(message);
this.code = code;
this.docUrl = docUrl ?? `${docErrorUrl}#${code.replace("_", "-")}`;
this.requestLogUrl = requestLogUrl;
}
}

Expand Down
4 changes: 4 additions & 0 deletions apps/web/lib/auth/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { API_DOMAIN, getSearchParams } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import { headers } from "next/headers";
import { captureRequestLog } from "../api-logs/capture-request-log";
import { createId } from "../api/create-id";
import { getRatelimitForPlan } from "../api/get-ratelimit-for-plan";
import {
PermissionAction,
Expand Down Expand Up @@ -91,6 +92,7 @@ export const withWorkspace = (
const params = (await initialParams) || {};
const searchParams = getSearchParams(req.url);

const requestId = createId({ prefix: "req_" });
let apiKey: string | undefined = undefined;
let requestHeaders = await headers();
let responseHeaders = new Headers();
Expand Down Expand Up @@ -479,6 +481,7 @@ export const withWorkspace = (

if (workspace) {
captureRequestLog({
requestId,
req: reqForLog,
response,
workspace,
Expand All @@ -499,6 +502,7 @@ export const withWorkspace = (

if (workspace) {
captureRequestLog({
requestId,
req: reqForLog,
response: errorResponse,
workspace,
Expand Down
Loading