Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/heavy-berries-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spotlightjs/overlay': minor
---

Add support for message events and transactions without spans
3 changes: 3 additions & 0 deletions packages/overlay/_fixtures/envelope_java.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"event_id":"dad187904fee477e9aa75429a1eda314","sdk":{"name":"sentry.java.spring-boot.jakarta","version":"7.3.0","packages":[{"name":"maven:io.sentry:sentry","version":"7.3.0"},{"name":"maven:io.sentry:sentry-spring-boot-starter-jakarta","version":"7.3.0"},{"name":"maven:io.sentry:sentry-jdbc","version":"7.3.0"},{"name":"maven:io.sentry:sentry-graphql","version":"7.3.0"},{"name":"maven:io.sentry:sentry-quartz","version":"7.3.0"},{"name":"maven:io.sentry:sentry-logback","version":"7.3.0"}],"integrations":["SpringBoot3","UncaughtExceptionHandler","ShutdownHook","JDBC","Spring6GrahQLWebMVC","GraphQL","Quartz","Logback"]},"trace":{"trace_id":"00bd13c1652f4f8e8e8570ba8cb0127b","public_key":"502f25099c204a2fbf4cb16edc5975d1","environment":"production","sample_rate":"1","sampled":"true"},"sent_at":"2024-02-19T08:57:42.882Z"}
{"content_type":"application/json","type":"transaction","length":3501}
{"transaction":"POST /person/","start_timestamp":1708333062.041582,"timestamp":1708333062.258721,"spans":[{"start_timestamp":1708333062.238941,"timestamp":1708333062.246714,"trace_id":"00bd13c1652f4f8e8e8570ba8cb0127b","span_id":"fc55571d1fee46f1","parent_span_id":"85b838c8f8004fb9","op":"PersonService.create","status":"ok","origin":"auto.function.spring_jakarta.advice","data":{"thread.name":"http-nio-8080-exec-1","thread.id":"47"}},{"start_timestamp":1708333062.244611,"timestamp":1708333062.245301,"trace_id":"00bd13c1652f4f8e8e8570ba8cb0127b","span_id":"5ffb3e08ca624077","parent_span_id":"fc55571d1fee46f1","op":"db.query","description":"insert into person (firstName, lastName) values (?, ?)","status":"ok","origin":"auto.db.jdbc","data":{"db.system":"hsqldb","thread.name":"http-nio-8080-exec-1","thread.id":"47","db.name":"testdb"}}],"type":"transaction","measurements":{"create_count":{"value":1}},"transaction_info":{"source":"route"},"event_id":"dad187904fee477e9aa75429a1eda314","contexts":{"runtime":{"name":"Azul Systems, Inc.","version":"17.0.5"},"trace":{"trace_id":"00bd13c1652f4f8e8e8570ba8cb0127b","span_id":"85b838c8f8004fb9","op":"http.server","status":"ok","origin":"auto.http.spring_jakarta.webmvc"}},"sdk":{"name":"sentry.java.spring-boot.jakarta","version":"7.3.0","packages":[{"name":"maven:io.sentry:sentry","version":"7.3.0"},{"name":"maven:io.sentry:sentry-spring-boot-starter-jakarta","version":"7.3.0"},{"name":"maven:io.sentry:sentry-jdbc","version":"7.3.0"},{"name":"maven:io.sentry:sentry-graphql","version":"7.3.0"},{"name":"maven:io.sentry:sentry-quartz","version":"7.3.0"},{"name":"maven:io.sentry:sentry-logback","version":"7.3.0"}],"integrations":["SpringBoot3","UncaughtExceptionHandler","ShutdownHook","JDBC","Spring6GrahQLWebMVC","GraphQL","Quartz","Logback"]},"request":{"url":"http://localhost:8080/person/","method":"POST","data":"{\"firstName\":\"John\",\"lastName\":\"a\"}","cookies":"","headers":{"authorization":"Basic dXNlcjpwYXNzd29yZA==","content-length":"35","host":"localhost:8080","content-type":"application/json","user-agent":"curl/8.4.0","accept":"*/*"}},"environment":"production","platform":"java","user":{"username":"user","ip_address":"0:0:0:0:0:0:0:1"},"server_name":"192.168.68.51","breadcrumbs":[{"timestamp":"2024-02-19T08:56:16.860Z","message":"Started SentryDemoApplication in 2.602 seconds (process running for 2.787)","data":{},"category":"io.sentry.samples.spring.boot.jakarta.SentryDemoApplication","level":"info"},{"timestamp":"2024-02-19T08:57:42.033Z","message":"Initializing Spring DispatcherServlet 'dispatcherServlet'","data":{},"category":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]","level":"info"},{"timestamp":"2024-02-19T08:57:42.033Z","message":"Initializing Servlet 'dispatcherServlet'","data":{},"category":"org.springframework.web.servlet.DispatcherServlet","level":"info"},{"timestamp":"2024-02-19T08:57:42.035Z","message":"Completed initialization in 1 ms","data":{},"category":"org.springframework.web.servlet.DispatcherServlet","level":"info"},{"timestamp":"2024-02-19T08:57:42.039Z","type":"http","data":{"method":"POST","url":"/person/"},"category":"http"},{"timestamp":"2024-02-19T08:57:42.050Z","message":"Cache miss for REQUEST dispatch to '/person/' (previous null). Performing CorsConfiguration lookup. This is logged once only at WARN level, and every time at TRACE.","data":{},"category":"org.springframework.web.servlet.handler.HandlerMappingIntrospector","level":"warning"}]}
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
import { SentryErrorEvent, SentryEvent } from '../../types';
import { SentryEvent } from '../../types';
import { Error, ErrorSummary, ErrorTitle } from './Error';

export function EventTitle({ event }: { event: SentryErrorEvent | SentryEvent }) {
function getEventMessage(event: SentryEvent) {
if (typeof event.message === 'string') {
return event.message;
} else if (event.message !== undefined && typeof event.message.formatted === 'string') {
return event.message.formatted;
} else {
return '';
}
}

export function EventTitle({ event }: { event: SentryEvent }) {
if ('exception' in event) {
return <ErrorTitle event={event} />;
}

return (
<>
<strong className="font-bold">{event.message}</strong>
<strong className="font-bold">{getEventMessage(event)}</strong>
</>
);
}

export function EventSummary({ event }: { event: SentryErrorEvent | SentryEvent }) {
export function EventSummary({ event }: { event: SentryEvent }) {
if ('exception' in event) {
return <ErrorSummary event={event} />;
}
return (
<div className="space-y-4 font-mono">
<h3 className="flex flex-col">
<strong className="text-xl">{event.message}</strong>
<strong className="text-xl">{getEventMessage(event)}</strong>
</h3>
</div>
);
}

export default function Event({ event }: { event: SentryErrorEvent | SentryEvent }) {
export default function Event({ event }: { event: SentryEvent }) {
if ('exception' in event) {
return <Error event={event} />;
}

return (
<h3 className="bg-primary-950 flex flex-col">
<strong className="text-xl">Message:</strong>
<pre>{event.message}</pre>
<pre>{getEventMessage(event)}</pre>
</h3>
);
}
33 changes: 18 additions & 15 deletions packages/overlay/src/integrations/sentry/data/sentryDataCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,23 +124,26 @@ class SentryDataCache {
trace.transactions.sort((a, b) => a.start_timestamp - b.start_timestamp);

// recompute tree as we might have txn out of order
// XXX: we're trusting timestamps, wihch are not trustworthy
// XXX: we're trusting timestamps, which are not trustworthy
const allSpans: Span[] = [];
trace.transactions.forEach(txn => {
allSpans.push(
{
...txn.contexts.trace,
start_timestamp: txn.start_timestamp,
timestamp: txn.timestamp,
description: traceCtx.description || txn.transaction,
transaction: txn,
},
...txn.spans.map(s => ({
...s,
timestamp: toTimestamp(s.timestamp),
start_timestamp: toTimestamp(s.start_timestamp),
})),
);
allSpans.push({
...txn.contexts.trace,
start_timestamp: txn.start_timestamp,
timestamp: txn.timestamp,
description: traceCtx.description || txn.transaction,
transaction: txn,
});

if (txn.spans) {
allSpans.push(
...txn.spans.map(s => ({
...s,
timestamp: toTimestamp(s.timestamp),
start_timestamp: toTimestamp(s.start_timestamp),
})),
);
}
});
trace.spans = allSpans;
trace.spanTree = groupSpans(trace.spans);
Expand Down
13 changes: 10 additions & 3 deletions packages/overlay/src/integrations/sentry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type CommonEventAttrs = {
// not always present, but we are forcing it in EventCache
event_id: string;
timestamp: number;
message?: string;
message?: SentryFormattedMessage;
breadcrumbs?: Breadcrumb[] | { values: Breadcrumb[] };
transaction?: string;
environment?: string;
Expand Down Expand Up @@ -87,8 +87,15 @@ export type Tags = {
[key: string]: string;
};

export type SentryFormattedMessage =
| string
| {
formatted: string;
params?: [];
};

export type SentryErrorEvent = CommonEventAttrs & {
type?: 'error' | 'event' | 'default';
type?: 'error' | 'event' | 'message' | 'default';
exception: EventException;
};

Expand All @@ -109,7 +116,7 @@ export type Span = {

export type SentryTransactionEvent = CommonEventAttrs & {
type: 'transaction';
spans: Span[];
spans?: Span[];
start_timestamp: string;
contexts: Contexts & {
trace: TraceContext;
Expand Down
7 changes: 7 additions & 0 deletions packages/overlay/test/integrations/sentry/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ describe('Sentry Integration', () => {
expect((processedEnvelope.event[1][0][1] as any).type).toEqual('transaction');
});

test('Process Java Transaction Envelope', () => {
const envelope = fs.readFileSync('./_fixtures/envelope_java.txt', 'utf-8');
const processedEnvelope = processEnvelope({ data: envelope, contentType: 'test' });
expect(processedEnvelope).not.toBe(undefined);
expect((processedEnvelope.event[1][0][1] as any).type).toEqual('transaction');
});

test('Process Astro SSR pageload (BE -> FE) trace', () => {
const nodeEnvelope = fs.readFileSync('./_fixtures/envelope_astro_ssr_node.txt', 'utf-8');
const processedNodeEnvelope = processEnvelope({ data: nodeEnvelope, contentType: 'test' });
Expand Down