Skip to content

Commit

Permalink
Merge pull request #727 from DataDog/marcosaia/RUM-6160/additional-er…
Browse files Browse the repository at this point in the history
…ror-config

[RUM-6160] Added errorSource property in LogEvent for mapping
  • Loading branch information
marco-saia-datadog authored Oct 9, 2024
2 parents 774e0f9 + d53483f commit c76b044
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 21 deletions.
34 changes: 21 additions & 13 deletions packages/core/src/logs/DdLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DATADOG_MESSAGE_PREFIX, InternalLog } from '../InternalLog';
import { SdkVerbosity } from '../SdkVerbosity';
import type { DdNativeLogsType } from '../nativeModulesTypes';
import { DdAttributes } from '../rum/DdAttributes';
import type { ErrorSource } from '../rum/types';
import { validateContext } from '../utils/argsUtils';

import { generateEventMapper } from './eventMapper';
Expand All @@ -16,7 +17,8 @@ import type {
LogArguments,
LogEventMapper,
LogWithErrorArguments,
NativeLogWithError
NativeLogWithError,
RawLogWithError
} from './types';

const SDK_NOT_INITIALIZED_MESSAGE = 'DD_INTERNAL_LOG_SENT_BEFORE_SDK_INIT';
Expand Down Expand Up @@ -99,7 +101,8 @@ class DdLogsWrapper implements DdLogsType {
args[3],
validateContext(args[4]),
'error',
args[5]
args[5],
args[6]
);
}
return this.log(args[0], validateContext(args[1]), 'error');
Expand Down Expand Up @@ -177,26 +180,31 @@ class DdLogsWrapper implements DdLogsType {
stacktrace: string | undefined,
context: object,
status: 'debug' | 'info' | 'warn' | 'error',
fingerprint?: string
fingerprint?: string,
source?: ErrorSource
): Promise<void> => {
const event = this.logEventMapper.applyEventMapper({
const rawLogEvent: RawLogWithError = {
message,
errorKind,
errorMessage,
stacktrace,
context,
status,
fingerprint: fingerprint ?? ''
});
if (!event) {
fingerprint: fingerprint ?? '',
source
};

const mappedEvent = this.logEventMapper.applyEventMapper(rawLogEvent);

if (!mappedEvent) {
this.printLogDroppedByMapper(message, status);
return generateEmptyPromise();
}

this.printLogTracked(event.message, status);
this.printLogTracked(mappedEvent.message, status);
try {
const updatedContext = {
...event.context,
...mappedEvent.context,
[DdAttributes.errorSourceType]: 'react-native'
};

Expand All @@ -205,10 +213,10 @@ class DdLogsWrapper implements DdLogsType {
}

return await this.nativeLogs[`${status}WithError`](
event.message,
(event as NativeLogWithError).errorKind,
(event as NativeLogWithError).errorMessage,
(event as NativeLogWithError).stacktrace,
mappedEvent.message,
(mappedEvent as NativeLogWithError).errorKind,
(mappedEvent as NativeLogWithError).errorMessage,
(mappedEvent as NativeLogWithError).stacktrace,
updatedContext
);
} catch (error) {
Expand Down
147 changes: 147 additions & 0 deletions packages/core/src/logs/__tests__/DdLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

import { NativeModules } from 'react-native';

import { DdSdkReactNativeConfiguration } from '../../DdSdkReactNativeConfiguration';
import { DdSdkReactNative } from '../../DdSdkReactNative';
import { InternalLog } from '../../InternalLog';
import { SdkVerbosity } from '../../SdkVerbosity';
import type { DdNativeLogsType } from '../../nativeModulesTypes';
import { ErrorSource } from '../../rum/types';
import { DdLogs } from '../DdLogs';
import type { LogEventMapper } from '../types';

Expand Down Expand Up @@ -135,6 +138,150 @@ describe('DdLogs', () => {
'debug'
);
});

it('log with error events can be filtered by error source', async () => {
const logEventMapper: LogEventMapper = logEvent => {
if (logEvent.source === ErrorSource.CONSOLE) {
return null;
}

return logEvent;
};

DdLogs.registerLogEventMapper(logEventMapper);

await DdLogs.error(
'message',
'kind',
'message',
'stacktrace',
{},
'fingerprint',
ErrorSource.CONSOLE
);

// Call with filtered ErrorSource.CONSOLE type
expect(NativeModules.DdLogs.error).not.toHaveBeenCalled();
expect(InternalLog.log).toHaveBeenCalledWith(
'error log dropped by log mapper: "message"',
'debug'
);

// Call with valid ErrorSource.CUSTOM type
await DdLogs.error(
'message',
'kind',
'message',
'stacktrace',
{},
'fingerprint',
ErrorSource.CUSTOM
);

expect(NativeModules.DdLogs.errorWithError).toHaveBeenCalledWith(
'message',
'kind',
'message',
'stacktrace',
{
'_dd.error.fingerprint': 'fingerprint',
'_dd.error.source_type': 'react-native'
}
);
expect(InternalLog.log).toHaveBeenCalledWith(
'Tracking error log "message"',
'debug'
);
});

it('console errors can be filtered with mappers when trackErrors=true', async () => {
// GIVEN
const fakeAppId = '1';
const fakeClientToken = '2';
const fakeEnvName = 'env';
const configuration = new DdSdkReactNativeConfiguration(
fakeClientToken,
fakeEnvName,
fakeAppId,
false,
false,
true // Track Errors
);

// Register log event mapper to filter console log events
configuration.logEventMapper = logEvent => {
if (logEvent.source === ErrorSource.CONSOLE) {
return null;
}

return logEvent;
};

NativeModules.DdSdk.initialize.mockResolvedValue(null);

// WHEN
await DdSdkReactNative.initialize(configuration);

console.error('console-error-message');
expect(NativeModules.DdLogs.error).not.toHaveBeenCalled();
expect(InternalLog.log).toHaveBeenCalledWith(
'error log dropped by log mapper: "console-error-message"',
'debug'
);

// Call with valid ErrorSource.CUSTOM type
await DdLogs.error(
'message',
'kind',
'message',
'stacktrace',
{},
'fingerprint',
ErrorSource.CUSTOM
);

expect(NativeModules.DdLogs.errorWithError).toHaveBeenCalledWith(
'message',
'kind',
'message',
'stacktrace',
{
'_dd.error.fingerprint': 'fingerprint',
'_dd.error.source_type': 'react-native'
}
);
expect(InternalLog.log).toHaveBeenCalledWith(
'Tracking error log "message"',
'debug'
);
});

it('console errors are reported in logs when trackErrors=true', async () => {
// GIVEN
const fakeAppId = '1';
const fakeClientToken = '2';
const fakeEnvName = 'env';
const configuration = new DdSdkReactNativeConfiguration(
fakeClientToken,
fakeEnvName,
fakeAppId,
false,
false,
true // Track Errors
);

NativeModules.DdSdk.initialize.mockResolvedValue(null);

// WHEN
await DdSdkReactNative.initialize(configuration);

console.error('console-error-message');
expect(NativeModules.DdLogs.error).not.toHaveBeenCalled();
expect(InternalLog.log).toHaveBeenCalledWith(
'Tracking error log "console-error-message"',
'debug'
);
});
});

describe('log with error', () => {
Expand Down
37 changes: 37 additions & 0 deletions packages/core/src/logs/__tests__/eventMapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

/* eslint-disable @typescript-eslint/ban-ts-comment */
import { ErrorSource } from '../../rum/types';
import { formatRawLogToLogEvent } from '../eventMapper';

describe('formatRawLogToLogEvent', () => {
Expand Down Expand Up @@ -84,4 +85,40 @@ describe('formatRawLogToLogEvent', () => {
attributes: { appType: 'student' }
});
});

it('formats a raw log with error attributes and with context, userInfo, attributes and source to a LogEvent', () => {
expect(
formatRawLogToLogEvent(
{
message: 'original',
errorKind: 'TypeError',
errorMessage: 'something went wrong',
stacktrace: 'stacktrace',
context: { loggedIn: true },
status: 'info',
source: ErrorSource.CONSOLE
},
{
userInfo: {
name: 'userName',
extraInfo: { loggedIn: true }
},
attributes: { appType: 'student' }
}
)
).toEqual({
message: 'original',
errorKind: 'TypeError',
errorMessage: 'something went wrong',
stacktrace: 'stacktrace',
context: { loggedIn: true },
status: 'info',
source: ErrorSource.CONSOLE,
userInfo: {
name: 'userName',
extraInfo: { loggedIn: true }
},
attributes: { appType: 'student' }
});
});
});
12 changes: 8 additions & 4 deletions packages/core/src/logs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Copyright 2016-Present Datadog, Inc.
*/

import type { ErrorSource } from '../rum/types';
import type { UserInfo } from '../sdk/UserInfoSingleton/types';

/**
Expand Down Expand Up @@ -49,12 +50,13 @@ export type RawLog = {
};
export type RawLogWithError = {
message: string;
errorKind: string;
errorMessage: string;
stacktrace: string;
errorKind?: string;
errorMessage?: string;
stacktrace?: string;
context: object;
status: LogStatus;
fingerprint?: string;
source?: ErrorSource;
};

/**
Expand Down Expand Up @@ -82,6 +84,7 @@ export type LogEvent = {
errorMessage?: string;
stacktrace?: string;
fingerprint?: string;
readonly source?: ErrorSource;
// readonly date: number; // TODO: RUMM-2446 & RUMM-2447
readonly status: LogStatus;
readonly userInfo: UserInfo;
Expand All @@ -98,5 +101,6 @@ export type LogWithErrorArguments = [
errorMessage?: string,
stacktrace?: string,
context?: object,
fingerprint?: string
fingerprint?: string,
source?: ErrorSource
];
16 changes: 12 additions & 4 deletions packages/core/src/rum/instrumentation/DdRumErrorTracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,18 @@ export class DdRumErrorTracking {
): Promise<[void, void]> => {
return Promise.all([
DdRum.addError(message, source, stacktrace, context),
DdLogs.error(message, errorName, message, stacktrace, {
...context,
'_dd.error_log.is_crash': true
})
DdLogs.error(
message,
errorName,
message,
stacktrace,
{
...context,
'_dd.error_log.is_crash': true
},
undefined,
source
)
]);
};
}

0 comments on commit c76b044

Please sign in to comment.