Skip to content

Commit db263fe

Browse files
committed
initial work on .log
1 parent 50942ce commit db263fe

File tree

8 files changed

+225
-1
lines changed

8 files changed

+225
-1
lines changed

packages/browser/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export {
1010
extraErrorDataIntegration,
1111
rewriteFramesIntegration,
1212
captureFeedback,
13+
_experimentalLogError,
14+
_experimentalLogInfo,
15+
_experimentalLogWarning,
1316
} from '@sentry/core';
1417

1518
export { replayIntegration, getReplay } from '@sentry-internal/replay';

packages/core/src/exports.ts

+26
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { timestampInSeconds } from './utils-hoist/time';
2424
import { GLOBAL_OBJ } from './utils-hoist/worldwide';
2525
import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
2626
import { parseEventHintOrCaptureContext } from './utils/prepareEvent';
27+
import { captureLog } from './ourlogs';
2728

2829
/**
2930
* Captures an exception event and sends it to Sentry.
@@ -334,3 +335,28 @@ export function captureSession(end: boolean = false): void {
334335
// only send the update
335336
_sendSessionUpdate();
336337
}
338+
339+
340+
/**
341+
* A utility to record a log with level 'INFO' and send it to sentry.
342+
*
343+
* Logs represent a message and some parameters which provide context for a trace or error.
344+
* Ex: sentry.logInfo`user ${username} just bought ${item}!`
345+
*/
346+
export const _experimentalLogInfo = captureLog.bind(null, 'info');
347+
348+
/**
349+
* A utility to record a log with level 'ERROR' and send it to sentry.
350+
*
351+
* Logs represent a message and some parameters which provide context for a trace or error.
352+
* Ex: sentry.logError`user ${username} just bought ${item}!`
353+
*/
354+
export const _experimentalLogError = captureLog.bind(null, 'error');
355+
356+
/**
357+
* A utility to record a log with level 'WARNING' and send it to sentry.
358+
*
359+
* Logs represent a message and some parameters which provide context for a trace or error.
360+
* Ex: sentry.logWarning`user ${username} just bought ${item}!`
361+
*/
362+
export const _experimentalLogWarning = captureLog.bind(null, 'warning');

packages/core/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export {
2929
endSession,
3030
captureSession,
3131
addEventProcessor,
32+
_experimentalLogError,
33+
_experimentalLogInfo,
34+
_experimentalLogWarning
3235
} from './exports';
3336
export {
3437
getCurrentScope,

packages/core/src/ourlogs.ts

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { getClient, getGlobalScope } from './currentScopes';
2+
import type { LogEnvelope, LogItem } from './types-hoist/envelope';
3+
import type { Log, LogAttribute, LogSeverityLevel } from './types-hoist/ourlogs';
4+
import { createEnvelope, dsnToString } from './utils-hoist';
5+
6+
/**
7+
* Creates envelope item for a single log
8+
*/
9+
export function createLogEnvelopeItem(log: Log): LogItem {
10+
const headers: LogItem[0] = {
11+
type: 'otel_log',
12+
};
13+
14+
return [headers, log];
15+
}
16+
17+
/**
18+
* Records a log and sends it to sentry.
19+
*
20+
* Logs represent a message (and optionally some structured data) which provide context for a trace or error.
21+
* Ex: sentry.addLog({level: 'warning', message: `user ${user} just bought ${item}`, attributes: {user, item}}
22+
*
23+
* @params log - the log object which will be sent
24+
*/
25+
function addLog(log: Log): void {
26+
const client = getClient();
27+
28+
if (!client) {
29+
return;
30+
}
31+
32+
// if (!client.getOptions()._experiments?.logSupport) {
33+
// return;
34+
// }
35+
36+
const globalScope = getGlobalScope();
37+
const dsn = client.getDsn();
38+
39+
const headers: LogEnvelope[0] = {
40+
trace: {
41+
trace_id: globalScope.getPropagationContext().traceId,
42+
public_key: dsn?.publicKey,
43+
},
44+
...(dsn ? {dsn: dsnToString(dsn)} : {}),
45+
}
46+
if(!log.traceId) {
47+
log.traceId = globalScope.getPropagationContext().traceId || '00000000-0000-0000-0000-000000000000';
48+
}
49+
if(!log.timeUnixNano) {
50+
log.timeUnixNano = `${(new Date()).getTime().toString()}000000`;
51+
}
52+
53+
const envelope = createEnvelope<LogEnvelope>(headers, [createLogEnvelopeItem(log)]);
54+
55+
client.sendEnvelope(envelope).then(null, ex => console.error(ex));
56+
}
57+
58+
/**
59+
* A utility function to be able to create methods like Sentry.info(...)
60+
*
61+
* The first parameter is bound with, e.g., const info = captureLog.bind(null, 'info')
62+
* The other parameters are in the format to be passed a template, Sentry.info`hello ${world}`
63+
*/
64+
export function captureLog(level: LogSeverityLevel, strings: string[], ...values: unknown[]): void {
65+
addLog({
66+
severityText: level,
67+
body: {
68+
stringValue: strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '' ),
69+
},
70+
attributes: values.map<LogAttribute>((value, index) => {
71+
const key = `param${index}`;
72+
if (typeof value === 'number') {
73+
if(Number.isInteger(value)) {
74+
return {
75+
key,
76+
value: {
77+
intValue: value
78+
}
79+
}
80+
}
81+
return {
82+
key,
83+
value: {
84+
doubleValue: value
85+
}
86+
}
87+
} else if (typeof value === 'boolean') {
88+
return {
89+
key,
90+
value: {
91+
boolValue: value
92+
}
93+
}
94+
} else if (typeof value === 'string') {
95+
return {
96+
key,
97+
value: {
98+
stringValue: value
99+
}
100+
}
101+
} else {
102+
return {
103+
key,
104+
value: {
105+
stringValue: JSON.stringify(value)
106+
}
107+
}
108+
}
109+
}, {})
110+
})
111+
}

packages/core/src/types-hoist/envelope.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { ReplayEvent, ReplayRecordingData } from './replay';
1010
import type { SdkInfo } from './sdkinfo';
1111
import type { SerializedSession, SessionAggregates } from './session';
1212
import type { SpanJSON } from './span';
13+
import { Log } from './ourlogs';
1314

1415
// Based on: https://develop.sentry.dev/sdk/envelopes/
1516

@@ -43,6 +44,7 @@ export type EnvelopeItemType =
4344
| 'replay_recording'
4445
| 'check_in'
4546
| 'span'
47+
| 'otel_log'
4648
| 'raw_security';
4749

4850
export type BaseEnvelopeHeaders = {
@@ -85,6 +87,7 @@ type CheckInItemHeaders = { type: 'check_in' };
8587
type ProfileItemHeaders = { type: 'profile' };
8688
type ProfileChunkItemHeaders = { type: 'profile_chunk' };
8789
type SpanItemHeaders = { type: 'span' };
90+
type LogItemHeaders = { type: 'otel_log' };
8891
type RawSecurityHeaders = { type: 'raw_security'; sentry_release?: string; sentry_environment?: string };
8992

9093
export type EventItem = BaseEnvelopeItem<EventItemHeaders, Event>;
@@ -101,6 +104,7 @@ export type FeedbackItem = BaseEnvelopeItem<FeedbackItemHeaders, FeedbackEvent>;
101104
export type ProfileItem = BaseEnvelopeItem<ProfileItemHeaders, Profile>;
102105
export type ProfileChunkItem = BaseEnvelopeItem<ProfileChunkItemHeaders, ProfileChunk>;
103106
export type SpanItem = BaseEnvelopeItem<SpanItemHeaders, Partial<SpanJSON>>;
107+
export type LogItem = BaseEnvelopeItem<LogItemHeaders, Log>;
104108
export type RawSecurityItem = BaseEnvelopeItem<RawSecurityHeaders, LegacyCSPReport>;
105109

106110
export type EventEnvelopeHeaders = { event_id: string; sent_at: string; trace?: Partial<DynamicSamplingContext> };
@@ -109,6 +113,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext };
109113
type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders;
110114
type ReplayEnvelopeHeaders = BaseEnvelopeHeaders;
111115
type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
116+
type LogEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext };
112117

113118
export type EventEnvelope = BaseEnvelope<
114119
EventEnvelopeHeaders,
@@ -121,6 +126,7 @@ export type CheckInEnvelope = BaseEnvelope<CheckInEnvelopeHeaders, CheckInItem>;
121126
export type SpanEnvelope = BaseEnvelope<SpanEnvelopeHeaders, SpanItem>;
122127
export type ProfileChunkEnvelope = BaseEnvelope<BaseEnvelopeHeaders, ProfileChunkItem>;
123128
export type RawSecurityEnvelope = BaseEnvelope<BaseEnvelopeHeaders, RawSecurityItem>;
129+
export type LogEnvelope = BaseEnvelope<LogEnvelopeHeaders, LogItem>;
124130

125131
export type Envelope =
126132
| EventEnvelope
@@ -130,6 +136,7 @@ export type Envelope =
130136
| ReplayEnvelope
131137
| CheckInEnvelope
132138
| SpanEnvelope
133-
| RawSecurityEnvelope;
139+
| RawSecurityEnvelope
140+
| LogEnvelope;
134141

135142
export type EnvelopeItem = Envelope[1][number];

packages/core/src/types-hoist/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ export type {
113113
SpanContextData,
114114
TraceFlag,
115115
} from './span';
116+
export type {
117+
Log,
118+
LogAttribute,
119+
LogSeverityLevel,
120+
LogAttributeValueType
121+
} from './ourlogs';
116122
export type { SpanStatus } from './spanStatus';
117123
export type { TimedEvent } from './timedEvent';
118124
export type { StackFrame } from './stackframe';
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { SeverityLevel } from './severity';
2+
3+
export type LogSeverityLevel = SeverityLevel | 'critical' | 'trace';
4+
5+
export type LogAttributeValueType = {
6+
stringValue: string
7+
} | {
8+
intValue: number
9+
} | {
10+
boolValue: boolean
11+
} | {
12+
doubleValue: number
13+
}
14+
15+
export type LogAttribute = {
16+
key: string,
17+
value: LogAttributeValueType
18+
};
19+
20+
export interface Log {
21+
/**
22+
* Allowed values are, from highest to lowest:
23+
* `critical`, `fatal`, `error`, `warning`, `info`, `debug`, `trace`.
24+
*
25+
* The log level changes how logs are filtered and displayed.
26+
* Critical level logs are emphasized more than trace level logs.
27+
*
28+
* @summary The severity level of the log.
29+
*/
30+
severityText?: LogSeverityLevel;
31+
32+
/**
33+
* The severity number - generally higher severity are levels like 'error' and lower are levels like 'debug'
34+
*/
35+
severityNumber?: number;
36+
37+
/**
38+
* OTEL trace flags (bitmap) - currently 1 means sampled, 0 means unsampled - for sentry always set to 0
39+
*/
40+
traceFlags?: number;
41+
42+
/**
43+
* The trace ID for this log
44+
*/
45+
traceId?: string;
46+
47+
/**
48+
* The message to be logged - for example, 'hello world' would become a log like '[INFO] hello world'
49+
*/
50+
body: {
51+
stringValue: string,
52+
};
53+
54+
/**
55+
* Arbitrary structured data that stores information about the log - e.g., userId: 100.
56+
*/
57+
attributes?: LogAttribute[];
58+
59+
/**
60+
* This doesn't have to be explicitly specified most of the time. If you need to set it, the value
61+
* is the number of seconds since midnight on January 1, 1970 ("unix epoch time")
62+
*
63+
* @summary A timestamp representing when the log occurred.
64+
* @link https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#:~:text=is%20info.-,timestamp,-(recommended)
65+
*/
66+
timeUnixNano?: string;
67+
}

packages/core/src/utils-hoist/envelope.ts

+1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ const ITEM_TYPE_TO_DATA_CATEGORY_MAP: Record<EnvelopeItemType, DataCategory> = {
223223
feedback: 'feedback',
224224
span: 'span',
225225
raw_security: 'security',
226+
otel_log: 'log_item',
226227
};
227228

228229
/**

0 commit comments

Comments
 (0)