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

✨ [RUMF-709][core] support 'null' as a context value #546

Merged
merged 6 commits into from
Sep 25, 2020
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
4 changes: 2 additions & 2 deletions packages/core/src/internalMonitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function startMonitoringBatch(configuration: Configuration) {
}

function withContext(message: MonitoringMessage) {
return utils.deepMerge(
return utils.combine(
{
date: new Date().getTime(),
view: {
Expand All @@ -77,7 +77,7 @@ function startMonitoringBatch(configuration: Configuration) {
},
externalContextProvider !== undefined ? externalContextProvider() : {},
message
) as utils.Context
)
}

return {
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ export function throttle(
}

const isContextArray = (value: ContextValue): value is ContextArray => Array.isArray(value)
const isContext = (value: ContextValue): value is Context => !Array.isArray(value) && typeof value === 'object'
const isContext = (value: ContextValue): value is Context =>
!Array.isArray(value) && typeof value === 'object' && value !== null

/**
* Performs a deep merge of objects and arrays
Expand All @@ -87,7 +88,7 @@ const isContext = (value: ContextValue): value is Context => !Array.isArray(valu
* ⚠️ this method does not prevent infinite loops while merging circular references ⚠️
*
*/
export function deepMerge(destination: ContextValue, ...toMerge: ContextValue[]): ContextValue {
function deepMerge(destination: ContextValue, ...toMerge: ContextValue[]): ContextValue {
return toMerge.reduce((value1: ContextValue, value2: ContextValue): ContextValue => {
if (isContextArray(value1) && isContextArray(value2)) {
return [...Array(Math.max(value1.length, value2.length))].map((_, index) =>
Expand All @@ -107,10 +108,11 @@ export function deepMerge(destination: ContextValue, ...toMerge: ContextValue[])
}, destination)
}

export function combine<A, B>(a: A, b: B): A & B
export function combine<A, B, C>(a: A, b: B, c: C): A & B & C
export function combine<A, B, C, D>(a: A, b: B, c: C, d: D): A & B & C & D
export function combine(a: Context, ...b: Context[]): Context {
return deepMerge(a, ...b) as Context
export function combine(destination: Context, ...toMerge: Array<Context | null>): Context {
return deepMerge(destination, ...toMerge.filter((object) => object !== null)) as Context
}

interface Assignable {
Expand Down Expand Up @@ -161,7 +163,7 @@ export interface Context {
[x: string]: ContextValue
}

export type ContextValue = string | number | boolean | Context | ContextArray | undefined
export type ContextValue = string | number | boolean | Context | ContextArray | undefined | null

export interface ContextArray extends Array<ContextValue> {}

Expand All @@ -177,7 +179,7 @@ export function deepSnakeCase(candidate: ContextValue): ContextValue {
if (Array.isArray(candidate)) {
return candidate.map((value: ContextValue) => deepSnakeCase(value))
}
if (typeof candidate === 'object') {
if (typeof candidate === 'object' && candidate !== null) {
return withSnakeCaseKeys(candidate)
}
return candidate
Expand Down
28 changes: 21 additions & 7 deletions packages/core/test/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
deepMerge,
combine,
findCommaSeparatedValue,
jsonStringify,
performDraw,
Expand All @@ -10,21 +10,31 @@ import {
} from '../src/utils'

describe('utils', () => {
describe('deepMerge', () => {
describe('combine', () => {
it('should deeply add and replace keys', () => {
const target = { a: { b: 'toBeReplaced', c: 'target' } }
const source = { a: { b: 'replaced', d: 'source' } }
expect(deepMerge(target, source)).toEqual({ a: { b: 'replaced', c: 'target', d: 'source' } })
expect(combine(target, source)).toEqual({ a: { b: 'replaced', c: 'target', d: 'source' } })
})

it('should not replace with undefined', () => {
expect(deepMerge({ a: 1 }, { a: undefined })).toEqual({ a: 1 })
expect(combine({ a: 1 }, { a: undefined as number | undefined })).toEqual({ a: 1 })
})

it('should replace a sub-value with null', () => {
// tslint:disable-next-line: no-null-keyword
expect(combine({ a: {} }, { a: null as any })).toEqual({ a: null })
})

it('should ignore null arguments', () => {
// tslint:disable-next-line: no-null-keyword
expect(combine({ a: 1 }, null)).toEqual({ a: 1 })
})

it('should merge arrays', () => {
const target = [{ a: 'target' }, 'extraString']
const source = [{ b: 'source' }]
expect(deepMerge(target, source)).toEqual([{ a: 'target', b: 'source' }, 'extraString'])
const target = [{ a: 'target' }, 'extraString'] as any
const source = [{ b: 'source' }] as any
expect(combine(target, source)).toEqual([{ a: 'target', b: 'source' }, 'extraString'])
})
})

Expand Down Expand Up @@ -259,10 +269,14 @@ describe('utils', () => {
withSnakeCaseKeys({
camelCase: 1,
nestedKey: { 'kebab-case': 'helloWorld', array: [{ camelCase: 1 }, { camelCase: 2 }] },
// tslint:disable-next-line: no-null-keyword
nullValue: null,
})
).toEqual({
camel_case: 1,
nested_key: { kebab_case: 'helloWorld', array: [{ camel_case: 1 }, { camel_case: 2 }] },
// tslint:disable-next-line: no-null-keyword
null_value: null,
})
})
})
Expand Down
31 changes: 14 additions & 17 deletions packages/logs/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
Batch,
combine,
Configuration,
Context,
ContextValue,
deepMerge,
ErrorMessage,
ErrorObservable,
ErrorOrigin,
Expand Down Expand Up @@ -61,8 +61,8 @@ export function startLogger(
) {
let globalContext: Context = {}

internalMonitoring.setExternalContextProvider(
() => deepMerge({ session_id: session.getId() }, globalContext, getRUMInternalContext() as Context) as Context
internalMonitoring.setExternalContextProvider(() =>
combine({ session_id: session.getId() }, globalContext, getRUMInternalContext())
)

const batch = startLoggerBatch(configuration, session, () => globalContext)
Expand All @@ -76,10 +76,7 @@ export function startLogger(
errorObservable.subscribe((e: ErrorMessage) =>
logger.error(
e.message,
deepMerge(
({ date: getTimestamp(e.startTime), ...e.context } as unknown) as Context,
getRUMInternalContext(e.startTime)
)
combine({ date: getTimestamp(e.startTime), ...e.context }, getRUMInternalContext(e.startTime))
)
)

Expand Down Expand Up @@ -118,7 +115,7 @@ function startLoggerBatch(configuration: Configuration, session: LoggerSession,
}

function withContext(message: LogsMessage) {
return deepMerge(
return combine(
{
date: new Date().getTime(),
service: configuration.service,
Expand All @@ -129,9 +126,9 @@ function startLoggerBatch(configuration: Configuration, session: LoggerSession,
},
},
globalContextProvider(),
getRUMInternalContext() as Context,
getRUMInternalContext(),
message
) as Context
)
}

return {
Expand Down Expand Up @@ -175,31 +172,31 @@ export class Logger {
}

@monitored
log(message: string, messageContext = {}, status = StatusType.info) {
log(message: string, messageContext?: Context, status = StatusType.info) {
if (this.session.isTracked() && STATUS_PRIORITIES[status] >= STATUS_PRIORITIES[this.level]) {
this.handler({ message, status, ...(deepMerge({}, this.loggerContext, messageContext) as Context) })
this.handler({ message, status, ...combine({}, this.loggerContext, messageContext) })
}
}

debug(message: string, messageContext = {}) {
debug(message: string, messageContext?: Context) {
this.log(message, messageContext, StatusType.debug)
}

info(message: string, messageContext = {}) {
info(message: string, messageContext?: Context) {
this.log(message, messageContext, StatusType.info)
}

warn(message: string, messageContext = {}) {
warn(message: string, messageContext?: Context) {
this.log(message, messageContext, StatusType.warn)
}

error(message: string, messageContext = {}) {
error(message: string, messageContext?: Context) {
const errorOrigin = {
error: {
origin: ErrorOrigin.LOGGER,
},
}
this.log(message, deepMerge({}, errorOrigin, messageContext), StatusType.error)
this.log(message, combine({}, errorOrigin, messageContext), StatusType.error)
}

setContext(context: Context) {
Expand Down
21 changes: 7 additions & 14 deletions packages/rum/src/rum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Configuration,
Context,
ContextValue,
deepMerge,
ErrorContext,
ErrorMessage,
generateUUID,
Expand Down Expand Up @@ -166,13 +165,13 @@ export function startRum(
const parentContexts = startParentContexts(lifeCycle, session)

internalMonitoring.setExternalContextProvider(() => {
return deepMerge(
return combine(
{
application_id: applicationId,
},
parentContexts.findView(),
globalContext
) as Context
)
})

const batch = makeRumBatch(configuration, lifeCycle)
Expand Down Expand Up @@ -206,11 +205,9 @@ export function startRum(
getInternalContext: monitor((startTime?: number): InternalContext | undefined => {
const viewContext = parentContexts.findView(startTime)
if (session.isTracked() && viewContext && viewContext.sessionId) {
return (withSnakeCaseKeys(deepMerge(
{ applicationId },
viewContext,
parentContexts.findAction(startTime)
) as Context) as unknown) as InternalContext
return (withSnakeCaseKeys(
combine({ applicationId }, viewContext, parentContexts.findAction(startTime))
) as unknown) as InternalContext
}
}),
removeRumGlobalContext: monitor((key: string) => {
Expand Down Expand Up @@ -255,7 +252,7 @@ function makeRumBatch(configuration: Configuration, lifeCycle: LifeCycle): RumBa
}

function withReplicaApplicationId(message: Context) {
return deepMerge(message, { application_id: replica!.applicationId }) as Context
return combine(message, { application_id: replica!.applicationId })
}

let stopped = false
Expand Down Expand Up @@ -313,11 +310,7 @@ function makeRumEventHandler(
if (session.isTracked() && view && view.sessionId) {
const action = parentContexts.findAction(startTime)
const rumEvent = assemble(event, { action, view, rum: rumContextProvider() })
const message = deepMerge(
globalContextProvider(),
customerContext,
withSnakeCaseKeys(rumEvent as Context)
) as Context
const message = combine(globalContextProvider(), customerContext, withSnakeCaseKeys(rumEvent))
callback(message, rumEvent)
}
}
Expand Down