From ed1cb825d235632cbaf83b0fcd14f2074d196fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Mon, 1 Dec 2025 16:04:20 +0100 Subject: [PATCH 01/19] add last_updated information to develop docs --- app/[[...path]]/page.tsx | 16 ++++- src/components/docPage/index.tsx | 3 + src/components/lastUpdated/index.tsx | 102 +++++++++++++++++++++++++++ src/mdx.ts | 14 +++- src/types/frontmatter.ts | 9 +++ src/utils/getGitMetadata.ts | 58 +++++++++++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/components/lastUpdated/index.tsx create mode 100644 src/utils/getGitMetadata.ts diff --git a/app/[[...path]]/page.tsx b/app/[[...path]]/page.tsx index 70872af0347639..a3ea25b73cb4e2 100644 --- a/app/[[...path]]/page.tsx +++ b/app/[[...path]]/page.tsx @@ -131,6 +131,20 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) } const {mdxSource, frontMatter} = doc; + // Fetch git metadata on-demand for this page only (faster in dev mode) + let gitMetadata = pageNode.frontmatter.gitMetadata; + if (!gitMetadata && pageNode.frontmatter.sourcePath) { + // In dev mode or if not cached, fetch git metadata for current page only + const {getGitMetadata} = await import('sentry-docs/utils/getGitMetadata'); + gitMetadata = getGitMetadata(pageNode.frontmatter.sourcePath); + } + + // Merge gitMetadata into frontMatter + const frontMatterWithGit = { + ...frontMatter, + gitMetadata, + }; + // pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc const pageType = (params.path?.[0] as PageType) || 'unknown'; return ( @@ -138,7 +152,7 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index 9390875a78d018..eb0eb7a8816c6a 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -16,6 +16,7 @@ import {CopyMarkdownButton} from '../copyMarkdownButton'; import {DocFeedback} from '../docFeedback'; import {GitHubCTA} from '../githubCTA'; import {Header} from '../header'; +import {LastUpdated} from '../lastUpdated'; import Mermaid from '../mermaid'; import {PaginationNav} from '../paginationNav'; import {PlatformSdkDetail} from '../platformSdkDetail'; @@ -94,6 +95,8 @@ export function DocPage({

{frontMatter.title}

+ {/* Show last updated info for develop-docs pages */} + {frontMatter.gitMetadata && }

{frontMatter.description}

{/* This exact id is important for Algolia indexing */} diff --git a/src/components/lastUpdated/index.tsx b/src/components/lastUpdated/index.tsx new file mode 100644 index 00000000000000..d5926b8de8da88 --- /dev/null +++ b/src/components/lastUpdated/index.tsx @@ -0,0 +1,102 @@ +'use client'; + +import Link from 'next/link'; + +interface GitMetadata { + commitHash: string; + author: string; + timestamp: number; +} + +interface LastUpdatedProps { + gitMetadata: GitMetadata; +} + +/** + * Format a timestamp as a relative time string (e.g., "2 days ago") + */ +function formatRelativeTime(timestamp: number): string { + const now = Date.now(); + const diff = now - timestamp * 1000; // timestamp is in seconds + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const months = Math.floor(days / 30); + const years = Math.floor(days / 365); + + if (years > 0) { + return years === 1 ? '1 year ago' : `${years} years ago`; + } + if (months > 0) { + return months === 1 ? '1 month ago' : `${months} months ago`; + } + if (days > 0) { + return days === 1 ? '1 day ago' : `${days} days ago`; + } + if (hours > 0) { + return hours === 1 ? '1 hour ago' : `${hours} hours ago`; + } + if (minutes > 0) { + return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`; + } + return 'just now'; +} + +/** + * Format a timestamp as a full date string for tooltip + */ +function formatFullDate(timestamp: number): string { + const date = new Date(timestamp * 1000); + return date.toLocaleString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + hour12: true, + }); +} + +/** + * Abbreviate a commit hash to first 7 characters + */ +function abbreviateHash(hash: string): string { + return hash.substring(0, 7); +} + +export function LastUpdated({gitMetadata}: LastUpdatedProps) { + const {commitHash, author, timestamp} = gitMetadata; + const relativeTime = formatRelativeTime(timestamp); + const fullDate = formatFullDate(timestamp); + const abbreviatedHash = abbreviateHash(commitHash); + const commitUrl = `https://github.com/getsentry/sentry-docs/commit/${commitHash}`; + + return ( +
+ {/* Text content */} + + updated by + {author} + {/* Relative time with tooltip */} + + {relativeTime} + + + + {/* Commit link */} + + + + #{abbreviatedHash} + + +
+ ); +} + diff --git a/src/mdx.ts b/src/mdx.ts index 611e2f212ead17..0e621bc6959944 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -252,10 +252,22 @@ export async function getDevDocsFrontMatterUncached(): Promise { const source = await readFile(file, 'utf8'); const {data: frontmatter} = matter(source); + const sourcePath = path.join(folder, fileName); + + // In production builds, fetch git metadata for all pages upfront + // In development, skip this and fetch on-demand per page (faster dev server startup) + let gitMetadata: typeof frontmatter.gitMetadata = undefined; + if (process.env.NODE_ENV !== 'development') { + const {getGitMetadata} = await import('./utils/getGitMetadata'); + const metadata = getGitMetadata(sourcePath); + gitMetadata = metadata ?? undefined; + } + return { ...(frontmatter as FrontMatter), slug: fileName.replace(/\/index.mdx?$/, '').replace(/\.mdx?$/, ''), - sourcePath: path.join(folder, fileName), + sourcePath, + gitMetadata, }; }, {concurrency: FILE_CONCURRENCY_LIMIT} diff --git a/src/types/frontmatter.ts b/src/types/frontmatter.ts index a336bcefefe48a..6477c336fc5a5e 100644 --- a/src/types/frontmatter.ts +++ b/src/types/frontmatter.ts @@ -114,6 +114,15 @@ export interface FrontMatter { */ sourcePath?: string; + /** + * Git metadata for the last commit that modified this file + */ + gitMetadata?: { + commitHash: string; + author: string; + timestamp: number; + }; + /** * Specific guides that this page is relevant to. */ diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts new file mode 100644 index 00000000000000..4ef67dcfe3c6da --- /dev/null +++ b/src/utils/getGitMetadata.ts @@ -0,0 +1,58 @@ +import {execSync} from 'child_process'; +import path from 'path'; + +export interface GitMetadata { + commitHash: string; + author: string; + timestamp: number; +} + +// Cache to avoid repeated git calls during build +const gitMetadataCache = new Map(); + +/** + * Get git metadata for a file + * @param filePath - Path to the file relative to the repository root + * @returns Git metadata or null if unavailable + */ +export function getGitMetadata(filePath: string): GitMetadata | null { + // Check cache first + if (gitMetadataCache.has(filePath)) { + return gitMetadataCache.get(filePath) ?? null; + } + + try { + // Get commit hash, author name, and timestamp + const logOutput = execSync( + `git log -1 --format="%H|%an|%at" -- "${filePath}"`, + { + encoding: 'utf8', + cwd: path.resolve(process.cwd()), + stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr + } + ).trim(); + + if (!logOutput) { + // No commits found for this file + gitMetadataCache.set(filePath, null); + return null; + } + + const [commitHash, author, timestampStr] = logOutput.split('|'); + const timestamp = parseInt(timestampStr, 10); + + const metadata: GitMetadata = { + commitHash, + author, + timestamp, + }; + + gitMetadataCache.set(filePath, metadata); + return metadata; + } catch (error) { + // Git command failed or file doesn't exist in git + gitMetadataCache.set(filePath, null); + return null; + } +} + From 16ad85f26460aa9c6315786f2b6fc721bf2308b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Mon, 1 Dec 2025 16:15:27 +0100 Subject: [PATCH 02/19] fix TS null vs undefined --- app/[[...path]]/page.tsx | 3 ++- src/types/frontmatter.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/[[...path]]/page.tsx b/app/[[...path]]/page.tsx index a3ea25b73cb4e2..bee2312ba6775c 100644 --- a/app/[[...path]]/page.tsx +++ b/app/[[...path]]/page.tsx @@ -136,7 +136,8 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) if (!gitMetadata && pageNode.frontmatter.sourcePath) { // In dev mode or if not cached, fetch git metadata for current page only const {getGitMetadata} = await import('sentry-docs/utils/getGitMetadata'); - gitMetadata = getGitMetadata(pageNode.frontmatter.sourcePath); + const metadata = getGitMetadata(pageNode.frontmatter.sourcePath); + gitMetadata = metadata ?? undefined; } // Merge gitMetadata into frontMatter diff --git a/src/types/frontmatter.ts b/src/types/frontmatter.ts index 6477c336fc5a5e..a7974ae261ebe3 100644 --- a/src/types/frontmatter.ts +++ b/src/types/frontmatter.ts @@ -115,7 +115,7 @@ export interface FrontMatter { sourcePath?: string; /** - * Git metadata for the last commit that modified this file + * Git metadata for the last commit & author that modified this file */ gitMetadata?: { commitHash: string; From 4e916b66ff994a35d0021a61fda992d322c9fa5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Mon, 1 Dec 2025 16:20:32 +0100 Subject: [PATCH 03/19] Lint --- src/components/lastUpdated/index.tsx | 2 +- src/types/frontmatter.ts | 20 ++++++++++---------- src/utils/getGitMetadata.ts | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/lastUpdated/index.tsx b/src/components/lastUpdated/index.tsx index d5926b8de8da88..934115f6a5abfa 100644 --- a/src/components/lastUpdated/index.tsx +++ b/src/components/lastUpdated/index.tsx @@ -3,8 +3,8 @@ import Link from 'next/link'; interface GitMetadata { - commitHash: string; author: string; + commitHash: string; timestamp: number; } diff --git a/src/types/frontmatter.ts b/src/types/frontmatter.ts index a7974ae261ebe3..0791839148e894 100644 --- a/src/types/frontmatter.ts +++ b/src/types/frontmatter.ts @@ -35,15 +35,23 @@ export interface FrontMatter { */ fullWidth?: boolean; + /** + * Git metadata for the last commit & author that modified this file + */ + gitMetadata?: { + author: string; + commitHash: string; + timestamp: number; + }; /** * A list of keywords for indexing with search. */ keywords?: string[]; + /** * Set this to true to show a "new" badge next to the title in the sidebar */ new?: boolean; - /** * The next page in the bottom pagination navigation. */ @@ -53,6 +61,7 @@ export interface FrontMatter { * takes precedence over children when present */ next_steps?: string[]; + /** * Set this to true to disable indexing (robots, algolia) of this content. */ @@ -114,15 +123,6 @@ export interface FrontMatter { */ sourcePath?: string; - /** - * Git metadata for the last commit & author that modified this file - */ - gitMetadata?: { - commitHash: string; - author: string; - timestamp: number; - }; - /** * Specific guides that this page is relevant to. */ diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts index 4ef67dcfe3c6da..8edac72ed82b9e 100644 --- a/src/utils/getGitMetadata.ts +++ b/src/utils/getGitMetadata.ts @@ -2,8 +2,8 @@ import {execSync} from 'child_process'; import path from 'path'; export interface GitMetadata { - commitHash: string; author: string; + commitHash: string; timestamp: number; } From 928a87834f69d874a94a006af59451850ba6cce2 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:14:47 +0000 Subject: [PATCH 04/19] [getsentry/action-github-commit] Auto commit --- src/components/docPage/index.tsx | 4 +++- src/components/lastUpdated/index.tsx | 1 - src/mdx.ts | 4 ++-- src/utils/getGitMetadata.ts | 14 +++++--------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index eb0eb7a8816c6a..3e9171a864f26c 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -96,7 +96,9 @@ export function DocPage({

{frontMatter.title}

{/* Show last updated info for develop-docs pages */} - {frontMatter.gitMetadata && } + {frontMatter.gitMetadata && ( + + )}

{frontMatter.description}

{/* This exact id is important for Algolia indexing */} diff --git a/src/components/lastUpdated/index.tsx b/src/components/lastUpdated/index.tsx index 934115f6a5abfa..e076aa679377b2 100644 --- a/src/components/lastUpdated/index.tsx +++ b/src/components/lastUpdated/index.tsx @@ -99,4 +99,3 @@ export function LastUpdated({gitMetadata}: LastUpdatedProps) {
); } - diff --git a/src/mdx.ts b/src/mdx.ts index 0e621bc6959944..4e2ba9a51f0486 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -253,7 +253,7 @@ export async function getDevDocsFrontMatterUncached(): Promise { const source = await readFile(file, 'utf8'); const {data: frontmatter} = matter(source); const sourcePath = path.join(folder, fileName); - + // In production builds, fetch git metadata for all pages upfront // In development, skip this and fetch on-demand per page (faster dev server startup) let gitMetadata: typeof frontmatter.gitMetadata = undefined; @@ -262,7 +262,7 @@ export async function getDevDocsFrontMatterUncached(): Promise { const metadata = getGitMetadata(sourcePath); gitMetadata = metadata ?? undefined; } - + return { ...(frontmatter as FrontMatter), slug: fileName.replace(/\/index.mdx?$/, '').replace(/\.mdx?$/, ''), diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts index 8edac72ed82b9e..3d494ebbcef6cb 100644 --- a/src/utils/getGitMetadata.ts +++ b/src/utils/getGitMetadata.ts @@ -23,14 +23,11 @@ export function getGitMetadata(filePath: string): GitMetadata | null { try { // Get commit hash, author name, and timestamp - const logOutput = execSync( - `git log -1 --format="%H|%an|%at" -- "${filePath}"`, - { - encoding: 'utf8', - cwd: path.resolve(process.cwd()), - stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr - } - ).trim(); + const logOutput = execSync(`git log -1 --format="%H|%an|%at" -- "${filePath}"`, { + encoding: 'utf8', + cwd: path.resolve(process.cwd()), + stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr + }).trim(); if (!logOutput) { // No commits found for this file @@ -55,4 +52,3 @@ export function getGitMetadata(filePath: string): GitMetadata | null { return null; } } - From 60325f6e3f76f043eb061d6449362da86ff3ca11 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:15:05 +0000 Subject: [PATCH 05/19] [getsentry/action-github-commit] Auto commit From 22617744d14ba8ab5cc318058031927a3377fb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Mon, 1 Dec 2025 23:56:09 +0100 Subject: [PATCH 06/19] Fix for only showing on develop docs --- app/[[...path]]/page.tsx | 2 +- src/mdx.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/[[...path]]/page.tsx b/app/[[...path]]/page.tsx index bee2312ba6775c..d6a191513040aa 100644 --- a/app/[[...path]]/page.tsx +++ b/app/[[...path]]/page.tsx @@ -133,7 +133,7 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) // Fetch git metadata on-demand for this page only (faster in dev mode) let gitMetadata = pageNode.frontmatter.gitMetadata; - if (!gitMetadata && pageNode.frontmatter.sourcePath) { + if (!gitMetadata && pageNode.frontmatter.sourcePath?.startsWith('develop-docs/')) { // In dev mode or if not cached, fetch git metadata for current page only const {getGitMetadata} = await import('sentry-docs/utils/getGitMetadata'); const metadata = getGitMetadata(pageNode.frontmatter.sourcePath); diff --git a/src/mdx.ts b/src/mdx.ts index 4e2ba9a51f0486..077ba5931c01b4 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -253,11 +253,11 @@ export async function getDevDocsFrontMatterUncached(): Promise { const source = await readFile(file, 'utf8'); const {data: frontmatter} = matter(source); const sourcePath = path.join(folder, fileName); - - // In production builds, fetch git metadata for all pages upfront + + // In production builds, fetch git metadata for develop-docs pages only // In development, skip this and fetch on-demand per page (faster dev server startup) let gitMetadata: typeof frontmatter.gitMetadata = undefined; - if (process.env.NODE_ENV !== 'development') { + if (process.env.NODE_ENV !== 'development' && sourcePath.startsWith('develop-docs/')) { const {getGitMetadata} = await import('./utils/getGitMetadata'); const metadata = getGitMetadata(sourcePath); gitMetadata = metadata ?? undefined; From 353c89564549c56caaed5e7fdb44658c7e4b37cf Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:02:46 +0000 Subject: [PATCH 07/19] [getsentry/action-github-commit] Auto commit --- src/mdx.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mdx.ts b/src/mdx.ts index 077ba5931c01b4..7e4462e1035bce 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -253,11 +253,14 @@ export async function getDevDocsFrontMatterUncached(): Promise { const source = await readFile(file, 'utf8'); const {data: frontmatter} = matter(source); const sourcePath = path.join(folder, fileName); - + // In production builds, fetch git metadata for develop-docs pages only // In development, skip this and fetch on-demand per page (faster dev server startup) let gitMetadata: typeof frontmatter.gitMetadata = undefined; - if (process.env.NODE_ENV !== 'development' && sourcePath.startsWith('develop-docs/')) { + if ( + process.env.NODE_ENV !== 'development' && + sourcePath.startsWith('develop-docs/') + ) { const {getGitMetadata} = await import('./utils/getGitMetadata'); const metadata = getGitMetadata(sourcePath); gitMetadata = metadata ?? undefined; From f387289be43c8902c884849cf28926a48b2d336d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Tue, 2 Dec 2025 00:22:55 +0100 Subject: [PATCH 08/19] return new object copies, preventing reference sharing in cached metadata --- src/utils/getGitMetadata.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts index 3d494ebbcef6cb..b49fed7b5234b6 100644 --- a/src/utils/getGitMetadata.ts +++ b/src/utils/getGitMetadata.ts @@ -18,7 +18,9 @@ const gitMetadataCache = new Map(); export function getGitMetadata(filePath: string): GitMetadata | null { // Check cache first if (gitMetadataCache.has(filePath)) { - return gitMetadataCache.get(filePath) ?? null; + const cached = gitMetadataCache.get(filePath); + // Return a NEW copy to avoid reference sharing + return cached ? { ...cached } : null; } try { @@ -38,14 +40,23 @@ export function getGitMetadata(filePath: string): GitMetadata | null { const [commitHash, author, timestampStr] = logOutput.split('|'); const timestamp = parseInt(timestampStr, 10); + // Create a fresh object for each call to avoid reference sharing const metadata: GitMetadata = { commitHash, author, timestamp, }; + // Cache the metadata gitMetadataCache.set(filePath, metadata); - return metadata; + + // IMPORTANT: Return a NEW object, not the cached one + // This prevents all pages from sharing the same object reference + return { + commitHash: metadata.commitHash, + author: metadata.author, + timestamp: metadata.timestamp, + }; } catch (error) { // Git command failed or file doesn't exist in git gitMetadataCache.set(filePath, null); From df13418a2d20654ed6fd7f3c13b00e2ae498502d Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:24:03 +0000 Subject: [PATCH 09/19] [getsentry/action-github-commit] Auto commit --- src/utils/getGitMetadata.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts index b49fed7b5234b6..549627aa5e5e1a 100644 --- a/src/utils/getGitMetadata.ts +++ b/src/utils/getGitMetadata.ts @@ -20,7 +20,7 @@ export function getGitMetadata(filePath: string): GitMetadata | null { if (gitMetadataCache.has(filePath)) { const cached = gitMetadataCache.get(filePath); // Return a NEW copy to avoid reference sharing - return cached ? { ...cached } : null; + return cached ? {...cached} : null; } try { @@ -49,7 +49,7 @@ export function getGitMetadata(filePath: string): GitMetadata | null { // Cache the metadata gitMetadataCache.set(filePath, metadata); - + // IMPORTANT: Return a NEW object, not the cached one // This prevents all pages from sharing the same object reference return { From af1d879d2f0596387d3e70e3bf2145eb747dbf42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Tue, 2 Dec 2025 14:03:58 +0100 Subject: [PATCH 10/19] Add some debug code --- src/mdx.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mdx.ts b/src/mdx.ts index 7e4462e1035bce..5e5a6ab0bc92fd 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -263,7 +263,13 @@ export async function getDevDocsFrontMatterUncached(): Promise { ) { const {getGitMetadata} = await import('./utils/getGitMetadata'); const metadata = getGitMetadata(sourcePath); - gitMetadata = metadata ?? undefined; + // Ensure we create a completely new object to avoid any reference sharing + gitMetadata = metadata ? {...metadata} : undefined; + + // Log during build to debug Vercel issues + if (process.env.CI || process.env.VERCEL) { + console.log(`[BUILD] Git metadata for ${sourcePath}:`, gitMetadata); + } } return { From 6733525161fa7b9bbf02e71c6f086fd159c36c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Tue, 2 Dec 2025 14:04:20 +0100 Subject: [PATCH 11/19] Update getGitMetadata.ts --- src/utils/getGitMetadata.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts index 549627aa5e5e1a..07b3688227be4e 100644 --- a/src/utils/getGitMetadata.ts +++ b/src/utils/getGitMetadata.ts @@ -31,6 +31,11 @@ export function getGitMetadata(filePath: string): GitMetadata | null { stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr }).trim(); + // Log for debugging on Vercel + if (process.env.CI || process.env.VERCEL) { + console.log(`[getGitMetadata] File: ${filePath} -> Output: ${logOutput}`); + } + if (!logOutput) { // No commits found for this file gitMetadataCache.set(filePath, null); From dbee391430ddf912d027983aac1750f598d1e492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Thu, 15 Jan 2026 13:48:59 +0100 Subject: [PATCH 12/19] Capture queue time docs --- .../ruby/common/configuration/options.mdx | 26 ++++++++++ .../automatic-instrumentation.mdx | 2 + .../instrumentation/performance-metrics.mdx | 2 + .../performance/queue-time-capture/ruby.mdx | 50 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 platform-includes/performance/queue-time-capture/ruby.mdx diff --git a/docs/platforms/ruby/common/configuration/options.mdx b/docs/platforms/ruby/common/configuration/options.mdx index 49955f5533f8ff..bb646f46fee0ec 100644 --- a/docs/platforms/ruby/common/configuration/options.mdx +++ b/docs/platforms/ruby/common/configuration/options.mdx @@ -326,6 +326,32 @@ config.trace_ignore_status_codes = [404, (502..511)] + + +Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.queue_time_ms`. + +This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. + +To disable queue time capture: + +```ruby +config.capture_queue_time = false +``` + +**Nginx:** + +```nginx +proxy_set_header X-Request-Start "t=${msec}"; +``` + +**HAProxy:** + +```haproxy +http-request set-header X-Request-Start t=%Ts%ms +``` + + + The instrumenter to use, `:sentry` or `:otel` for [use with OpenTelemetry](../../tracing/instrumentation/opentelemetry). diff --git a/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx index 06e5c62996a28c..969849a219443c 100644 --- a/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx +++ b/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx @@ -20,5 +20,7 @@ Spans are instrumented for the following operations within a transaction: - includes common database systems such as Postgres and MySQL - Outgoing HTTP requests made with `Net::HTTP` - Redis operations +- Queue time for requests behind reverse proxies (Nginx, HAProxy, Heroku) + - Requires `X-Request-Start` header from reverse proxy Spans are only created within an existing transaction. If you're not using any of the supported frameworks, you'll need to create transactions manually. diff --git a/docs/platforms/ruby/common/tracing/instrumentation/performance-metrics.mdx b/docs/platforms/ruby/common/tracing/instrumentation/performance-metrics.mdx index 8955adc9646099..ab8c95a9b51ee3 100644 --- a/docs/platforms/ruby/common/tracing/instrumentation/performance-metrics.mdx +++ b/docs/platforms/ruby/common/tracing/instrumentation/performance-metrics.mdx @@ -22,6 +22,8 @@ Sentry supports adding arbitrary custom units, but we recommend using one of the + + ## Supported Measurement Units Units augment measurement values by giving meaning to what otherwise might be abstract numbers. Adding units also allows Sentry to offer controls - unit conversions, filters, and so on - based on those units. For values that are unitless, you can supply an empty string or `none`. diff --git a/platform-includes/performance/queue-time-capture/ruby.mdx b/platform-includes/performance/queue-time-capture/ruby.mdx new file mode 100644 index 00000000000000..8385fa823c7396 --- /dev/null +++ b/platform-includes/performance/queue-time-capture/ruby.mdx @@ -0,0 +1,50 @@ +## Automatic Queue Time Capture + +The Ruby SDK automatically captures queue time for Rack-based applications when the `X-Request-Start` header is present. This measures how long requests wait in the web server queue (e.g., waiting for a Puma thread) before your application begins processing them. + +Queue time is attached to transactions as `http.queue_time_ms` and helps identify server capacity issues. + +### Setup + +Configure your reverse proxy to add the `X-Request-Start` header: + +**Nginx:** + +```nginx +location / { + proxy_pass http://your-app; + proxy_set_header X-Request-Start "t=${msec}"; +} +``` + +**HAProxy:** + +```haproxy +frontend http-in + http-request set-header X-Request-Start t=%Ts%ms +``` + +**Heroku:** The header is automatically set by Heroku's router. + +### How It Works + +The SDK: + +1. Reads the `X-Request-Start` header timestamp from your reverse proxy +2. Calculates the time difference between the header timestamp and when the request reaches your application +3. Subtracts `puma.request_body_wait` (if present) to exclude time spent waiting for slow client uploads +4. Attaches the result as `http.queue_time_ms` to the transaction + +### Disable Queue Time Capture + +If you don't want queue time captured, disable it in your configuration: + +```ruby +Sentry.init do |config| + config.capture_queue_time = false +end +``` + +### Viewing Queue Time + +Queue time appears in the Sentry transaction details under the "Data" section as `http.queue_time_ms` (measured in milliseconds). From 340aa909f29e8727aa5cc7d067da9cd8f572d792 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:51:19 +0000 Subject: [PATCH 13/19] [getsentry/action-github-commit] Auto commit --- src/mdx.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mdx.ts b/src/mdx.ts index cba6fdf0dff32d..d94cb2a664a618 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -281,7 +281,7 @@ export async function getDevDocsFrontMatterUncached(): Promise { const metadata = getGitMetadata(sourcePath); // Ensure we create a completely new object to avoid any reference sharing gitMetadata = metadata ? {...metadata} : undefined; - + // Log during build to debug Vercel issues if (process.env.CI || process.env.VERCEL) { console.log(`[BUILD] Git metadata for ${sourcePath}:`, gitMetadata); From d421c309ade72c52aa953414ab697ce88913ef13 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:51:49 +0000 Subject: [PATCH 14/19] [getsentry/action-github-commit] Auto commit From 72c0502169e91ad067b58274e97cdacc15ea2eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Fri, 16 Jan 2026 13:55:44 +0100 Subject: [PATCH 15/19] remove unrelated code --- app/[[...path]]/page.tsx | 17 +---- src/components/docPage/index.tsx | 5 -- src/components/lastUpdated/index.tsx | 101 --------------------------- src/mdx.ts | 22 +----- src/types/frontmatter.ts | 8 --- src/utils/getGitMetadata.ts | 70 ------------------- 6 files changed, 2 insertions(+), 221 deletions(-) delete mode 100644 src/components/lastUpdated/index.tsx delete mode 100644 src/utils/getGitMetadata.ts diff --git a/app/[[...path]]/page.tsx b/app/[[...path]]/page.tsx index 0e47344541bc37..9a17e9433d0956 100644 --- a/app/[[...path]]/page.tsx +++ b/app/[[...path]]/page.tsx @@ -131,21 +131,6 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) } const {mdxSource, frontMatter} = doc; - // Fetch git metadata on-demand for this page only (faster in dev mode) - let gitMetadata = pageNode.frontmatter.gitMetadata; - if (!gitMetadata && pageNode.frontmatter.sourcePath?.startsWith('develop-docs/')) { - // In dev mode or if not cached, fetch git metadata for current page only - const {getGitMetadata} = await import('sentry-docs/utils/getGitMetadata'); - const metadata = getGitMetadata(pageNode.frontmatter.sourcePath); - gitMetadata = metadata ?? undefined; - } - - // Merge gitMetadata into frontMatter - const frontMatterWithGit = { - ...frontMatter, - gitMetadata, - }; - // pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc const pageType = (params.path?.[0] as PageType) || 'unknown'; return ( @@ -153,7 +138,7 @@ export default async function Page(props: {params: Promise<{path?: string[]}>}) diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx index 3e9171a864f26c..9390875a78d018 100644 --- a/src/components/docPage/index.tsx +++ b/src/components/docPage/index.tsx @@ -16,7 +16,6 @@ import {CopyMarkdownButton} from '../copyMarkdownButton'; import {DocFeedback} from '../docFeedback'; import {GitHubCTA} from '../githubCTA'; import {Header} from '../header'; -import {LastUpdated} from '../lastUpdated'; import Mermaid from '../mermaid'; import {PaginationNav} from '../paginationNav'; import {PlatformSdkDetail} from '../platformSdkDetail'; @@ -95,10 +94,6 @@ export function DocPage({

{frontMatter.title}

- {/* Show last updated info for develop-docs pages */} - {frontMatter.gitMetadata && ( - - )}

{frontMatter.description}

{/* This exact id is important for Algolia indexing */} diff --git a/src/components/lastUpdated/index.tsx b/src/components/lastUpdated/index.tsx deleted file mode 100644 index e076aa679377b2..00000000000000 --- a/src/components/lastUpdated/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -'use client'; - -import Link from 'next/link'; - -interface GitMetadata { - author: string; - commitHash: string; - timestamp: number; -} - -interface LastUpdatedProps { - gitMetadata: GitMetadata; -} - -/** - * Format a timestamp as a relative time string (e.g., "2 days ago") - */ -function formatRelativeTime(timestamp: number): string { - const now = Date.now(); - const diff = now - timestamp * 1000; // timestamp is in seconds - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - const months = Math.floor(days / 30); - const years = Math.floor(days / 365); - - if (years > 0) { - return years === 1 ? '1 year ago' : `${years} years ago`; - } - if (months > 0) { - return months === 1 ? '1 month ago' : `${months} months ago`; - } - if (days > 0) { - return days === 1 ? '1 day ago' : `${days} days ago`; - } - if (hours > 0) { - return hours === 1 ? '1 hour ago' : `${hours} hours ago`; - } - if (minutes > 0) { - return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`; - } - return 'just now'; -} - -/** - * Format a timestamp as a full date string for tooltip - */ -function formatFullDate(timestamp: number): string { - const date = new Date(timestamp * 1000); - return date.toLocaleString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: '2-digit', - hour12: true, - }); -} - -/** - * Abbreviate a commit hash to first 7 characters - */ -function abbreviateHash(hash: string): string { - return hash.substring(0, 7); -} - -export function LastUpdated({gitMetadata}: LastUpdatedProps) { - const {commitHash, author, timestamp} = gitMetadata; - const relativeTime = formatRelativeTime(timestamp); - const fullDate = formatFullDate(timestamp); - const abbreviatedHash = abbreviateHash(commitHash); - const commitUrl = `https://github.com/getsentry/sentry-docs/commit/${commitHash}`; - - return ( -
- {/* Text content */} - - updated by - {author} - {/* Relative time with tooltip */} - - {relativeTime} - - - - {/* Commit link */} - - - - #{abbreviatedHash} - - -
- ); -} diff --git a/src/mdx.ts b/src/mdx.ts index d94cb2a664a618..af0374a87ebf78 100644 --- a/src/mdx.ts +++ b/src/mdx.ts @@ -268,31 +268,11 @@ export async function getDevDocsFrontMatterUncached(): Promise { const source = await readFile(file, 'utf8'); const {data: frontmatter} = matter(source); - const sourcePath = path.join(folder, fileName); - - // In production builds, fetch git metadata for develop-docs pages only - // In development, skip this and fetch on-demand per page (faster dev server startup) - let gitMetadata: typeof frontmatter.gitMetadata = undefined; - if ( - process.env.NODE_ENV !== 'development' && - sourcePath.startsWith('develop-docs/') - ) { - const {getGitMetadata} = await import('./utils/getGitMetadata'); - const metadata = getGitMetadata(sourcePath); - // Ensure we create a completely new object to avoid any reference sharing - gitMetadata = metadata ? {...metadata} : undefined; - - // Log during build to debug Vercel issues - if (process.env.CI || process.env.VERCEL) { - console.log(`[BUILD] Git metadata for ${sourcePath}:`, gitMetadata); - } - } return { ...(frontmatter as FrontMatter), slug: fileName.replace(/\/index.mdx?$/, '').replace(/\.mdx?$/, ''), - sourcePath, - gitMetadata, + sourcePath: path.join(folder, fileName), }; }, {concurrency: FILE_CONCURRENCY_LIMIT} diff --git a/src/types/frontmatter.ts b/src/types/frontmatter.ts index 0791839148e894..aed7608e1ec619 100644 --- a/src/types/frontmatter.ts +++ b/src/types/frontmatter.ts @@ -35,14 +35,6 @@ export interface FrontMatter { */ fullWidth?: boolean; - /** - * Git metadata for the last commit & author that modified this file - */ - gitMetadata?: { - author: string; - commitHash: string; - timestamp: number; - }; /** * A list of keywords for indexing with search. */ diff --git a/src/utils/getGitMetadata.ts b/src/utils/getGitMetadata.ts deleted file mode 100644 index 07b3688227be4e..00000000000000 --- a/src/utils/getGitMetadata.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {execSync} from 'child_process'; -import path from 'path'; - -export interface GitMetadata { - author: string; - commitHash: string; - timestamp: number; -} - -// Cache to avoid repeated git calls during build -const gitMetadataCache = new Map(); - -/** - * Get git metadata for a file - * @param filePath - Path to the file relative to the repository root - * @returns Git metadata or null if unavailable - */ -export function getGitMetadata(filePath: string): GitMetadata | null { - // Check cache first - if (gitMetadataCache.has(filePath)) { - const cached = gitMetadataCache.get(filePath); - // Return a NEW copy to avoid reference sharing - return cached ? {...cached} : null; - } - - try { - // Get commit hash, author name, and timestamp - const logOutput = execSync(`git log -1 --format="%H|%an|%at" -- "${filePath}"`, { - encoding: 'utf8', - cwd: path.resolve(process.cwd()), - stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr - }).trim(); - - // Log for debugging on Vercel - if (process.env.CI || process.env.VERCEL) { - console.log(`[getGitMetadata] File: ${filePath} -> Output: ${logOutput}`); - } - - if (!logOutput) { - // No commits found for this file - gitMetadataCache.set(filePath, null); - return null; - } - - const [commitHash, author, timestampStr] = logOutput.split('|'); - const timestamp = parseInt(timestampStr, 10); - - // Create a fresh object for each call to avoid reference sharing - const metadata: GitMetadata = { - commitHash, - author, - timestamp, - }; - - // Cache the metadata - gitMetadataCache.set(filePath, metadata); - - // IMPORTANT: Return a NEW object, not the cached one - // This prevents all pages from sharing the same object reference - return { - commitHash: metadata.commitHash, - author: metadata.author, - timestamp: metadata.timestamp, - }; - } catch (error) { - // Git command failed or file doesn't exist in git - gitMetadataCache.set(filePath, null); - return null; - } -} From 9dec483e7d26dd769dab8c4aac868b2341027915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= <114897+dingsdax@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:44:22 +0100 Subject: [PATCH 16/19] Apply suggestions from code review Co-authored-by: Alex Krawiec --- docs/platforms/ruby/common/configuration/options.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/platforms/ruby/common/configuration/options.mdx b/docs/platforms/ruby/common/configuration/options.mdx index 9767dbd1316efb..23cd01e5c980b7 100644 --- a/docs/platforms/ruby/common/configuration/options.mdx +++ b/docs/platforms/ruby/common/configuration/options.mdx @@ -330,7 +330,7 @@ config.trace_ignore_status_codes = [404, (502..511)] Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.queue_time_ms`. -This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. +This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under high load. To disable queue time capture: From f7ddb6f67087eea46ad6d6e2435a6292cde6ba6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Fri, 23 Jan 2026 15:20:29 +0100 Subject: [PATCH 17/19] Fix attribute name, simplify options --- .../ruby/common/configuration/options.mdx | 16 ++-------------- .../performance/queue-time-capture/ruby.mdx | 6 +++--- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/docs/platforms/ruby/common/configuration/options.mdx b/docs/platforms/ruby/common/configuration/options.mdx index 23cd01e5c980b7..fe7819fa04dcf3 100644 --- a/docs/platforms/ruby/common/configuration/options.mdx +++ b/docs/platforms/ruby/common/configuration/options.mdx @@ -328,9 +328,9 @@ config.trace_ignore_status_codes = [404, (502..511)] -Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.queue_time_ms`. +Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.server.request.time_in_queue`. -This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under high load. +This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. Learn more about [automatic queue time capture](/tracing/instrumentation/performance-metrics/#automatic-queue-time-capture). To disable queue time capture: @@ -338,18 +338,6 @@ To disable queue time capture: config.capture_queue_time = false ``` -**Nginx:** - -```nginx -proxy_set_header X-Request-Start "t=${msec}"; -``` - -**HAProxy:** - -```haproxy -http-request set-header X-Request-Start t=%Ts%ms -``` - diff --git a/platform-includes/performance/queue-time-capture/ruby.mdx b/platform-includes/performance/queue-time-capture/ruby.mdx index 8385fa823c7396..3548f29b215166 100644 --- a/platform-includes/performance/queue-time-capture/ruby.mdx +++ b/platform-includes/performance/queue-time-capture/ruby.mdx @@ -2,7 +2,7 @@ The Ruby SDK automatically captures queue time for Rack-based applications when the `X-Request-Start` header is present. This measures how long requests wait in the web server queue (e.g., waiting for a Puma thread) before your application begins processing them. -Queue time is attached to transactions as `http.queue_time_ms` and helps identify server capacity issues. +Queue time is attached to transactions as `http.server.request.time_in_queue` and helps identify server capacity issues. ### Setup @@ -33,7 +33,7 @@ The SDK: 1. Reads the `X-Request-Start` header timestamp from your reverse proxy 2. Calculates the time difference between the header timestamp and when the request reaches your application 3. Subtracts `puma.request_body_wait` (if present) to exclude time spent waiting for slow client uploads -4. Attaches the result as `http.queue_time_ms` to the transaction +4. Attaches the result as `http.server.request.time_in_queue` to the transaction ### Disable Queue Time Capture @@ -47,4 +47,4 @@ end ### Viewing Queue Time -Queue time appears in the Sentry transaction details under the "Data" section as `http.queue_time_ms` (measured in milliseconds). +Queue time appears in the Sentry transaction details under the "Data" section as `http.server.request.time_in_queue` (measured in milliseconds). From 100bfc5c4408ea1e5e5c453a312acb6da8180eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Tue, 24 Feb 2026 14:17:24 +0100 Subject: [PATCH 18/19] docs(ruby): Review and fix queue time capture docs Fix a broken relative link in options.mdx that would 404 in production, correct the automatic-instrumentation page to reflect that queue time is transaction data (not a child span), and add a note that tracing must be enabled. Remove redundant "Viewing Queue Time" section. Co-Authored-By: Claude --- docs/platforms/ruby/common/configuration/options.mdx | 2 +- .../tracing/instrumentation/automatic-instrumentation.mdx | 4 ++-- platform-includes/performance/queue-time-capture/ruby.mdx | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/platforms/ruby/common/configuration/options.mdx b/docs/platforms/ruby/common/configuration/options.mdx index fe7819fa04dcf3..59cedd46ba77d4 100644 --- a/docs/platforms/ruby/common/configuration/options.mdx +++ b/docs/platforms/ruby/common/configuration/options.mdx @@ -330,7 +330,7 @@ config.trace_ignore_status_codes = [404, (502..511)] Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.server.request.time_in_queue`. -This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. Learn more about [automatic queue time capture](/tracing/instrumentation/performance-metrics/#automatic-queue-time-capture). +This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. Learn more about [automatic queue time capture](../../tracing/instrumentation/performance-metrics/#automatic-queue-time-capture). To disable queue time capture: diff --git a/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx index 969849a219443c..4b510f2712a4a4 100644 --- a/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx +++ b/docs/platforms/ruby/common/tracing/instrumentation/automatic-instrumentation.mdx @@ -20,7 +20,7 @@ Spans are instrumented for the following operations within a transaction: - includes common database systems such as Postgres and MySQL - Outgoing HTTP requests made with `Net::HTTP` - Redis operations -- Queue time for requests behind reverse proxies (Nginx, HAProxy, Heroku) - - Requires `X-Request-Start` header from reverse proxy + +The SDK also captures **queue time** as transaction data (not a child span) when a `X-Request-Start` header is present from your reverse proxy (Nginx, HAProxy, or Heroku). See Automatic Queue Time Capture for setup instructions. Spans are only created within an existing transaction. If you're not using any of the supported frameworks, you'll need to create transactions manually. diff --git a/platform-includes/performance/queue-time-capture/ruby.mdx b/platform-includes/performance/queue-time-capture/ruby.mdx index 3548f29b215166..799e33464d2dfb 100644 --- a/platform-includes/performance/queue-time-capture/ruby.mdx +++ b/platform-includes/performance/queue-time-capture/ruby.mdx @@ -2,7 +2,7 @@ The Ruby SDK automatically captures queue time for Rack-based applications when the `X-Request-Start` header is present. This measures how long requests wait in the web server queue (e.g., waiting for a Puma thread) before your application begins processing them. -Queue time is attached to transactions as `http.server.request.time_in_queue` and helps identify server capacity issues. +Queue time is attached to transactions as the `http.server.request.time_in_queue` attribute and helps identify server capacity issues. Tracing must be enabled for queue time to be captured. ### Setup @@ -44,7 +44,3 @@ Sentry.init do |config| config.capture_queue_time = false end ``` - -### Viewing Queue Time - -Queue time appears in the Sentry transaction details under the "Data" section as `http.server.request.time_in_queue` (measured in milliseconds). From 5894dc4ffe6c637d172f7634451b6fdf6740ec9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Daxb=C3=B6ck?= Date: Tue, 24 Feb 2026 21:26:24 +0100 Subject: [PATCH 19/19] fix(ruby): Use PlatformLink for queue time capture reference in options Replace raw relative link with PlatformLink component to fix 404. The relative path resolved incorrectly because the common/ directory is stripped from URLs. Co-Authored-By: Claude --- docs/platforms/ruby/common/configuration/options.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/platforms/ruby/common/configuration/options.mdx b/docs/platforms/ruby/common/configuration/options.mdx index 59cedd46ba77d4..a53d63cd9f20c6 100644 --- a/docs/platforms/ruby/common/configuration/options.mdx +++ b/docs/platforms/ruby/common/configuration/options.mdx @@ -330,7 +330,7 @@ config.trace_ignore_status_codes = [404, (502..511)] Automatically capture how long requests wait in the web server queue before processing begins. The SDK reads the `X-Request-Start` header set by reverse proxies (Nginx, HAProxy, Heroku) and attaches queue time to transactions as `http.server.request.time_in_queue`. -This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. Learn more about [automatic queue time capture](../../tracing/instrumentation/performance-metrics/#automatic-queue-time-capture). +This helps identify when requests are delayed due to insufficient worker threads or server capacity, which is especially useful under load. Learn more about automatic queue time capture. To disable queue time capture: @@ -480,7 +480,7 @@ end -Whether to also capture events and traces into [Spotlight](https://spotlightjs.com/setup/other/). +Whether to also capture events and traces into [Spotlight](https://spotlightjs.com/). If you set this to true, Sentry will send events and traces to the local Sidecar proxy at `http://localhost:8969/stream`.