@aircall/logger
Advanced tools
Comparing version 2.6.0 to 2.7.0
@@ -6,2 +6,13 @@ # Change Log | ||
# [2.7.0](https://gitlab.com/aircall/shared/front-end-modules/compare/@aircall/logger@2.6.0...@aircall/logger@2.7.0) (2022-08-26) | ||
### Features | ||
* **logger:** add payload validation based on Aircall logs schema [PH-7453] ([7de46c3](https://gitlab.com/aircall/shared/front-end-modules/commit/7de46c3ad8378133b58c4bf3c2105b506cb7758d)) | ||
# [2.6.0](https://gitlab.com/aircall/shared/front-end-modules/compare/@aircall/logger@2.5.6...@aircall/logger@2.6.0) (2022-08-03) | ||
@@ -8,0 +19,0 @@ |
export declare const DEBUG_MODE_LIMIT_STORED_ACTIONS = 20; | ||
export declare const SENSITIVE_KEYS: string[]; | ||
export declare const DEFAULT_SENSITIVE_TEXT = "<sensitive>"; | ||
export declare const CUSTOM_LOG_SCHEMA_KEYWORDS: string[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DEBUG_MODE_LIMIT_STORED_ACTIONS = void 0; | ||
exports.CUSTOM_LOG_SCHEMA_KEYWORDS = exports.DEFAULT_SENSITIVE_TEXT = exports.SENSITIVE_KEYS = exports.DEBUG_MODE_LIMIT_STORED_ACTIONS = void 0; | ||
// Number of stored actions for the debug mode; | ||
exports.DEBUG_MODE_LIMIT_STORED_ACTIONS = 20; | ||
// List of keys that we need to check against to see if it needs to be hidden. | ||
exports.SENSITIVE_KEYS = [ | ||
'password', | ||
'Authorization', | ||
'confirmationPassword', | ||
'newPassword', | ||
'currentPassword', | ||
'idToken', | ||
'token' | ||
]; | ||
exports.DEFAULT_SENSITIVE_TEXT = '<sensitive>'; | ||
// Our common log schema relies on those custom keywords | ||
// @see https://ajv.js.org/strict-mode.html#json-schema-schemas | ||
exports.CUSTOM_LOG_SCHEMA_KEYWORDS = [ | ||
'destination', | ||
'bytes_read', | ||
'bytes_written', | ||
'connectivity', | ||
'downlink_kbps', | ||
'signal_strength', | ||
'uplink_kbps', | ||
'useragent_details' | ||
]; | ||
//# sourceMappingURL=constants.js.map |
@@ -1,2 +0,2 @@ | ||
import { StatusType } from '@datadog/browser-logs'; | ||
import { StatusType, LogsEvent } from '@datadog/browser-logs'; | ||
import { Context } from '@datadog/browser-core'; | ||
@@ -11,2 +11,6 @@ export { StatusType as LOGGER_LEVEL }; | ||
} | ||
export declare interface User extends Context { | ||
id?: number; | ||
company_id?: number; | ||
} | ||
export declare interface UserSession extends Context { | ||
@@ -32,8 +36,36 @@ company_id?: number; | ||
level: StatusType; | ||
version: string; | ||
environment: LOGGER_ENVIRONMENT; | ||
service: string; | ||
user: User; | ||
} | ||
export declare class Logger { | ||
level: StatusType; | ||
initialized: boolean; | ||
queue: Function[]; | ||
level: StatusType; | ||
init({ level, context, token }: LoggerInitOptions): void; | ||
logSchemaValidator: any; | ||
compiledLogSchema: any; | ||
init({ level, context, token, version, environment, service, user }: LoggerInitOptions): void; | ||
/** | ||
* Remove sensitive properties, add mandatory fields and validate the payload against the log schema. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
*/ | ||
beforeSendListener(log: LogsEvent, version: string, environment: string, service: string, user: User): void; | ||
/** | ||
* Every logs must include mandatory fields such as the environment or the version for instance. | ||
* This function adds all of them into the log payload. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
* @returns | ||
*/ | ||
addMandatoryFields(log: LogsEvent, version: string, environment: string, service: string, user: User): LogsEvent; | ||
setContext(context: UserContext): void; | ||
@@ -40,0 +72,0 @@ private logOrEnqueue; |
@@ -6,2 +6,6 @@ "use strict"; | ||
Object.defineProperty(exports, "LOGGER_LEVEL", { enumerable: true, get: function () { return browser_logs_1.StatusType; } }); | ||
const ajv_1 = require("ajv"); | ||
const ajv_formats_1 = require("ajv-formats"); | ||
const constants_1 = require("./constants"); | ||
const log_schema_1 = require("./constants/log_schema"); | ||
const utils_1 = require("./utils"); | ||
@@ -16,13 +20,2 @@ var LOGGER_ENVIRONMENT; | ||
})(LOGGER_ENVIRONMENT = exports.LOGGER_ENVIRONMENT || (exports.LOGGER_ENVIRONMENT = {})); | ||
// List of keys that we need to check against to see if it needs to be hidden. | ||
const SENSITIVE_KEYS = [ | ||
'password', | ||
'Authorization', | ||
'confirmationPassword', | ||
'newPassword', | ||
'currentPassword', | ||
'idToken', | ||
'token' | ||
]; | ||
const DEFAULT_SENSITIVE_TEXT = '<sensitive>'; | ||
class Logger { | ||
@@ -32,7 +25,11 @@ constructor() { | ||
this.queue = []; | ||
this.level = browser_logs_1.StatusType.info; | ||
} | ||
// Init SDK with user informations, token and level | ||
init({ level, context, token }) { | ||
init({ level, context, token, version, environment, service, user }) { | ||
const isDevelopment = context.user_session.environment === LOGGER_ENVIRONMENT.DEVELOPMENT; | ||
// Instantiate and configure Ajv schema validator | ||
this.logSchemaValidator = new ajv_1.default({ allErrors: false }); // options can be passed, e.g. {allErrors: true} | ||
(0, ajv_formats_1.default)(this.logSchemaValidator); | ||
this.logSchemaValidator.addVocabulary(constants_1.CUSTOM_LOG_SCHEMA_KEYWORDS); | ||
this.compiledLogSchema = this.logSchemaValidator.compile(log_schema_1.logSchema); | ||
browser_logs_1.datadogLogs.init({ | ||
@@ -42,3 +39,5 @@ clientToken: token, | ||
forwardErrorsToLogs: true, | ||
sampleRate: 100 | ||
forwardConsoleLogs: ['error', 'warn'], | ||
sampleRate: 100, | ||
beforeSend: log => this.beforeSendListener(log, version, environment, service, user) | ||
}); | ||
@@ -51,2 +50,48 @@ this.setContext(context); | ||
} | ||
/** | ||
* Remove sensitive properties, add mandatory fields and validate the payload against the log schema. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
*/ | ||
beforeSendListener(log, version, environment, service, user) { | ||
// TODO PH-7453: move the clean properties function here | ||
const logPayload = this.addMandatoryFields(log, version, environment, service, user); | ||
const valid = this.compiledLogSchema(logPayload); | ||
if (!valid) { | ||
console.log('An error occured while validating the log payload. For more information, please refer to https://aircall-product.atlassian.net/wiki/spaces/7SE/pages/1942061270/Distributed+Logging+and+Tracing+Best+Practices.', { | ||
error: this.compiledLogSchema.errors, | ||
logPayload | ||
}); | ||
} | ||
} | ||
/** | ||
* Every logs must include mandatory fields such as the environment or the version for instance. | ||
* This function adds all of them into the log payload. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
* @returns | ||
*/ | ||
addMandatoryFields(log, version, environment, service, user) { | ||
const currentDate = new Date(Date.now()); | ||
const datadogInternalContext = browser_logs_1.datadogLogs.getInternalContext(); | ||
const commonMandatoryFields = { | ||
version, | ||
env: environment, | ||
service, | ||
host: (datadogInternalContext === null || datadogInternalContext === void 0 ? void 0 : datadogInternalContext.session_id) || '', | ||
timestamp: currentDate.toISOString(), | ||
level: log.status, | ||
message: log.message, | ||
user | ||
}; | ||
return Object.assign(Object.assign({}, commonMandatoryFields), log); | ||
} | ||
// set logger context | ||
@@ -80,5 +125,5 @@ setContext(context) { | ||
} | ||
const keyContainsSensitiveData = (0, utils_1.isString)(key) && (0, utils_1.containsAValue)(key, SENSITIVE_KEYS); | ||
const keyContainsSensitiveData = (0, utils_1.isString)(key) && (0, utils_1.containsAValue)(key, constants_1.SENSITIVE_KEYS); | ||
if (keyContainsSensitiveData) { | ||
return DEFAULT_SENSITIVE_TEXT; | ||
return constants_1.DEFAULT_SENSITIVE_TEXT; | ||
} | ||
@@ -85,0 +130,0 @@ return value; |
{ | ||
"name": "@aircall/logger", | ||
"version": "2.6.0", | ||
"version": "2.7.0", | ||
"main": "dist/index.js", | ||
@@ -14,7 +14,9 @@ "types": "dist/index.d.ts", | ||
}, | ||
"gitHead": "2caf56dc15480449b52405ad7865d355f0d1247b", | ||
"gitHead": "662f36791e522dadaeb14a1263d39bf8819738c4", | ||
"dependencies": { | ||
"@datadog/browser-logs": "4.11.2", | ||
"@datadog/browser-logs": "4.17.1", | ||
"ajv": "8.11.0", | ||
"ajv-formats": "2.1.1", | ||
"redux": "4.2.0" | ||
} | ||
} |
// Number of stored actions for the debug mode; | ||
export const DEBUG_MODE_LIMIT_STORED_ACTIONS = 20; | ||
// List of keys that we need to check against to see if it needs to be hidden. | ||
export const SENSITIVE_KEYS = [ | ||
'password', | ||
'Authorization', | ||
'confirmationPassword', | ||
'newPassword', | ||
'currentPassword', | ||
'idToken', | ||
'token' | ||
]; | ||
export const DEFAULT_SENSITIVE_TEXT = '<sensitive>'; | ||
// Our common log schema relies on those custom keywords | ||
// @see https://ajv.js.org/strict-mode.html#json-schema-schemas | ||
export const CUSTOM_LOG_SCHEMA_KEYWORDS = [ | ||
'destination', | ||
'bytes_read', | ||
'bytes_written', | ||
'connectivity', | ||
'downlink_kbps', | ||
'signal_strength', | ||
'uplink_kbps', | ||
'useragent_details' | ||
]; |
@@ -9,8 +9,17 @@ jest.mock('@datadog/browser-logs'); | ||
const version = 'RELEASE'; | ||
const environment = LOGGER_ENVIRONMENT.TEST; | ||
const service = 'phone'; | ||
const user = { | ||
id: 0, | ||
company_id: 1 | ||
}; | ||
const host = 'session_id'; | ||
const user_session = { | ||
company_id: 0, | ||
device: 'DEVICE', | ||
environment: LOGGER_ENVIRONMENT.TEST, | ||
release: 'RELEASE', | ||
user_id: 0, | ||
environment, | ||
release: version, | ||
user_id: user.id, | ||
is_trial: true, | ||
@@ -40,3 +49,3 @@ tier_level: 'a', | ||
const context = { | ||
service: 'phone', | ||
service, | ||
user_session | ||
@@ -47,3 +56,7 @@ }; | ||
token, | ||
level: LOGGER_LEVEL.debug | ||
level: LOGGER_LEVEL.debug, | ||
version, | ||
environment, | ||
service, | ||
user | ||
}); | ||
@@ -53,8 +66,3 @@ | ||
expect(logger.initialized).toEqual(true); | ||
expect(datadogLogs.init).toHaveBeenCalledWith({ | ||
clientToken: token, | ||
datacenter: 'us', | ||
forwardErrorsToLogs: true, | ||
sampleRate: 100 | ||
}); | ||
expect(datadogLogs.init).toHaveBeenCalled(); | ||
expect(logger.setContext).toHaveBeenCalledWith(context); | ||
@@ -196,2 +204,77 @@ expect(datadogLogs.logger.setContext).toHaveBeenCalledWith(context); | ||
}); | ||
describe('addMandatoryFields', (): void => { | ||
it('should add mandatory fields', (): void => { | ||
jest.spyOn(datadogLogs, 'getInternalContext').mockReturnValue({ session_id: host }); | ||
//@ts-ignore | ||
Date.now = jest.fn(() => new Date(2022, 7, 24, 8, 32, 16)); | ||
const logPayload = { | ||
date: 1660119130, | ||
foo: 'BAR', | ||
bar: 10, | ||
message: 'message', | ||
status: StatusType.info, | ||
view: { url: 'https://phone.aircall-staging.com/' } | ||
}; | ||
const expected = { | ||
level: LOGGER_LEVEL.info, | ||
version, | ||
env: environment, | ||
host, | ||
service, | ||
timestamp: '2022-08-24T08:32:16.000Z', | ||
user, | ||
...logPayload | ||
}; | ||
expect( | ||
logger.addMandatoryFields(logPayload, version, environment, service, user) | ||
).toStrictEqual(expected); | ||
}); | ||
}); | ||
describe('beforeSendListener', (): void => { | ||
//@ts-ignore | ||
Date.now = jest.fn(() => new Date(2022, 7, 24, 8, 32, 16)); | ||
const logPayload = { | ||
date: 1660119130, | ||
foo: 'BAR', | ||
bar: 10, | ||
message: 'message', | ||
status: StatusType.info, | ||
view: { url: 'https://phone.aircall-staging.com/' } | ||
}; | ||
it('should add mandatory fields and validate schema', (): void => { | ||
jest.spyOn(logger, 'compiledLogSchema'); | ||
jest.spyOn(console, 'log'); | ||
const expected = { | ||
level: LOGGER_LEVEL.info, | ||
version, | ||
env: environment, | ||
host, | ||
service, | ||
timestamp: '2022-08-24T08:32:16.000Z', | ||
user, | ||
...logPayload | ||
}; | ||
logger.beforeSendListener(logPayload, version, environment, service, user); | ||
expect(logger.compiledLogSchema).toHaveBeenCalledWith(expected); | ||
expect(console.log).toBeCalledTimes(0); | ||
}); | ||
it('should add mandatory fields and log an error if the schema is invalid', (): void => { | ||
jest.spyOn(logger, 'compiledLogSchema'); | ||
jest.spyOn(console, 'log'); | ||
jest.spyOn(logger, 'addMandatoryFields').mockReturnValue(logPayload); | ||
logger.beforeSendListener(logPayload, version, environment, service, user); | ||
expect(logger.compiledLogSchema).toHaveBeenCalledWith(logPayload); | ||
expect(console.log).toBeCalledTimes(1); | ||
}); | ||
}); | ||
}); |
@@ -1,4 +0,8 @@ | ||
import { datadogLogs as sdk, StatusType, HandlerType } from '@datadog/browser-logs'; | ||
import { datadogLogs as sdk, StatusType, HandlerType, LogsEvent } from '@datadog/browser-logs'; | ||
import { Context } from '@datadog/browser-core'; | ||
import Ajv from 'ajv'; | ||
import addFormats from 'ajv-formats'; | ||
import { CUSTOM_LOG_SCHEMA_KEYWORDS, SENSITIVE_KEYS, DEFAULT_SENSITIVE_TEXT } from './constants'; | ||
import { logSchema } from './constants/log_schema'; | ||
import { containsAValue, isString, deepMap, isObject, isArray } from './utils'; | ||
@@ -16,2 +20,27 @@ | ||
// Corresponds to the required properties for all logs across Aircall services and apps | ||
interface MandatoryFields { | ||
version: string; | ||
env: string; | ||
service: string; | ||
host: string; | ||
timestamp: string; // date-time format | ||
level: StatusType; | ||
message: string; | ||
user?: User; | ||
} | ||
export declare interface User extends Context { | ||
id?: number; | ||
company_id?: number; | ||
// cti_name?: string; | ||
// device: string; | ||
// electron_version?: string; | ||
// environment: LOGGER_ENVIRONMENT; | ||
// release: string; | ||
// is_trial?: boolean; | ||
// tier_level?: string; | ||
// provider?: string; | ||
} | ||
export declare interface UserSession extends Context { | ||
@@ -39,26 +68,34 @@ company_id?: number; | ||
level: StatusType; | ||
version: string; | ||
environment: LOGGER_ENVIRONMENT; | ||
service: string; | ||
user: User; | ||
} | ||
// List of keys that we need to check against to see if it needs to be hidden. | ||
const SENSITIVE_KEYS = [ | ||
'password', | ||
'Authorization', | ||
'confirmationPassword', | ||
'newPassword', | ||
'currentPassword', | ||
'idToken', | ||
'token' | ||
]; | ||
const DEFAULT_SENSITIVE_TEXT = '<sensitive>'; | ||
export class Logger { | ||
public level: StatusType; | ||
public initialized = false; | ||
public queue: Function[] = []; | ||
public level: StatusType = StatusType.info; | ||
public logSchemaValidator: any; | ||
public compiledLogSchema: any; | ||
// Init SDK with user informations, token and level | ||
public init({ level, context, token }: LoggerInitOptions): void { | ||
public init({ | ||
level, | ||
context, | ||
token, | ||
version, | ||
environment, | ||
service, | ||
user | ||
}: LoggerInitOptions): void { | ||
const isDevelopment: boolean = | ||
context.user_session.environment === LOGGER_ENVIRONMENT.DEVELOPMENT; | ||
// Instantiate and configure Ajv schema validator | ||
this.logSchemaValidator = new Ajv({ allErrors: false }); // options can be passed, e.g. {allErrors: true} | ||
addFormats(this.logSchemaValidator); | ||
this.logSchemaValidator.addVocabulary(CUSTOM_LOG_SCHEMA_KEYWORDS); | ||
this.compiledLogSchema = this.logSchemaValidator.compile(logSchema); | ||
sdk.init({ | ||
@@ -68,3 +105,5 @@ clientToken: token, | ||
forwardErrorsToLogs: true, | ||
sampleRate: 100 | ||
forwardConsoleLogs: ['error', 'warn'], // TODO PH-7453: do we want to forward warn logs as well? | ||
sampleRate: 100, | ||
beforeSend: log => this.beforeSendListener(log, version, environment, service, user) | ||
}); | ||
@@ -80,2 +119,68 @@ | ||
/** | ||
* Remove sensitive properties, add mandatory fields and validate the payload against the log schema. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
*/ | ||
public beforeSendListener( | ||
log: LogsEvent, | ||
version: string, | ||
environment: string, | ||
service: string, | ||
user: User | ||
): void { | ||
// TODO PH-7453: move the clean properties function here | ||
const logPayload = this.addMandatoryFields(log, version, environment, service, user); | ||
const valid = this.compiledLogSchema(logPayload); | ||
if (!valid) { | ||
console.log( | ||
'An error occured while validating the log payload. For more information, please refer to https://aircall-product.atlassian.net/wiki/spaces/7SE/pages/1942061270/Distributed+Logging+and+Tracing+Best+Practices.', | ||
{ | ||
error: this.compiledLogSchema.errors, | ||
logPayload | ||
} | ||
); | ||
} | ||
} | ||
/** | ||
* Every logs must include mandatory fields such as the environment or the version for instance. | ||
* This function adds all of them into the log payload. | ||
* | ||
* @param log | ||
* @param version | ||
* @param environment | ||
* @param service | ||
* @param user | ||
* @returns | ||
*/ | ||
public addMandatoryFields( | ||
log: LogsEvent, | ||
version: string, | ||
environment: string, | ||
service: string, | ||
user: User | ||
): LogsEvent { | ||
const currentDate = new Date(Date.now()); | ||
const datadogInternalContext = sdk.getInternalContext(); | ||
const commonMandatoryFields: MandatoryFields = { | ||
version, | ||
env: environment, | ||
service, | ||
host: datadogInternalContext?.session_id || '', | ||
timestamp: currentDate.toISOString(), | ||
level: log.status, | ||
message: log.message, | ||
user | ||
}; | ||
return { ...commonMandatoryFields, ...log }; | ||
} | ||
// set logger context | ||
@@ -82,0 +187,0 @@ public setContext(context: UserContext) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
137325
36
3065
4
1
+ Addedajv@8.11.0
+ Addedajv-formats@2.1.1
+ Added@datadog/browser-core@4.17.1(transitive)
+ Added@datadog/browser-logs@4.17.1(transitive)
+ Addedajv@8.11.0(transitive)
+ Addedajv-formats@2.1.1(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedjson-schema-traverse@1.0.0(transitive)
+ Addedpunycode@2.3.1(transitive)
+ Addedrequire-from-string@2.0.2(transitive)
+ Addeduri-js@4.4.1(transitive)
- Removed@datadog/browser-core@4.11.2(transitive)
- Removed@datadog/browser-logs@4.11.2(transitive)
Updated@datadog/browser-logs@4.17.1