Skip to content

Commit

Permalink
Merge pull request #125 from instana/large_webvitals
Browse files Browse the repository at this point in the history
New API webvitalsInCustomEvent to track webvitals in custom event.
  • Loading branch information
lisr authored Aug 29, 2023
2 parents 329ec1f + 02f57be commit 118b99e
Show file tree
Hide file tree
Showing 15 changed files with 689 additions and 177 deletions.
20 changes: 4 additions & 16 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,9 @@ module.exports = {
ErrorLike: false
},
rules: {
indent: [
'error',
2
],
'linebreak-style': [
'error',
'unix'
],
quotes: [
'error',
'single'
],
semi: [
'error',
'always'
]
indent: ['error', 2, {SwitchCase: 1}],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always']
}
};
6 changes: 4 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"printWidth": 120,
"singleQuote": true
}
"singleQuote": true,
"quoteProps": "preserve",
"bracketSpacing": false
}
244 changes: 124 additions & 120 deletions lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,126 +10,130 @@ import vars from './vars';

export function processCommand(command: any[]): any {
switch (command[0]) {
case 'apiKey':
vars.apiKey = command[1];
break;
case 'key':
vars.apiKey = command[1];
break;
case 'reportingUrl':
vars.reportingUrl = command[1];
break;
case 'meta':
vars.meta[command[1]] = command[2];
break;
case 'traceId':
vars.pageLoadBackendTraceId = command[1];
break;
case 'ignoreUrls':
if (DEBUG) {
validateRegExpArray('ignoreUrls', command[1]);
}
vars.ignoreUrls = command[1];
break;
case 'ignoreErrorMessages':
if (DEBUG) {
validateRegExpArray('ignoreErrorMessages', command[1]);
}
vars.ignoreErrorMessages = command[1];
break;
case 'allowedOrigins':
case 'whitelistedOrigins': // a deprecated alias for allowedOrigins
if (DEBUG) {
validateRegExpArray('allowedOrigins', command[1]);
}
vars.allowedOrigins = command[1];
break;
case 'ignoreUserTimings':
if (DEBUG) {
validateRegExpArray('ignoreUserTimings', command[1]);
}
vars.ignoreUserTimings = command[1];
break;
case 'xhrTransmissionTimeout':
vars.xhrTransmissionTimeout = command[1];
break;
case 'page':
setPage(command[1]);
break;
case 'ignorePings':
vars.ignorePings = command[1];
break;
case 'reportError':
reportError(command[1], command[2]);
break;
case 'wrapEventHandlers':
vars.wrapEventHandlers = command[1];
break;
case 'wrapTimers':
vars.wrapTimers = command[1];
break;
case 'getPageLoadId':
return vars.pageLoadTraceId;
case 'user':
if (command[1]) {
vars.userId = String(command[1]).substring(0, 128);
}
if (command[2]) {
vars.userName = String(command[2]).substring(0, 128);
}
if (command[3]) {
vars.userEmail = String(command[3]).substring(0, 128);
}
break;
case 'reportEvent':
reportCustomEvent(command[1], command[2]);
break;
case 'beaconBatchingTime':
vars.beaconBatchingTime = command[1];
break;
case 'maxWaitForResourceTimingsMillis':
vars.maxWaitForResourceTimingsMillis = command[1];
break;
case 'maxMaitForPageLoadMetricsMillis':
vars.maxMaitForPageLoadMetricsMillis = command[1];
break;
case 'trackSessions':
trackSessions(command[1], command[2]);
break;
case 'terminateSession':
terminateSession();
break;
case 'urlsToCheckForGraphQlInsights':
if (DEBUG) {
validateRegExpArray('urlsToCheckForGraphQlInsights', command[1]);
}
vars.urlsToCheckForGraphQlInsights = command[1];
break;
case 'secrets':
if (DEBUG) {
validateRegExpArray('secrets', command[1]);
}
vars.secrets = command[1];
break;
case 'captureHeaders':
if (DEBUG) {
validateRegExpArray('captureHeaders', command[1]);
}
vars.headersToCapture = command[1];
break;
case 'debug':
// Not using an if (DEBUG) {…} wrapper nor the integrated logging
// facilities to keep the end-user impact as low as possible.
console.log({vars});
break;
case 'reportingBackends':
processReportingBackends(command[1]);
break;
default:
if (DEBUG) {
warn('Unsupported command: ' + command[0]);
}
break;
case 'apiKey':
vars.apiKey = command[1];
break;
case 'key':
vars.apiKey = command[1];
break;
case 'reportingUrl':
vars.reportingUrl = command[1];
break;
case 'meta':
vars.meta[command[1]] = command[2];
break;
case 'traceId':
vars.pageLoadBackendTraceId = command[1];
break;
case 'ignoreUrls':
if (DEBUG) {
validateRegExpArray('ignoreUrls', command[1]);
}
vars.ignoreUrls = command[1];
break;
case 'ignoreErrorMessages':
if (DEBUG) {
validateRegExpArray('ignoreErrorMessages', command[1]);
}
vars.ignoreErrorMessages = command[1];
break;
case 'allowedOrigins':
case 'whitelistedOrigins': // a deprecated alias for allowedOrigins
if (DEBUG) {
validateRegExpArray('allowedOrigins', command[1]);
}
vars.allowedOrigins = command[1];
break;
case 'ignoreUserTimings':
if (DEBUG) {
validateRegExpArray('ignoreUserTimings', command[1]);
}
vars.ignoreUserTimings = command[1];
break;
case 'xhrTransmissionTimeout':
vars.xhrTransmissionTimeout = command[1];
break;
case 'page':
setPage(command[1]);
break;
case 'ignorePings':
vars.ignorePings = command[1];
break;
case 'reportError':
reportError(command[1], command[2]);
break;
case 'wrapEventHandlers':
vars.wrapEventHandlers = command[1];
break;
case 'wrapTimers':
vars.wrapTimers = command[1];
break;
case 'getPageLoadId':
return vars.pageLoadTraceId;
case 'user':
if (command[1]) {
vars.userId = String(command[1]).substring(0, 128);
}
if (command[2]) {
vars.userName = String(command[2]).substring(0, 128);
}
if (command[3]) {
vars.userEmail = String(command[3]).substring(0, 128);
}
break;
case 'reportEvent':
reportCustomEvent(command[1], command[2]);
break;
case 'beaconBatchingTime':
vars.beaconBatchingTime = command[1];
break;
case 'maxWaitForResourceTimingsMillis':
vars.maxWaitForResourceTimingsMillis = command[1];
break;
case 'maxMaitForPageLoadMetricsMillis':
vars.maxMaitForPageLoadMetricsMillis = command[1];
break;
case 'trackSessions':
trackSessions(command[1], command[2]);
break;
case 'terminateSession':
terminateSession();
break;
case 'urlsToCheckForGraphQlInsights':
if (DEBUG) {
validateRegExpArray('urlsToCheckForGraphQlInsights', command[1]);
}
vars.urlsToCheckForGraphQlInsights = command[1];
break;
case 'secrets':
if (DEBUG) {
validateRegExpArray('secrets', command[1]);
}
vars.secrets = command[1];
break;
case 'captureHeaders':
if (DEBUG) {
validateRegExpArray('captureHeaders', command[1]);
}
vars.headersToCapture = command[1];
break;
case 'debug':
// Not using an if (DEBUG) {…} wrapper nor the integrated logging
// facilities to keep the end-user impact as low as possible.
// eslint-disable-next-line no-console
console.log({vars});
break;
case 'reportingBackends':
processReportingBackends(command[1]);
break;
case 'webvitalsInCustomEvent':
vars.webvitalsInCustomEvent = command[1];
break;
default:
if (DEBUG) {
warn('Unsupported command: ' + command[0]);
}
break;
}
}

Expand Down
9 changes: 6 additions & 3 deletions lib/customEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,19 @@ function enrich(beacon: CustomEventBeacon, opts: CustomEventOptions) {

if (typeof opts['duration'] === 'number' && !isNaN(opts['duration'])) {
beacon['d'] = opts['duration'];
// add Math.round since duration could be a float
// $FlowFixMe: We know that both properties are numbers. Flow thinks they are strings because we access them via […].
beacon['ts'] = beacon['ts'] - opts['duration'];
beacon['ts'] = Math.round(beacon['ts'] - opts['duration']);
}

if (typeof opts['timestamp'] === 'number' && !isNaN(opts['timestamp'])) {
beacon['ts'] = opts['timestamp'];
}

if (typeof opts['backendTraceId'] === 'string'
&& (opts['backendTraceId'].length === 16 || opts['backendTraceId'].length === 32)) {
if (
typeof opts['backendTraceId'] === 'string' &&
(opts['backendTraceId'].length === 16 || opts['backendTraceId'].length === 32)
) {
beacon['bt'] = opts['backendTraceId'];
}

Expand Down
16 changes: 12 additions & 4 deletions lib/vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,16 @@ const defaultVars: {
// Changes backends which beacons will be send.
// Change via
// eum('reportingBackends', [{reportingUrl: '//eum.example.com', key: 'key'}]);
reportingBackends: ReportingBackend[]
reportingBackends: ReportingBackend[],

// Whether or not weasel should generate dedicated custom events on webvital metrics
// these custom events is more reliable than webvitals in PageLoad beacon, as we
// will not wait for so long in PageLoad beacon, with custom events, these metrics
// could be reported on demand at any time.
//
// Set via:
// eum('webvitalsInCustomEvent', false)
webvitalsInCustomEvent: boolean
} = {
nameOfLongGlobal: 'EumObject',
trackingSnippetVersion: null,
Expand Down Expand Up @@ -312,7 +320,7 @@ const defaultVars: {
sessionStorageKey: 'session',
defaultSessionInactivityTimeoutMillis: 1000 * 60 * 60 * 3,
defaultSessionTerminationTimeoutMillis: 1000 * 60 * 60 * 6,
maxAllowedSessionTimeoutMillis: 1000 * 60 * 60 * 24,
maxAllowedSessionTimeoutMillis: 1000 * 60 * 60 * 24,
// The default ignore rules cover specific React and Angular patterns:
//
// React has a whole lot of user timings. Luckily all of them start with
Expand All @@ -329,8 +337,8 @@ const defaultVars: {
urlsToCheckForGraphQlInsights: [/\/graphql/i],
secrets: [/key/i, /password/i, /secret/i],
headersToCapture: [],
reportingBackends: []

reportingBackends: [],
webvitalsInCustomEvent: false
};

export default defaultVars;
29 changes: 24 additions & 5 deletions lib/webVitals.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// @flow

// $FlowFixMe Flow doesn't find the file. Let's ignore this for now.
import { getCLS, getLCP, getFID } from 'web-vitals/dist/web-vitals.js';
import {getCLS, getLCP, getFID} from 'web-vitals/dist/web-vitals.js';
import type {PageLoadBeacon} from './types';
import {pageLoadStartTimestamp} from './timings';
import {reportCustomEvent} from './customEvents';
import vars from './vars';

interface Metric {
name: 'CLS' | 'FID' | 'LCP',
name: 'CLS' | 'FID' | 'LCP';

value: number
value: number;
id?: string;
}

function reportExtraMetrics(metric: Metric) {
if (!vars.webvitalsInCustomEvent) {
return;
}

// $FlowFixMe: Flow cannot detect that this is a proper usage of the function. We have to write it this way because of the Closure compiler advanced mode.
reportCustomEvent('instana-webvitals-' + metric.name, {
'timestamp': pageLoadStartTimestamp + Math.round(metric.value),
'duration': metric.value,
'meta': {
'id': metric.id
}
});
}

export function addWebVitals(beacon: PageLoadBeacon) {
Expand All @@ -23,11 +42,11 @@ export function addWebVitals(beacon: PageLoadBeacon) {

function onMetric(metric: Metric) {
beacon['t_' + metric.name.toLocaleLowerCase()] = Math.round(metric.value);
reportExtraMetrics(metric);
}

function onMetricWithoutRounding(metric: Metric) {
beacon['t_' + metric.name.toLocaleLowerCase()] = metric.value;
reportExtraMetrics(metric);
}
}


Loading

0 comments on commit 118b99e

Please sign in to comment.