Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RUM-6160] Added errorSource property in LogEvent for mapping #727

Merged
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
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');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Quality Violation

Unexpected console statement. (...read more)

Debugging with console is not considered a bad practice, but it's easy to forget about console statements and leave them in production code. There is no need to pollute production builds with debugging statements.

View in Datadog  Leave us feedback  Documentation

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
)
]);
};
}
Loading