Skip to content

Commit

Permalink
Limit focused events to session-captured trace IDs (#217)
Browse files Browse the repository at this point in the history
Adjust the behavior of Traces and Errors to show events which are associated with a known-local trace ID. If no trace IDs are known, assume all events should be present instead.
  • Loading branch information
dcramer authored Dec 5, 2023
1 parent f6db31c commit 66f31fa
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 62 deletions.
7 changes: 7 additions & 0 deletions packages/overlay/src/components/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type ComponentProps } from 'react';

export default function Badge(props: Omit<ComponentProps<'span'>, 'className'>) {
return (
<span className="bg-primary-800 inline-flex items-center rounded-md px-1.5 py-0.5 text-xs font-medium" {...props} />
);
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fragment } from 'react';
import classNames from '~/lib/classNames';
import Time from '../../../components/Time';
import { Breadcrumb, SentryEvent } from '../types';
import Time from './Time';

const EXAMPLE_BREADCRUMB = `Sentry.addBreadcrumb({
category: "auth",
Expand Down
34 changes: 31 additions & 3 deletions packages/overlay/src/integrations/sentry/components/EventList.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import Badge from '~/components/Badge';
import CardList from '~/components/CardList';
import TimeSince from '../../../components/TimeSince';
import { useSentryEvents } from '../data/useSentryEvents';
import { useSentryHelpers } from '../data/useSentryHelpers';
import { SentryEvent } from '../types';
import { EventSummary } from './Events/Event';
import HiddenItemsButton from './HiddenItemsButton';
import PlatformIcon from './PlatformIcon';
import TimeSince from './TimeSince';

function renderEvent(event: SentryEvent) {
return <EventSummary event={event} />;
}

export default function EventList() {
const events = useSentryEvents();
const helpers = useSentryHelpers();

const matchingEvents = events.filter(e => e.type !== 'transaction');

const [showAll, setShowAll] = useState(false);
const filteredEvents = showAll
? matchingEvents
: matchingEvents.filter(
e => (e.contexts?.trace?.trace_id ? helpers.isLocalToSession(e.contexts?.trace?.trace_id) : null) !== false,
);
const hiddenItemCount = matchingEvents.length - filteredEvents.length;

return matchingEvents.length !== 0 ? (
<CardList>
{matchingEvents.map(e => {
{hiddenItemCount > 0 && (
<HiddenItemsButton
itemCount={hiddenItemCount}
onClick={() => {
setShowAll(true);
}}
/>
)}
{filteredEvents.map(e => {
const traceId = e.contexts?.trace?.trace_id;
return (
<Link
className="hover:bg-primary-900 flex cursor-pointer items-center gap-x-4 px-6 py-2"
Expand All @@ -26,7 +48,13 @@ export default function EventList() {
>
<PlatformIcon event={e} className="text-primary-300" />
<div className="text-primary-300 flex w-48 flex-col truncate font-mono text-sm">
<span>{(e.event_id || '').substring(0, 8)}</span>
<div className="flex items-center gap-x-2">
<div>{(e.event_id || '').substring(0, 8)}</div>
{traceId && helpers.isLocalToSession(traceId) ? (
<Badge title="This event is part of your local session.">Local</Badge>
) : null}
</div>
<span></span>
<TimeSince date={e.timestamp} />
</div>
<div className="flex-1">{renderEvent(e)}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type ComponentProps } from 'react';

export default function HiddenItemsButton({
itemCount,
...props
}: Omit<ComponentProps<'button'>, 'className' | 'children'> & {
itemCount: number;
}) {
return (
<button
className="bg-primary-900 hover:bg-primary-800 text-primary-200 flex w-full cursor-pointer items-center gap-x-4 px-6 py-2 text-sm"
{...props}
>
<strong>
{itemCount.toLocaleString()} {itemCount !== 1 ? 'items were' : 'item was'} hidden from different sessions.
</strong>
<button className="hover:bg-primary-900 border-primary-500 rounded border px-1.5 py-0.5">Reveal</button>
</button>
);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import CardList from '~/components/CardList';
import TimeSince from '../../../components/TimeSince';
import { useSentrySdks } from '../data/useSentrySdks';
import { sdkToPlatform } from '../utils/sdkToPlatform';
import PlatformIcon from './PlatformIcon';
import TimeSince from './TimeSince';

export default function SdkList() {
const sdkList = useSentrySdks();
Expand Down
28 changes: 25 additions & 3 deletions packages/overlay/src/integrations/sentry/components/TraceList.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import CardList from '~/components/CardList';
import Badge from '../../../components/Badge';
import TimeSince from '../../../components/TimeSince';
import classNames from '../../../lib/classNames';
import { useSentryHelpers } from '../data/useSentryHelpers';
import { useSentryTraces } from '../data/useSentryTraces';
import { getDuration } from '../utils/duration';
import HiddenItemsButton from './HiddenItemsButton';
import PlatformIcon from './PlatformIcon';
import TimeSince from './TimeSince';

export default function TraceList() {
const traceList = useSentryTraces();
const helpers = useSentryHelpers();

const [showAll, setShowAll] = useState(false);
const filteredTraces = showAll ? traceList : traceList.filter(t => helpers.isLocalToSession(t.trace_id) !== false);
const hiddenItemCount = traceList.length - filteredTraces.length;

return (
<>
{traceList.length !== 0 ? (
<CardList>
{traceList.map(trace => {
{hiddenItemCount > 0 && (
<HiddenItemsButton
itemCount={hiddenItemCount}
onClick={() => {
setShowAll(true);
}}
/>
)}
{filteredTraces.map(trace => {
const duration = getDuration(trace.start_timestamp, trace.timestamp);
return (
<Link
Expand All @@ -24,7 +41,12 @@ export default function TraceList() {
<PlatformIcon platform={trace.rootTransaction?.platform} />

<div className="text-primary-300 flex w-48 flex-col truncate font-mono text-sm">
<div>{trace.trace_id.substring(0, 8)}</div>
<div className="flex items-center gap-x-2">
<div>{trace.trace_id.substring(0, 8)}</div>
{helpers.isLocalToSession(trace.trace_id) ? (
<Badge title="This trace is part of your local session.">Local</Badge>
) : null}
</div>
<TimeSince date={trace.start_timestamp} />
</div>
<div className="flex flex-col truncate font-mono">
Expand Down
25 changes: 24 additions & 1 deletion packages/overlay/src/integrations/sentry/data/sentryDataCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class SentryDataCache {
protected sdks: Sdk[] = [];
protected traces: Trace[] = [];
protected tracesById: { [id: string]: Trace } = {};
protected localTraceIds: Set<string> = new Set<string>();

protected subscribers: Map<string, Subscription> = new Map();

Expand Down Expand Up @@ -99,9 +100,10 @@ class SentryDataCache {
event.timestamp = toTimestamp(event.timestamp);
if (event.start_timestamp) event.start_timestamp = toTimestamp(event.start_timestamp);

const traceCtx = event.contexts?.trace;

this.events.push(event);

const traceCtx = event.contexts?.trace;
if (traceCtx) {
const existingTrace = this.tracesById[traceCtx.trace_id];
const startTs = event.start_timestamp ? event.start_timestamp : new Date().getTime();
Expand Down Expand Up @@ -206,6 +208,27 @@ class SentryDataCache {
this.subscribers.delete(id);
};
}

/**
* Mark a traceId as being seen in the local session.
*
* @param traceId
*/
trackLocalTrace(traceId: string) {
this.localTraceIds.add(traceId);
}

/**
* Determine if a traceId was seen in the local session.
*
* A result of `null` means "unknown", which implies there is no known
* information about any session-initiated traces.
*/
isTraceLocal(traceId: string): boolean | null {
if (this.localTraceIds.has(traceId)) return true;
if (this.localTraceIds.size > 0) return false;
return null;
}
}

export default new SentryDataCache();
Expand Down
13 changes: 13 additions & 0 deletions packages/overlay/src/integrations/sentry/data/useSentryHelpers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from 'react';
import sentryDataCache from './sentryDataCache';
import { SentryEventsContext } from './sentryEventsContext';

export const useSentryHelpers = () => {
useContext(SentryEventsContext);

return {
isLocalToSession: (traceId: string) => {
return sentryDataCache.isTraceLocal(traceId);
},
};
};
10 changes: 8 additions & 2 deletions packages/overlay/src/integrations/sentry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ export default function sentryIntegration(options?: SentryIntegrationOptions) {
processEvent: (event: RawEventContext) => processEnvelope(event),

tabs: () => {
const errorsCount = sentryDataCache.getEvents().filter(e => e.type != 'transaction').length;
const errorsCount = sentryDataCache
.getEvents()
.filter(
e =>
e.type != 'transaction' &&
(e.contexts?.trace?.trace_id ? sentryDataCache.isTraceLocal(e.contexts?.trace?.trace_id) : null) !== false,
).length;

return [
{
Expand All @@ -57,7 +63,7 @@ export default function sentryIntegration(options?: SentryIntegrationOptions) {
id: 'traces',
title: 'Traces',
notificationCount: {
count: sentryDataCache.getTraces().length,
count: sentryDataCache.getTraces().filter(t => sentryDataCache.isTraceLocal(t.trace_id) !== false).length,
},
content: TracesTab,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Client, Envelope, Event, EventProcessor, Hub, Integration } from '@sentry/types';
import { serializeEnvelope } from '@sentry/utils';
import { log } from '../../lib/logger';
import sentryDataCache from './data/sentryDataCache';

type SpotlightBrowserIntegationOptions = {
/**
Expand All @@ -23,6 +24,11 @@ export class Spotlight implements Integration {

public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
addGlobalEventProcessor(async (event: Event) => {
const traceId = event.contexts?.trace?.trace_id;
if (traceId) {
sentryDataCache.trackLocalTrace(traceId);
}

if (event.type || !event.exception || !event.exception.values) {
return event;
}
Expand Down
Loading

0 comments on commit 66f31fa

Please sign in to comment.