diff --git a/react-packager/src/Activity/Types.js b/react-packager/src/Activity/Types.js index 47332bf1..cc996c74 100644 --- a/react-packager/src/Activity/Types.js +++ b/react-packager/src/Activity/Types.js @@ -11,16 +11,26 @@ */ 'use strict'; -export type EventOptions = { +export type Options = { telemetric?: boolean, silent?: boolean, + displayFields?: Array | true, }; -export type Event = { - id: number, - startTimeStamp: [number, number], - durationMs?: number, - name: string, - data?: any, - options: EventOptions, +type EventFieldDescriptor = { + type: 'int' | 'normal', + value: number | string | boolean, +}; + +export type NormalisedEventData = {[key: string]: EventFieldDescriptor}; + +export type EventData = {[key: string]: number | string | boolean}; + +export type Event = { + data: NormalisedEventData, + durationMs?: number, + id: number, + name: string, + options: Options, + startTimeStamp: [number, number], }; diff --git a/react-packager/src/Activity/__tests__/Activity-test.js b/react-packager/src/Activity/__tests__/Activity-test.js index 5edadd98..a8153426 100644 --- a/react-packager/src/Activity/__tests__/Activity-test.js +++ b/react-packager/src/Activity/__tests__/Activity-test.js @@ -32,7 +32,7 @@ describe('Activity', () => { const EVENT_NAME = 'EVENT_NAME'; const DATA = {someData: 42}; - Activity.startEvent(EVENT_NAME, DATA); + Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']}); jest.runOnlyPendingTimers(); // eslint-disable-next-line no-console-disallow @@ -41,7 +41,7 @@ describe('Activity', () => { const consoleMsg = console.log.mock.calls[0][0]; expect(consoleMsg).toContain('START'); expect(consoleMsg).toContain(EVENT_NAME); - expect(consoleMsg).toContain(JSON.stringify(DATA)); + expect(consoleMsg).toContain('someData: 42'); }); it('does not write the "START" phase of silent events to the console', () => { @@ -61,7 +61,7 @@ describe('Activity', () => { const EVENT_NAME = 'EVENT_NAME'; const DATA = {someData: 42}; - const eventID = Activity.startEvent(EVENT_NAME, DATA); + const eventID = Activity.startEvent(EVENT_NAME, DATA, {displayFields: ['someData']}); Activity.endEvent(eventID); jest.runOnlyPendingTimers(); @@ -71,7 +71,7 @@ describe('Activity', () => { const consoleMsg = console.log.mock.calls[1][0]; expect(consoleMsg).toContain('END'); expect(consoleMsg).toContain(EVENT_NAME); - expect(consoleMsg).toContain(JSON.stringify(DATA)); + expect(consoleMsg).toContain('someData: 42'); }); it('does not write the "END" phase of silent events to the console', () => { diff --git a/react-packager/src/Activity/formatData.js b/react-packager/src/Activity/formatData.js new file mode 100644 index 00000000..a4c0f586 --- /dev/null +++ b/react-packager/src/Activity/formatData.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + * + */ +'use strict'; + +import type {Event} from './Types'; + +function getDataString(event: Event): string { + const {options, data} = event; + const {displayFields} = options; + + if (!Object.keys(data).length || + !(Array.isArray(displayFields) || displayFields === true)) { + return ''; + } + + const fields = Array.isArray(displayFields) ? displayFields : Object.keys(data); + const dataList = fields.map(field => { + if (data[field] === undefined) { + throw new Error(`"${field}" is not defined for event ""${event.name}"!`); + } + return `${field}: ${data[field].value.toString()}`; + }); + + let dataString = dataList.join(' | '); + + if (dataString) { + dataString = ` ${dataString} `; + } + + return dataString; +} + +module.exports = getDataString; diff --git a/react-packager/src/Activity/index.js b/react-packager/src/Activity/index.js index 2339090f..4ad5ad05 100644 --- a/react-packager/src/Activity/index.js +++ b/react-packager/src/Activity/index.js @@ -11,11 +11,12 @@ */ 'use strict'; -import type {EventOptions} from './Types'; -import type {Event} from './Types'; +import type {Event, EventData, Options} from './Types'; const chalk = require('chalk'); const events = require('events'); +const formatData = require('./formatData'); +const normaliseEventData = require('./normaliseEventData'); let ENABLED = true; let UUID = 1; @@ -23,24 +24,21 @@ let UUID = 1; const EVENT_INDEX: {[key: number]: Event} = Object.create(null); const EVENT_EMITTER = new events.EventEmitter(); -function startEvent( - name: string, - data: any = null, - options?: EventOptions = {}, -): number { +function startEvent(name: string, data: EventData = {}, options: Options = {}): number { if (name == null) { throw new Error('No event name specified!'); } const id = UUID++; EVENT_INDEX[id] = { + data: normaliseEventData(data), id, - startTimeStamp: process.hrtime(), - name, - data, options, + name, + startTimeStamp: process.hrtime(), }; logEvent(id, 'startEvent'); + return id; } @@ -74,12 +72,11 @@ function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void { const { name, durationMs, - data, options, } = event; const logTimeStamp = new Date().toLocaleString(); - const dataString = data ? ': ' + JSON.stringify(data) : ''; + const dataString = formatData(event); const {telemetric, silent} = options; switch (phase) { @@ -94,15 +91,15 @@ function logEvent(id: number, phase: 'startEvent' | 'endEvent'): void { if (!silent) { // eslint-disable-next-line no-console-disallow console.log( - chalk.dim(`[${logTimeStamp}] ${name}${dataString} `) + - (telemetric ? chalk.reset.cyan(`(${+durationMs}ms)`) : chalk.dim(`(${+durationMs}ms)`)) + chalk.dim(`[${logTimeStamp}] ${name}${dataString}`) + + (telemetric ? chalk.reset.cyan(` (${+durationMs}ms)`) : chalk.dim(` (${+durationMs}ms)`)) ); } forgetEvent(id); break; default: - throw new Error('Unexpected scheduled event type: ' + name); + throw new Error(`Unexpected event phase "${phase}"!`); } } diff --git a/react-packager/src/Activity/normaliseEventData.js b/react-packager/src/Activity/normaliseEventData.js new file mode 100644 index 00000000..df0da238 --- /dev/null +++ b/react-packager/src/Activity/normaliseEventData.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + * + */ +'use strict'; + +import type {EventData, NormalisedEventData} from './Types'; + +function normaliseEventData(eventData: EventData): NormalisedEventData { + if (!eventData) { + return {}; + } + + const normalisedEventData = {}; + + Object.keys(eventData).forEach(field => { + const value = eventData[field]; + let type; + + if (typeof value === 'string' || typeof value === 'boolean') { + type = 'normal'; + } else if (typeof value === 'number') { + type = 'int'; + } else { + throw new Error(`Disallowed value for event field "${field}""!`); + } + + normalisedEventData[field] = {type, value}; + }); + + return normalisedEventData; +} + +module.exports = normaliseEventData; diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index f6ed1ed1..d1c2dbc6 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -366,11 +366,12 @@ class Bundler { }) { const findEventId = Activity.startEvent( 'Transforming modules', - null, + { + entry_point: entryFile, + environment: dev ? 'dev' : 'prod', + }, { telemetric: true, - entryPoint: entryFile, - details: dev ? 'dev' : 'prod', }, ); const modulesByName = Object.create(null); diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index 9a5619e5..e3f64e5a 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -114,7 +114,9 @@ class Transformer { debug('transforming file', fileName); const transformEventId = Activity.startEvent( 'Transforming file', - fileName, + { + file_name: fileName, + }, { telemetric: true, silent: true, diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index a9153d27..3778289e 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -499,7 +499,15 @@ class Server { _processAssetsRequest(req, res) { const urlObj = url.parse(decodeURI(req.url), true); const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); - const assetEvent = Activity.startEvent('Processing asset request', {asset: assetPath[1]}); + const assetEvent = Activity.startEvent( + 'Processing asset request', + { + asset: assetPath[1], + }, + { + displayFields: true, + }, + ); this._assetServer.get(assetPath[1], urlObj.query.platform) .then( data => { @@ -538,10 +546,11 @@ class Server { Activity.startEvent( 'Updating existing bundle', { - outdatedModules: outdated.size, + outdated_modules: outdated.size, }, { telemetric: true, + displayFields: true, }, ); debug('Attempt to update existing bundle'); @@ -650,11 +659,11 @@ class Server { 'Requesting bundle', { url: req.url, + entry_point: options.entryFile, }, { telemetric: true, - entryPoint: options.entryFile, - details: req.url, + displayFields: ['url'], }, );