diff --git a/packages/logs/src/boot/logs.entry.ts b/packages/logs/src/boot/logs.entry.ts index 08965f79c2..0bd114342e 100644 --- a/packages/logs/src/boot/logs.entry.ts +++ b/packages/logs/src/boot/logs.entry.ts @@ -12,10 +12,12 @@ import { UserConfiguration, } from '@datadog/browser-core' import { HandlerType, Logger, LogsMessage, StatusType } from '../domain/logger' +import { LogsEventsFormat } from '../logsEventsFormat' import { startLogs } from './logs' export interface LogsUserConfiguration extends UserConfiguration { forwardErrorsToLogs?: boolean + beforeSend?: (event: LogsEventsFormat) => void } export interface LoggerConfiguration { @@ -47,7 +49,6 @@ export function makeLogsGlobal(startLogsImpl: StartLogs) { let sendLogStrategy = (message: LogsMessage, currentContext: Context) => { beforeInitSendLog.add([message, currentContext]) } - const logger = new Logger(sendLog) return makeGlobal({ diff --git a/packages/logs/src/boot/logs.spec.ts b/packages/logs/src/boot/logs.spec.ts index 8d5c335ff3..49a6166dec 100644 --- a/packages/logs/src/boot/logs.spec.ts +++ b/packages/logs/src/boot/logs.spec.ts @@ -11,7 +11,8 @@ import { import sinon from 'sinon' import { Logger, LogsMessage, StatusType } from '../domain/logger' -import { assembleMessageContexts, doStartLogs } from './logs' +import { LogsEventsFormat } from '../logsEventsFormat' +import { buildAssemble, doStartLogs } from './logs' interface SentMessage extends LogsMessage { logger?: { name: string } @@ -126,16 +127,35 @@ describe('logs', () => { }) }) - describe('assembleMessageContexts', () => { - it('assembles various contexts', () => { - expect( - assembleMessageContexts( - { session_id: SESSION_ID, service: 'Service' }, - { foo: 'from-current-context' }, - { view: { url: 'http://from-rum-context.com', id: 'view-id' } }, - DEFAULT_MESSAGE - ) - ).toEqual({ + describe('assemble', () => { + let assemble: (message: LogsMessage, currentContext: Context) => Context | undefined + let beforeSend: (event: LogsEventsFormat) => void + + beforeEach(() => { + beforeSend = noop + assemble = buildAssemble(session, { + ...(baseConfiguration as Configuration), + beforeSend: (x: LogsEventsFormat) => beforeSend(x), + }) + window.DD_RUM = { + getInternalContext: noop, + } + }) + + it('should not assemble when session is not tracked', () => { + sessionIsTracked = false + + expect(assemble(DEFAULT_MESSAGE, { foo: 'from-current-context' })).toBeUndefined() + }) + + it('add default, current and RUM context to message', () => { + spyOn(window.DD_RUM!, 'getInternalContext').and.returnValue({ + view: { url: 'http://from-rum-context.com', id: 'view-id' }, + }) + + const assembledMessage = assemble(DEFAULT_MESSAGE, { foo: 'from-current-context' }) + + expect(assembledMessage).toEqual({ foo: 'from-current-context', message: DEFAULT_MESSAGE.message, service: 'Service', @@ -146,36 +166,41 @@ describe('logs', () => { }) it('message context should take precedence over RUM context', () => { - expect( - assembleMessageContexts( - {}, - { session_id: 'from-rum-context' }, - {}, - { ...DEFAULT_MESSAGE, session_id: 'from-message-context' } - ).session_id - ).toBe('from-message-context') + spyOn(window.DD_RUM!, 'getInternalContext').and.returnValue({ session_id: 'from-rum-context' }) + + const assembledMessage = assemble({ ...DEFAULT_MESSAGE, session_id: 'from-message-context' }, {}) + + expect(assembledMessage!.session_id).toBe('from-message-context') }) it('RUM context should take precedence over current context', () => { - expect( - assembleMessageContexts( - {}, - { session_id: 'from-current-context' }, - { session_id: 'from-rum-context' }, - DEFAULT_MESSAGE - ).session_id - ).toBe('from-rum-context') + spyOn(window.DD_RUM!, 'getInternalContext').and.returnValue({ session_id: 'from-rum-context' }) + + const assembledMessage = assemble(DEFAULT_MESSAGE, { session_id: 'from-current-context' }) + + expect(assembledMessage!.session_id).toBe('from-rum-context') }) it('current context should take precedence over default context', () => { - expect( - assembleMessageContexts( - { service: 'from-default-context' }, - { service: 'from-current-context' }, - undefined, - DEFAULT_MESSAGE - ).service - ).toBe('from-current-context') + const assembledMessage = assemble(DEFAULT_MESSAGE, { service: 'from-current-context' }) + + expect(assembledMessage!.service).toBe('from-current-context') + }) + + it('should allow modification on sensitive field', () => { + beforeSend = (event: LogsEventsFormat) => (event.message = 'modified') + + const assembledMessage = assemble(DEFAULT_MESSAGE, {}) + + expect(assembledMessage!.message).toBe('modified') + }) + + it('should reject modification on non sensitive field', () => { + beforeSend = (event: LogsEventsFormat) => ((event.service as any) = 'modified') + + const assembledMessage = assemble(DEFAULT_MESSAGE, {}) + + expect(assembledMessage!.service).toBe('Service') }) }) diff --git a/packages/logs/src/boot/logs.ts b/packages/logs/src/boot/logs.ts index 0bcc8b2e85..493cb59c33 100644 --- a/packages/logs/src/boot/logs.ts +++ b/packages/logs/src/boot/logs.ts @@ -9,15 +9,20 @@ import { getTimestamp, HttpRequest, InternalMonitoring, + limitModification, + noop, Observable, RawError, startAutomaticErrorCollection, } from '@datadog/browser-core' import { Logger, LogsMessage } from '../domain/logger' import { LoggerSession, startLoggerSession } from '../domain/loggerSession' +import { LogsEventsFormat } from '../logsEventsFormat' import { buildEnv } from './buildEnv' import { LogsUserConfiguration } from './logs.entry' +const FIELDS_WITH_SENSITIVE_DATA = ['view.url', 'view.referrer', 'message', 'error.stack', 'http.url'] + export function startLogs( userConfiguration: LogsUserConfiguration, errorLogger: Logger, @@ -44,7 +49,8 @@ export function doStartLogs( combine({ session_id: session.getId() }, getGlobalContext(), getRUMInternalContext()) ) - const batch = startLoggerBatch(configuration, session) + const assemble = buildAssemble(session, configuration) + const batch = startLoggerBatch(configuration) errorObservable.subscribe((error: RawError) => { errorLogger.error( @@ -73,13 +79,14 @@ export function doStartLogs( }) return (message: LogsMessage, currentContext: Context) => { - if (session.isTracked()) { - batch.add(message, currentContext) + const contextualizedMessage = assemble(message, currentContext) + if (contextualizedMessage) { + batch.add(contextualizedMessage) } } } -function startLoggerBatch(configuration: Configuration, session: LoggerSession) { +function startLoggerBatch(configuration: Configuration) { const primaryBatch = createLoggerBatch(configuration.logsEndpoint) let replicaBatch: Batch | undefined @@ -98,21 +105,40 @@ function startLoggerBatch(configuration: Configuration, session: LoggerSession) } return { - add(message: LogsMessage, currentContext: Context) { - const contextualizedMessage = assembleMessageContexts( - { service: configuration.service, session_id: session.getId() }, - currentContext, - getRUMInternalContext(), - message - ) - primaryBatch.add(contextualizedMessage) + add(message: Context) { + primaryBatch.add(message) if (replicaBatch) { - replicaBatch.add(contextualizedMessage) + replicaBatch.add(message) } }, } } +export function buildAssemble( + session: LoggerSession, + configuration: Configuration +): (message: LogsMessage, currentContext: Context) => Context | undefined { + return (message: LogsMessage, currentContext: Context) => { + if (!session.isTracked()) { + return undefined + } + const contextualizedMessage = combine( + { service: configuration.service, session_id: session.getId() }, + currentContext, + getRUMInternalContext(), + message + ) + if (configuration.beforeSend) { + limitModification( + contextualizedMessage as LogsEventsFormat & Context, + FIELDS_WITH_SENSITIVE_DATA, + configuration.beforeSend + ) + } + return contextualizedMessage + } +} + export function assembleMessageContexts( defaultContext: { service?: string; session_id?: string }, currentContext: Context,