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

New API webvitalsInCustomEvent to track webvitals in custom event. #125

Merged
merged 5 commits into from
Aug 29, 2023
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
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
Loading