@bugsnag/browser-performance
Advanced tools
Comparing version 0.3.0 to 1.0.0
@@ -0,1 +1,2 @@ | ||
import { getPermittedAttributes } from '../send-page-attributes.js'; | ||
import { instrumentPageLoadPhaseSpans } from './page-load-phase-spans.js'; | ||
@@ -27,2 +28,3 @@ | ||
const span = this.spanFactory.startSpan('[FullPageLoad]', { startTime: 0, parentContext: null }); | ||
const permittedAttributes = getPermittedAttributes(configuration.sendPageAttributes); | ||
const url = new URL(this.location.href); | ||
@@ -37,6 +39,9 @@ this.onSettle((endTime) => { | ||
span.setAttribute('bugsnag.span.category', 'full_page_load'); | ||
span.setAttribute('bugsnag.browser.page.referrer', this.document.referrer); | ||
span.setAttribute('bugsnag.browser.page.title', this.document.title); | ||
span.setAttribute('bugsnag.browser.page.url', url.toString()); | ||
span.setAttribute('bugsnag.browser.page.route', route); | ||
if (permittedAttributes.referrer) | ||
span.setAttribute('bugsnag.browser.page.referrer', this.document.referrer); | ||
if (permittedAttributes.title) | ||
span.setAttribute('bugsnag.browser.page.title', this.document.title); | ||
if (permittedAttributes.url) | ||
span.setAttribute('bugsnag.browser.page.url', url.toString()); | ||
this.webVitals.attachTo(span); | ||
@@ -43,0 +48,0 @@ this.spanFactory.endSpan(span, endTime); |
@@ -0,1 +1,4 @@ | ||
import { defaultNetworkRequestCallback } from '../network-request-callback.js'; | ||
const permittedPrefixes = ['http://', 'https://', '/', './', '../']; | ||
class NetworkRequestPlugin { | ||
@@ -7,9 +10,18 @@ constructor(spanFactory, fetchTracker, xhrTracker) { | ||
this.configEndpoint = ''; | ||
this.networkRequestCallback = defaultNetworkRequestCallback; | ||
this.logger = { debug: console.debug, warn: console.warn, info: console.info, error: console.error }; | ||
this.trackRequest = (startContext) => { | ||
if (!this.shouldTrackRequest(startContext)) | ||
return; | ||
const networkRequestInfo = this.networkRequestCallback({ url: startContext.url, type: startContext.type }); | ||
if (!networkRequestInfo) | ||
return; | ||
if (typeof networkRequestInfo.url !== 'string') { | ||
this.logger.warn(`expected url to be a string following network request callback, got ${typeof networkRequestInfo.url}`); | ||
return; | ||
} | ||
const span = this.spanFactory.startSpan(`[HTTP]/${startContext.method.toUpperCase()}`, { startTime: startContext.startTime, makeCurrentContext: false }); | ||
span.setAttribute('bugsnag.span.category', 'network'); | ||
span.setAttribute('http.url', startContext.url); | ||
span.setAttribute('http.method', startContext.method); | ||
span.setAttribute('http.url', networkRequestInfo.url); | ||
return (endContext) => { | ||
@@ -24,2 +36,3 @@ if (endContext.state === 'success') { | ||
configure(configuration) { | ||
this.logger = configuration.logger; | ||
if (configuration.autoInstrumentNetworkRequests) { | ||
@@ -29,6 +42,7 @@ this.configEndpoint = configuration.endpoint; | ||
this.fetchTracker.onStart(this.trackRequest); | ||
this.networkRequestCallback = configuration.networkRequestCallback; | ||
} | ||
} | ||
shouldTrackRequest(startContext) { | ||
return startContext.url !== this.configEndpoint; | ||
return startContext.url !== this.configEndpoint && permittedPrefixes.some((prefix) => startContext.url.startsWith(prefix)); | ||
} | ||
@@ -35,0 +49,0 @@ } |
@@ -44,5 +44,19 @@ function getHttpVersion(protocol) { | ||
if (parentContext) { | ||
const url = new URL(entry.name); | ||
url.search = ''; | ||
const name = url.href; | ||
const networkRequestInfo = configuration.networkRequestCallback({ url: entry.name, type: entry.initiatorType }); | ||
if (!networkRequestInfo) | ||
return; | ||
if (typeof networkRequestInfo.url !== 'string') { | ||
configuration.logger.warn(`expected url to be a string following network request callback, got ${typeof networkRequestInfo.url}`); | ||
return; | ||
} | ||
let name = ''; | ||
try { | ||
const url = new URL(networkRequestInfo.url); | ||
url.search = ''; | ||
name = url.href; | ||
} | ||
catch (err) { | ||
configuration.logger.warn(`Unable to parse URL returned from networkRequestCallback: ${networkRequestInfo.url}`); | ||
return; | ||
} | ||
const span = this.spanFactory.startSpan(`[ResourceLoad]${name}`, { | ||
@@ -54,3 +68,3 @@ parentContext, | ||
span.setAttribute('bugsnag.span.category', 'resource_load'); | ||
span.setAttribute('http.url', entry.name); | ||
span.setAttribute('http.url', networkRequestInfo.url); | ||
const httpFlavor = getHttpVersion(entry.nextHopProtocol); | ||
@@ -57,0 +71,0 @@ if (httpFlavor) { |
import { validateSpanOptions, isString, coreSpanOptionSchema } from '@bugsnag/core-performance'; | ||
import { getPermittedAttributes } from '../send-page-attributes.js'; | ||
import { defaultRouteResolver } from '../default-routing-provider.js'; | ||
@@ -24,3 +26,5 @@ // exclude isFirstClass from the route change option schema | ||
return; | ||
let previousRoute = configuration.routingProvider.resolveRoute(new URL(this.location.href)); | ||
const previousUrl = new URL(this.location.href); | ||
let previousRoute = configuration.routingProvider.resolveRoute(previousUrl) || defaultRouteResolver(previousUrl); | ||
const permittedAttributes = getPermittedAttributes(configuration.sendPageAttributes); | ||
configuration.routingProvider.listenForRouteChanges((url, trigger, options) => { | ||
@@ -49,3 +53,3 @@ let absoluteUrl; | ||
const cleanOptions = validateSpanOptions('[RouteChange]', routeChangeSpanOptions, routeChangeSpanOptionSchema, configuration.logger); | ||
const route = configuration.routingProvider.resolveRoute(absoluteUrl); | ||
const route = configuration.routingProvider.resolveRoute(absoluteUrl) || defaultRouteResolver(absoluteUrl); | ||
// update the span name using the validated route | ||
@@ -56,5 +60,6 @@ cleanOptions.name += route; | ||
span.setAttribute('bugsnag.browser.page.route', route); | ||
span.setAttribute('bugsnag.browser.page.url', url.toString()); | ||
span.setAttribute('bugsnag.browser.page.previous_route', previousRoute); | ||
span.setAttribute('bugsnag.browser.page.route_change.trigger', cleanOptions.options.trigger); | ||
if (permittedAttributes.url) | ||
span.setAttribute('bugsnag.browser.page.url', url.toString()); | ||
previousRoute = route; | ||
@@ -66,3 +71,4 @@ return { | ||
end: (endTime) => { | ||
span.setAttribute('bugsnag.browser.page.title', this.document.title); | ||
if (permittedAttributes.title) | ||
span.setAttribute('bugsnag.browser.page.title', this.document.title); | ||
this.spanFactory.toPublicApi(span).end(endTime); | ||
@@ -69,0 +75,0 @@ } |
@@ -12,14 +12,14 @@ import { createClient } from '@bugsnag/core-performance'; | ||
import idGenerator from './id-generator.js'; | ||
import createOnSettle from './on-settle/index.js'; | ||
import makeBrowserPersistence from './persistence.js'; | ||
import createOnSettle from './on-settle/index.js'; | ||
import createFetchRequestTracker from './request-tracker/request-tracker-fetch.js'; | ||
import createXmlHttpRequestTracker from './request-tracker/request-tracker-xhr.js'; | ||
import createResourceAttributesSource from './resource-attributes-source.js'; | ||
import createSpanAttributesSource from './span-attributes-source.js'; | ||
import { createSpanAttributesSource } from './span-attributes-source.js'; | ||
import { WebVitals } from './web-vitals.js'; | ||
const backgroundingListener = createBrowserBackgroundingListener(document); | ||
const spanAttributesSource = createSpanAttributesSource(document); | ||
const clock = createClock(performance, backgroundingListener); | ||
const persistence = makeBrowserPersistence(window); | ||
const spanAttributesSource = createSpanAttributesSource(document.title, window.location.href); | ||
const resourceAttributesSource = createResourceAttributesSource(navigator, persistence); | ||
@@ -26,0 +26,0 @@ const fetchRequestTracker = createFetchRequestTracker(window, clock); |
@@ -5,14 +5,22 @@ import { millisecondsToNanoseconds } from '@bugsnag/core-performance'; | ||
const MAX_CLOCK_DRIFT_MS = 300000; | ||
function recalculateTimeOrigin(timeOrigin, performance) { | ||
// if the machine has been sleeping the monatomic clock used by performance.now() may have been paused, | ||
// so we need to check if this has drifted significantly from Date.now() | ||
// if the drift is > 5 minutes re-set the clock's origin to bring it back in line with Date.now() | ||
if (Math.abs(Date.now() - (timeOrigin + performance.now())) > MAX_CLOCK_DRIFT_MS) { | ||
return Date.now() - performance.now(); | ||
} | ||
return timeOrigin; | ||
} | ||
function createClock(performance, backgroundingListener) { | ||
let calculatedTimeOrigin = performance.timeOrigin === undefined | ||
const initialTimeOrigin = performance.timeOrigin === undefined | ||
? performance.timing.navigationStart | ||
: performance.timeOrigin; | ||
// if the machine has been sleeping the monatomic clock used by performance.now() may have been paused, | ||
// so when the app returns to the foreground we need to check if this has drifted significantly from Date.now() | ||
// if the drift is > 5 minutes re-set the clock's origin to bring it back in line with Date.now() | ||
// the performance clock could be shared between different tabs running in the same process | ||
// so may already have diverged - for this reason we calculate a time origin when we first create the clock | ||
// as well as when the app returns to the foreground | ||
let calculatedTimeOrigin = recalculateTimeOrigin(initialTimeOrigin, performance); | ||
backgroundingListener.onStateChange(state => { | ||
if (state === 'in-foreground') { | ||
if (Math.abs(Date.now() - (calculatedTimeOrigin + performance.now())) > MAX_CLOCK_DRIFT_MS) { | ||
calculatedTimeOrigin = Date.now() - performance.now(); | ||
} | ||
calculatedTimeOrigin = recalculateTimeOrigin(calculatedTimeOrigin, performance); | ||
} | ||
@@ -19,0 +27,0 @@ }); |
import { schema, isBoolean, isStringOrRegExpArray } from '@bugsnag/core-performance'; | ||
import { defaultNetworkRequestCallback, isNetworkRequestCallback } from './network-request-callback.js'; | ||
import { isRoutingProvider } from './routing-provider.js'; | ||
import { defaultSendPageAttributes, isSendPageAttributes } from './send-page-attributes.js'; | ||
@@ -29,2 +31,10 @@ function createSchema(hostname, defaultRoutingProvider) { | ||
validate: isStringOrRegExpArray | ||
}, networkRequestCallback: { | ||
defaultValue: defaultNetworkRequestCallback, | ||
message: 'should be a function', | ||
validate: isNetworkRequestCallback | ||
}, sendPageAttributes: { | ||
defaultValue: defaultSendPageAttributes, | ||
message: 'should be an object', | ||
validate: isSendPageAttributes | ||
} }); | ||
@@ -31,0 +41,0 @@ } |
import getAbsoluteUrl from './request-tracker/url-helpers.js'; | ||
const defaultRouteResolver = (url) => url.pathname; | ||
const defaultRouteResolver = (url) => url.pathname || '/'; | ||
const createDefaultRoutingProvider = (onSettle, location) => { | ||
@@ -33,2 +33,2 @@ return class DefaultRoutingProvider { | ||
export { createDefaultRoutingProvider }; | ||
export { createDefaultRoutingProvider, defaultRouteResolver }; |
@@ -8,3 +8,3 @@ import { RequestTracker } from './request-tracker.js'; | ||
const method = (!!init && init.method) || (inputIsRequest && input.method) || 'GET'; | ||
return { url: getAbsoluteUrl(url, baseUrl), method, startTime }; | ||
return { url: getAbsoluteUrl(url, baseUrl), method, startTime, type: 'fetch' }; | ||
} | ||
@@ -11,0 +11,0 @@ function isRequest(input) { |
@@ -24,2 +24,3 @@ import { RequestTracker } from './request-tracker.js'; | ||
const onRequestEnd = requestTracker.start({ | ||
type: 'xmlhttprequest', | ||
method: requestData.method, | ||
@@ -26,0 +27,0 @@ url: requestData.url, |
@@ -8,3 +8,3 @@ import cuid from '@bugsnag/cuid'; | ||
return function resourceAttributesSource(config) { | ||
const attributes = new ResourceAttributes(config.releaseStage, config.appVersion, 'bugsnag.performance.browser', '0.3.0'); | ||
const attributes = new ResourceAttributes(config.releaseStage, config.appVersion, 'bugsnag.performance.browser', '1.0.0'); | ||
attributes.set('browser.user_agent', navigator.userAgent); | ||
@@ -11,0 +11,0 @@ // chromium only |
@@ -1,11 +0,29 @@ | ||
const createSpanAttributesSource = (title, url) => { | ||
return () => { | ||
const spanAttributes = new Map(); | ||
spanAttributes.set('bugsnag.span.category', 'custom'); | ||
spanAttributes.set('bugsnag.browser.page.url', url); | ||
spanAttributes.set('bugsnag.browser.page.title', title); | ||
return spanAttributes; | ||
const createSpanAttributesSource = (document) => { | ||
const defaultAttributes = { | ||
url: { | ||
name: 'bugsnag.browser.page.url', | ||
getValue: () => document.location.href, | ||
permitted: false | ||
}, | ||
title: { | ||
name: 'bugsnag.browser.page.title', | ||
getValue: () => document.title, | ||
permitted: false | ||
} | ||
}; | ||
return { | ||
configure(configuration) { | ||
defaultAttributes.title.permitted = configuration.sendPageAttributes.title || false; | ||
defaultAttributes.url.permitted = configuration.sendPageAttributes.url || false; | ||
}, | ||
requestAttributes(span) { | ||
for (const attribute of Object.values(defaultAttributes)) { | ||
if (attribute.permitted) { | ||
span.setAttribute(attribute.name, attribute.getValue()); | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
export { createSpanAttributesSource as default }; | ||
export { createSpanAttributesSource, createSpanAttributesSource as default }; |
@@ -14,5 +14,5 @@ import { type BackgroundingListener, type InternalConfiguration, type Plugin, type SpanFactory } from '@bugsnag/core-performance'; | ||
private wasBackgrounded; | ||
constructor(document: Document, location: Location, spanFactory: SpanFactory, webVitals: WebVitals, onSettle: OnSettle, backgroundingListener: BackgroundingListener, performance: PerformanceWithTiming); | ||
constructor(document: Document, location: Location, spanFactory: SpanFactory<BrowserConfiguration>, webVitals: WebVitals, onSettle: OnSettle, backgroundingListener: BackgroundingListener, performance: PerformanceWithTiming); | ||
configure(configuration: InternalConfiguration<BrowserConfiguration>): void; | ||
} | ||
//# sourceMappingURL=full-page-load-plugin.d.ts.map |
@@ -0,4 +1,4 @@ | ||
import { type InternalConfiguration, type Plugin, type SpanFactory } from '@bugsnag/core-performance'; | ||
import { type BrowserConfiguration } from '../config'; | ||
import { type RequestTracker } from '../request-tracker/request-tracker'; | ||
import { type SpanFactory, type Plugin, type InternalConfiguration } from '@bugsnag/core-performance'; | ||
import { type BrowserConfiguration } from '../config'; | ||
export declare class NetworkRequestPlugin implements Plugin<BrowserConfiguration> { | ||
@@ -9,3 +9,5 @@ private spanFactory; | ||
private configEndpoint; | ||
constructor(spanFactory: SpanFactory, fetchTracker: RequestTracker, xhrTracker: RequestTracker); | ||
private networkRequestCallback; | ||
private logger; | ||
constructor(spanFactory: SpanFactory<BrowserConfiguration>, fetchTracker: RequestTracker, xhrTracker: RequestTracker); | ||
configure(configuration: InternalConfiguration<BrowserConfiguration>): void; | ||
@@ -12,0 +14,0 @@ private trackRequest; |
import type { SpanContext, SpanFactory } from '@bugsnag/core-performance'; | ||
import { type BrowserConfiguration } from '../config'; | ||
import { type PerformanceWithTiming } from '../on-settle/load-event-end-settler'; | ||
export declare const instrumentPageLoadPhaseSpans: (spanFactory: SpanFactory, performance: PerformanceWithTiming, route: string, parentContext: SpanContext) => void; | ||
export declare const instrumentPageLoadPhaseSpans: (spanFactory: SpanFactory<BrowserConfiguration>, performance: PerformanceWithTiming, route: string, parentContext: SpanContext) => void; | ||
//# sourceMappingURL=page-load-phase-spans.d.ts.map |
@@ -8,5 +8,5 @@ import { type SpanContextStorage, type InternalConfiguration, type Plugin, type SpanFactory } from '@bugsnag/core-performance'; | ||
private readonly PerformanceObserverClass; | ||
constructor(spanFactory: SpanFactory, spanContextStorage: SpanContextStorage, PerformanceObserverClass: typeof PerformanceObserver); | ||
constructor(spanFactory: SpanFactory<BrowserConfiguration>, spanContextStorage: SpanContextStorage, PerformanceObserverClass: typeof PerformanceObserver); | ||
configure(configuration: InternalConfiguration<BrowserConfiguration>): void; | ||
} | ||
//# sourceMappingURL=resource-load-plugin.d.ts.map |
@@ -7,5 +7,5 @@ import { type InternalConfiguration, type Plugin, type SpanFactory } from '@bugsnag/core-performance'; | ||
private readonly document; | ||
constructor(spanFactory: SpanFactory, location: Location, document: Document); | ||
constructor(spanFactory: SpanFactory<BrowserConfiguration>, location: Location, document: Document); | ||
configure(configuration: InternalConfiguration<BrowserConfiguration>): void; | ||
} | ||
//# sourceMappingURL=route-change-plugin.d.ts.map |
import { type ConfigOption, type Configuration, type CoreSchema } from '@bugsnag/core-performance'; | ||
import { type NetworkRequestCallback } from './network-request-callback'; | ||
import { type RoutingProvider } from './routing-provider'; | ||
import { type SendPageAttributes } from './send-page-attributes'; | ||
export interface BrowserSchema extends CoreSchema { | ||
@@ -10,2 +12,4 @@ autoInstrumentFullPageLoads: ConfigOption<boolean>; | ||
settleIgnoreUrls: ConfigOption<Array<string | RegExp>>; | ||
networkRequestCallback: ConfigOption<NetworkRequestCallback>; | ||
sendPageAttributes: ConfigOption<SendPageAttributes>; | ||
} | ||
@@ -19,4 +23,6 @@ export interface BrowserConfiguration extends Configuration { | ||
settleIgnoreUrls?: Array<string | RegExp>; | ||
networkRequestCallback?: NetworkRequestCallback; | ||
sendPageAttributes?: SendPageAttributes; | ||
} | ||
export declare function createSchema(hostname: string, defaultRoutingProvider: RoutingProvider): BrowserSchema; | ||
//# sourceMappingURL=config.d.ts.map |
import { type OnSettle } from './on-settle'; | ||
import { type RouteResolver, type StartRouteChangeCallback } from './routing-provider'; | ||
export declare const defaultRouteResolver: RouteResolver; | ||
export declare const createDefaultRoutingProvider: (onSettle: OnSettle, location: Location) => { | ||
@@ -4,0 +5,0 @@ new (resolveRoute?: RouteResolver): { |
@@ -5,2 +5,3 @@ export interface RequestStartContext { | ||
startTime: number; | ||
type: 'fetch' | 'xmlhttprequest'; | ||
} | ||
@@ -7,0 +8,0 @@ export interface RequestEndContextSuccess { |
import type { SpanAttributesSource } from '@bugsnag/core-performance'; | ||
declare const createSpanAttributesSource: (title: string, url: string) => SpanAttributesSource; | ||
import { type BrowserConfiguration } from './config'; | ||
export declare const createSpanAttributesSource: (document: Document) => SpanAttributesSource<BrowserConfiguration>; | ||
export default createSpanAttributesSource; | ||
//# sourceMappingURL=span-attributes-source.d.ts.map |
{ | ||
"name": "@bugsnag/browser-performance", | ||
"version": "0.3.0", | ||
"version": "1.0.0", | ||
"description": "BugSnag performance monitoring for browsers", | ||
@@ -24,3 +24,3 @@ "homepage": "https://www.bugsnag.com/", | ||
"dependencies": { | ||
"@bugsnag/core-performance": "^0.3.0", | ||
"@bugsnag/core-performance": "^1.0.0", | ||
"@bugsnag/cuid": "^3.0.2" | ||
@@ -34,3 +34,3 @@ }, | ||
], | ||
"gitHead": "076d6311e6534e8ed2bdacb82cd3bcfeb2033ffd" | ||
"gitHead": "d30abd6e95ef5fe79582fd63186e243aa40269de" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
87468
95
1477
0
+ Added@bugsnag/core-performance@1.2.0(transitive)
- Removed@bugsnag/core-performance@0.3.0(transitive)