From efb7b9b6dbb686827a288fae9a9477964fe6a3a4 Mon Sep 17 00:00:00 2001 From: Jon McCallum <66999846+jonmcwest@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:51:31 +0100 Subject: [PATCH 1/3] fix(tracing): use the logs timestamp format Span timestamps rendered through toLocaleString, so a US reader saw 3/8/2026, 2:07:09 PM and a reader elsewhere saw a different order. Logs shows 2026-03-08 14:07:09.123 and converts the timestamp on hover. Render the tracing timestamps with TZLabel in UTC, on the logs format, and collect the format and timezone constants in dateFormats.ts. Generated-By: PostHog Desktop Task-Id: dbdbefb4-52bf-47b3-a940-6d76bd6251b9 --- .../tracing/frontend/TracingLatencyHeatmap.tsx | 5 +++-- .../tracing/frontend/TracingOperationScene.tsx | 13 ++++++++++--- products/tracing/frontend/TracingScene.tsx | 3 ++- products/tracing/frontend/TracingSparkline.tsx | 3 ++- .../components/Comparison/ComparisonBar.tsx | 5 +++-- .../components/TraceDrawer/SpanSummaryHeader.tsx | 16 ++++++++++++---- .../VirtualizedSpanList/VirtualizedSpanList.tsx | 15 +++++++++++++-- products/tracing/frontend/dateFormats.ts | 11 +++++++++++ products/tracing/frontend/tracingDataLogic.ts | 3 ++- 9 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 products/tracing/frontend/dateFormats.ts diff --git a/products/tracing/frontend/TracingLatencyHeatmap.tsx b/products/tracing/frontend/TracingLatencyHeatmap.tsx index e038cd8a9207..72683f77b71f 100644 --- a/products/tracing/frontend/TracingLatencyHeatmap.tsx +++ b/products/tracing/frontend/TracingLatencyHeatmap.tsx @@ -6,6 +6,7 @@ import { Heatmap, type HeatmapBrushData, useChartTheme } from '@posthog/quill-ch import { dayjs } from 'lib/dayjs' import { shortTimeZone } from 'lib/utils/timezones' +import { TRACING_DATE_TIME_FORMAT } from './dateFormats' import type { TracingLatencyHeatmapData } from './durationBuckets' const MAX_X_TICKS = 6 @@ -38,7 +39,7 @@ export function TracingLatencyHeatmap({ } else if (hoursDiff <= 48) { return 'HH:mm' } - return 'D MMM HH:mm' + return 'MM-DD HH:mm' }, [data.timeBuckets]) const tickStep = Math.max(1, Math.ceil(data.timeBuckets.length / MAX_X_TICKS)) @@ -57,7 +58,7 @@ export function TracingLatencyHeatmap({ (label: string): string => { const d = displayTimezone ? dayjs(label).tz(displayTimezone) : dayjs(label) const tz = displayTimezone === 'UTC' ? 'UTC' : (shortTimeZone(displayTimezone, d.toDate()) ?? 'Local') - return `${d.format('D MMM YYYY HH:mm:ss')} ${tz}` + return `${d.format(TRACING_DATE_TIME_FORMAT)} ${tz}` }, [displayTimezone] ) diff --git a/products/tracing/frontend/TracingOperationScene.tsx b/products/tracing/frontend/TracingOperationScene.tsx index b65091639645..bf1c0ab9c9e0 100644 --- a/products/tracing/frontend/TracingOperationScene.tsx +++ b/products/tracing/frontend/TracingOperationScene.tsx @@ -4,7 +4,7 @@ import { IconChevronLeft, IconChevronRight } from '@posthog/icons' import { LemonButton, LemonSegmentedButton, LemonTag, Link, SpinnerOverlay } from '@posthog/lemon-ui' import { DateFilter } from 'lib/components/DateFilter/DateFilter' -import { humanFriendlyDetailedTime } from 'lib/utils/datetime' +import { TZLabel } from 'lib/components/TZLabel' import { humanFriendlyNumber } from 'lib/utils/numbers' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' @@ -14,6 +14,7 @@ import { SceneDivider } from '~/layout/scenes/components/SceneDivider' import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection' import { ProductKey } from '~/queries/schema/schema-general' +import { TRACING_DATE_FORMAT, TRACING_DISPLAY_TIMEZONE, TRACING_TIME_FORMAT } from './dateFormats' import { formatBucketLabel } from './durationBuckets' import { OperationHistogram } from './OperationHistogram' import { errorRate, formatErrorRate } from './OperationsTable' @@ -162,7 +163,7 @@ export function TracingOperationScene(): JSX.Element { @@ -216,7 +217,13 @@ export function TracingOperationScene(): JSX.Element { /> {currentSample && (
- {humanFriendlyDetailedTime(currentSample.timestamp)} + {formatDuration(currentSample.duration_nano)} {currentSample.status_code === 2 && Error}
diff --git a/products/tracing/frontend/TracingScene.tsx b/products/tracing/frontend/TracingScene.tsx index cb520e66e964..c94aaa780af0 100644 --- a/products/tracing/frontend/TracingScene.tsx +++ b/products/tracing/frontend/TracingScene.tsx @@ -21,6 +21,7 @@ import { ComparisonBar } from './components/Comparison/ComparisonBar' import { FacetRail } from './components/FacetRail/FacetRail' import { TraceDrawer } from './components/TraceDrawer/TraceDrawer' import { VirtualizedSpanList } from './components/VirtualizedSpanList/VirtualizedSpanList' +import { TRACING_DISPLAY_TIMEZONE } from './dateFormats' import { tracingEmptyState } from './emptyState/tracingEmptyState' import { OperationsTable } from './OperationsTable' import { TraceCompareFlame } from './TraceCompareFlame' @@ -203,7 +204,7 @@ function TracingSceneContents(): JSX.Element { sparklineData={sparklineData} sparklineLoading={sparklineLoading || (isDurationMode && !showHeatmap && durationHistogramLoading)} onDateRangeChange={setDateRange} - displayTimezone="UTC" + displayTimezone={TRACING_DISPLAY_TIMEZONE} compare={compareConfig} visibleRowDateRange={visibleRowDateRange} durationHistogram={isDurationMode && !showHeatmap ? durationHistogramData : null} diff --git a/products/tracing/frontend/TracingSparkline.tsx b/products/tracing/frontend/TracingSparkline.tsx index 21dcc2ad9364..2daac3be6c1a 100644 --- a/products/tracing/frontend/TracingSparkline.tsx +++ b/products/tracing/frontend/TracingSparkline.tsx @@ -25,6 +25,7 @@ import { shortTimeZone } from 'lib/utils/timezones' import { DateRange } from '~/queries/schema/schema-general' +import { TRACING_DATE_TIME_FORMAT } from './dateFormats' import { type TracingDurationHistogramData, type TracingLatencyHeatmapData, @@ -116,7 +117,7 @@ export function TracingSparkline({ (label: string): string => { const d = displayTimezone ? dayjs(label).tz(displayTimezone) : dayjs(label) const tz = displayTimezone === 'UTC' ? 'UTC' : (shortTimeZone(displayTimezone, d.toDate()) ?? 'Local') - return `${d.format('D MMM YYYY HH:mm:ss')} ${tz}` + return `${d.format(TRACING_DATE_TIME_FORMAT)} ${tz}` }, [displayTimezone] ) diff --git a/products/tracing/frontend/components/Comparison/ComparisonBar.tsx b/products/tracing/frontend/components/Comparison/ComparisonBar.tsx index 3ee1e9de6dcc..493605af6825 100644 --- a/products/tracing/frontend/components/Comparison/ComparisonBar.tsx +++ b/products/tracing/frontend/components/Comparison/ComparisonBar.tsx @@ -5,6 +5,7 @@ import { LemonButton, LemonTag } from '@posthog/lemon-ui' import { dayjs } from 'lib/dayjs' +import { TRACING_DATE_FORMAT } from '../../dateFormats' import { COMPARE_CURRENT_BORDER, COMPARE_PREVIOUS_BORDER } from '../../SparklineCompareOverlay' import { type OverlayWindow, TIME_COMPARE_PRESET_DEFS, tracingFiltersLogic } from '../../tracingFiltersLogic' @@ -13,8 +14,8 @@ import { type OverlayWindow, TIME_COMPARE_PRESET_DEFS, tracingFiltersLogic } fro function formatComparisonWindow(window: OverlayWindow): string { const start = dayjs(window.startMs).utc() const end = dayjs(window.endMs).utc() - const endFormat = start.isSame(end, 'day') ? 'HH:mm' : 'MMM D, HH:mm' - return `${start.format('MMM D, HH:mm')} – ${end.format(endFormat)} UTC` + const endFormat = start.isSame(end, 'day') ? 'HH:mm' : `${TRACING_DATE_FORMAT} HH:mm` + return `${start.format(`${TRACING_DATE_FORMAT} HH:mm`)} – ${end.format(endFormat)} UTC` } function ComparisonPill({ color, label, detail }: { color: string; label: string; detail: string }): JSX.Element { diff --git a/products/tracing/frontend/components/TraceDrawer/SpanSummaryHeader.tsx b/products/tracing/frontend/components/TraceDrawer/SpanSummaryHeader.tsx index 2b260a3caf76..b1825fd52b90 100644 --- a/products/tracing/frontend/components/TraceDrawer/SpanSummaryHeader.tsx +++ b/products/tracing/frontend/components/TraceDrawer/SpanSummaryHeader.tsx @@ -4,8 +4,10 @@ import { LemonTag } from '@posthog/lemon-ui' import { getSeriesColor } from 'lib/colors' import { CopyToClipboardInline } from 'lib/components/CopyToClipboard' +import { TZLabel } from 'lib/components/TZLabel' import { dayjs } from 'lib/dayjs' +import { TRACING_DATE_FORMAT, TRACING_DISPLAY_TIMEZONE, TRACING_TIME_FORMAT } from '../../dateFormats' import { deriveSpanSummary } from '../../spanSummary' import { formatDuration } from '../../TraceWaterfallView' import type { Span } from '../../types' @@ -80,12 +82,18 @@ export function SpanSummaryHeader({ )} {formatDuration(summary.durationNano)} - {/* UTC to match the waterfall/sparkline (displayTimezone="UTC"). end shows time-only — - same day as start in all but pathological spans, so the date would just be noise. */} + {/* UTC to match the waterfall/sparkline. end shows time-only — same day as start in + all but pathological spans, so the date would just be noise. */} - {dayjs(summary.timestamp).tz('UTC').format('MMM D HH:mm:ss.SSS')} + {' → '} - {dayjs(summary.endTimestamp).tz('UTC').format('HH:mm:ss.SSS')} + {dayjs(summary.endTimestamp).tz(TRACING_DISPLAY_TIMEZONE).format(TRACING_TIME_FORMAT)} diff --git a/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx b/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx index a40ce23bad54..b2bb8057dd63 100644 --- a/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx +++ b/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx @@ -5,9 +5,11 @@ import { LemonTag } from '@posthog/lemon-ui' import { AutoSizer } from 'lib/components/AutoSizer' import { SizeProps } from 'lib/components/AutoSizer/AutoSizer' +import { TZLabel } from 'lib/components/TZLabel' import { SortingIndicator } from 'lib/lemon-ui/LemonTable/sorting' import { cn } from 'lib/utils/css-classes' +import { TRACING_DATE_FORMAT, TRACING_DISPLAY_TIMEZONE, TRACING_TIME_FORMAT } from '../../dateFormats' import { formatDuration } from '../../TraceWaterfallView' import type { TracingOrderBy, TracingOrderDirection } from '../../tracingFiltersLogic' import { SPAN_KIND_LABELS, STATUS_CODE_LABELS } from '../../types' @@ -25,7 +27,7 @@ const LOAD_MORE_THRESHOLD = 10 // Default column widths (px), in render order. Anyone can drag a column wider or narrower from here. const SPAN_COLUMNS: ResizableColumnSpec[] = [ - { key: 'timestamp', width: 190 }, + { key: 'timestamp', width: 215 }, { key: 'name', width: 320, grow: true }, { key: 'service', width: 200 }, { key: 'kind', width: 90 }, @@ -164,7 +166,16 @@ function SpanRow({ role="button" tabIndex={0} > - {new Date(span.timestamp).toLocaleString()} + + + + + {span.name} diff --git a/products/tracing/frontend/dateFormats.ts b/products/tracing/frontend/dateFormats.ts new file mode 100644 index 000000000000..d46b20e4c277 --- /dev/null +++ b/products/tracing/frontend/dateFormats.ts @@ -0,0 +1,11 @@ +// Tracing shows timestamps the way Logs does: an ISO-ordered date and a 24-hour clock. A +// month-first date is ambiguous outside the US. +export const TRACING_DATE_FORMAT = 'YYYY-MM-DD' +export const TRACING_TIME_FORMAT = 'HH:mm:ss.SSS' + +// Charts and range pills drop the milliseconds. A bucket edge is never sub-second. +export const TRACING_DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' + +// Every chart, axis, and timestamp in the product reads in UTC, so a span lines up with the +// sparkline above it. TZLabel converts to the reader's own timezone on hover. +export const TRACING_DISPLAY_TIMEZONE = 'UTC' diff --git a/products/tracing/frontend/tracingDataLogic.ts b/products/tracing/frontend/tracingDataLogic.ts index 1debf7c055d2..52175f0f5590 100644 --- a/products/tracing/frontend/tracingDataLogic.ts +++ b/products/tracing/frontend/tracingDataLogic.ts @@ -29,6 +29,7 @@ import { PropertyGroupFilter } from '~/types' import type { DateRange } from '../../../frontend/src/queries/schema/schema-general' import type { UniversalFiltersGroup } from '../../../frontend/src/types' +import { TRACING_DATE_FORMAT } from './dateFormats' import { type DurationHistogramRow, type LatencyHeatmapRow, @@ -1264,7 +1265,7 @@ export const tracingDataLogic = kea([ (accumulator, currentItem) => { if (currentItem.time !== lastTime) { labels.push( - humanFriendlyDetailedTime(currentItem.time, 'YYYY-MM-DD', 'HH:mm:ss', { + humanFriendlyDetailedTime(currentItem.time, TRACING_DATE_FORMAT, 'HH:mm:ss', { timestampStyle: 'absolute', }) ) From 9726309f6575f76140673b6e4f8105f409878063 Mon Sep 17 00:00:00 2001 From: Jon McCallum <66999846+jonmcwest@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:09:57 +0100 Subject: [PATCH 2/3] fix(tracing): show seconds in the span row timezone popover The span row renders seconds and milliseconds, so its hover popover has to match. Without showSeconds the conversion stopped at the minute, unlike the sample picker and the drawer header. Generated-By: PostHog Desktop Task-Id: f91f83fa-3425-44d7-b290-3df28097e4d6 --- .../components/VirtualizedSpanList/VirtualizedSpanList.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx b/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx index b2bb8057dd63..fc33da1f0dab 100644 --- a/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx +++ b/products/tracing/frontend/components/VirtualizedSpanList/VirtualizedSpanList.tsx @@ -173,6 +173,7 @@ function SpanRow({ formatDate={TRACING_DATE_FORMAT} formatTime={TRACING_TIME_FORMAT} displayTimezone={TRACING_DISPLAY_TIMEZONE} + showSeconds /> From 956579c859002136c614910c79a072ab21df21fa Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:50:57 +0000 Subject: [PATCH 3/3] chore(visual): update storybook baselines 2 updated Run: 68f06fd3-9a1e-45c2-8d58-292cf762bac6 Co-authored-by: jonmcwest <66999846+jonmcwest@users.noreply.github.com> --- frontend/snapshots.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/snapshots.yml b/frontend/snapshots.yml index de63cf19d27d..40b9ddfd540e 100644 --- a/frontend/snapshots.yml +++ b/frontend/snapshots.yml @@ -5453,9 +5453,9 @@ snapshots: products-tracing-operationstable--loading--light: hash: v1.k794b7964.04bcc55cb9522d72f548084a92d35c0aba20ff4995949bd8b2ee8686f96cb6ac.P_KhZ-T4DKSiE8XyxMJSgUEUR9bDfDvgYnGqSOiR860 products-tracing-virtualizedspanlist--default--dark: - hash: v1.k794b7964.2d26cb574fb8b9011fcf44c766f491adb06c254b9bc1bb70fa1a14bf757eb72d.HK_kiCD_GlyKPWUd6GcCvJJ5tVWaz6t45kZwDWUYg_4 + hash: v1.k794b7964.d6cf5ccbc379f67404b237b878878ce7e60512d45869c05c24dfab7784335406.zEh7cw_wZ10BGEuJkOUdjqS8SiWt7386-Cuz4Qc0Les products-tracing-virtualizedspanlist--default--light: - hash: v1.k794b7964.21b6c26c19954d60a3447075f70b9c9dcfb5f6032f94c85fa223f728ee02bf1c.dAAs5oIhp4WcsPd9uA5wTgaPWyEeuf7PV46c6WFv-9g + hash: v1.k794b7964.7ec7e24fc514e8eac101ce26ef491c0becbbe2cc6aeb8759c1c451b5977e6aa7.cP3_uVsCx_-JFlTOLhjShsAIyaSC9JRkJ4Rnlgog0Kk products-tracing-virtualizedspanlist--empty--dark: hash: v1.k794b7964.5590e8e6497de2fefefa74cf07edd5f814a798deb8f7e5f0102a383f5e8ac6c8.ZvuooUS3qT6kDX8yW2_oQ1iDgO1DwVFMAWM517bhCHw products-tracing-virtualizedspanlist--empty--light: