@grafana/faro-react 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/faro-react.iife.js +1 -1
- package/dist/bundle/types/profiler/FaroProfiler.d.ts +1 -1
- package/dist/bundle/types/profiler/FaroProfiler.test.d.ts +1 -0
- package/dist/cjs/profiler/FaroProfiler.js +18 -8
- package/dist/cjs/profiler/FaroProfiler.js.map +1 -1
- package/dist/cjs/profiler/FaroProfiler.test.js +75 -0
- package/dist/cjs/profiler/FaroProfiler.test.js.map +1 -0
- package/dist/esm/profiler/FaroProfiler.js +18 -8
- package/dist/esm/profiler/FaroProfiler.js.map +1 -1
- package/dist/esm/profiler/FaroProfiler.test.js +73 -0
- package/dist/esm/profiler/FaroProfiler.test.js.map +1 -0
- package/dist/types/profiler/FaroProfiler.d.ts +1 -1
- package/dist/types/profiler/FaroProfiler.test.d.ts +1 -0
- package/package.json +5 -5
|
@@ -6,4 +6,4 @@ var GrafanaFaroReact=function(e,t,r){"use strict";const n={hasError:!1,error:nul
|
|
|
6
6
|
*
|
|
7
7
|
* This source code is licensed under the MIT license found in the
|
|
8
8
|
* LICENSE file in the root directory of this source tree.
|
|
9
|
-
*/function E(){return O||(O=1,"production"===process.env.NODE_ENV?g.exports=function(){if(y)return v;y=1;var e="function"==typeof Symbol&&Symbol.for,t=e?Symbol.for("react.element"):60103,r=e?Symbol.for("react.portal"):60106,n=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,a=e?Symbol.for("react.provider"):60109,u=e?Symbol.for("react.context"):60110,c=e?Symbol.for("react.async_mode"):60111,s=e?Symbol.for("react.concurrent_mode"):60111,l=e?Symbol.for("react.forward_ref"):60112,f=e?Symbol.for("react.suspense"):60113,p=e?Symbol.for("react.suspense_list"):60120,d=e?Symbol.for("react.memo"):60115,m=e?Symbol.for("react.lazy"):60116,b=e?Symbol.for("react.block"):60121,g=e?Symbol.for("react.fundamental"):60117,h=e?Symbol.for("react.responder"):60118,O=e?Symbol.for("react.scope"):60119;function P(e){if("object"==typeof e&&null!==e){var p=e.$$typeof;switch(p){case t:switch(e=e.type){case c:case s:case n:case i:case o:case f:return e;default:switch(e=e&&e.$$typeof){case u:case l:case m:case d:case a:return e;default:return p}}case r:return p}}}function S(e){return P(e)===s}return v.AsyncMode=c,v.ConcurrentMode=s,v.ContextConsumer=u,v.ContextProvider=a,v.Element=t,v.ForwardRef=l,v.Fragment=n,v.Lazy=m,v.Memo=d,v.Portal=r,v.Profiler=i,v.StrictMode=o,v.Suspense=f,v.isAsyncMode=function(e){return S(e)||P(e)===c},v.isConcurrentMode=S,v.isContextConsumer=function(e){return P(e)===u},v.isContextProvider=function(e){return P(e)===a},v.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===t},v.isForwardRef=function(e){return P(e)===l},v.isFragment=function(e){return P(e)===n},v.isLazy=function(e){return P(e)===m},v.isMemo=function(e){return P(e)===d},v.isPortal=function(e){return P(e)===r},v.isProfiler=function(e){return P(e)===i},v.isStrictMode=function(e){return P(e)===o},v.isSuspense=function(e){return P(e)===f},v.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===n||e===s||e===i||e===o||e===f||e===p||"object"==typeof e&&null!==e&&(e.$$typeof===m||e.$$typeof===d||e.$$typeof===a||e.$$typeof===u||e.$$typeof===l||e.$$typeof===g||e.$$typeof===h||e.$$typeof===O||e.$$typeof===b)},v.typeOf=P,v}():g.exports=(h||(h=1,"production"!==process.env.NODE_ENV&&function(){var e="function"==typeof Symbol&&Symbol.for,t=e?Symbol.for("react.element"):60103,r=e?Symbol.for("react.portal"):60106,n=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,a=e?Symbol.for("react.provider"):60109,u=e?Symbol.for("react.context"):60110,c=e?Symbol.for("react.async_mode"):60111,s=e?Symbol.for("react.concurrent_mode"):60111,l=e?Symbol.for("react.forward_ref"):60112,f=e?Symbol.for("react.suspense"):60113,p=e?Symbol.for("react.suspense_list"):60120,d=e?Symbol.for("react.memo"):60115,m=e?Symbol.for("react.lazy"):60116,b=e?Symbol.for("react.block"):60121,y=e?Symbol.for("react.fundamental"):60117,g=e?Symbol.for("react.responder"):60118,v=e?Symbol.for("react.scope"):60119;function h(e){if("object"==typeof e&&null!==e){var p=e.$$typeof;switch(p){case t:var b=e.type;switch(b){case c:case s:case n:case i:case o:case f:return b;default:var y=b&&b.$$typeof;switch(y){case u:case l:case m:case d:case a:return y;default:return p}}case r:return p}}}var O=c,P=s,S=u,E=a,R=t,V=l,T=n,$=m,I=d,F=r,C=i,D=o,N=f,x=!1;function _(e){return h(e)===s}j.AsyncMode=O,j.ConcurrentMode=P,j.ContextConsumer=S,j.ContextProvider=E,j.Element=R,j.ForwardRef=V,j.Fragment=T,j.Lazy=$,j.Memo=I,j.Portal=F,j.Profiler=C,j.StrictMode=D,j.Suspense=N,j.isAsyncMode=function(e){return x||(x=!0,console.warn("The ReactIs.isAsyncMode() alias has been deprecated, and will be removed in React 17+. Update your code to use ReactIs.isConcurrentMode() instead. It has the exact same API.")),_(e)||h(e)===c},j.isConcurrentMode=_,j.isContextConsumer=function(e){return h(e)===u},j.isContextProvider=function(e){return h(e)===a},j.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===t},j.isForwardRef=function(e){return h(e)===l},j.isFragment=function(e){return h(e)===n},j.isLazy=function(e){return h(e)===m},j.isMemo=function(e){return h(e)===d},j.isPortal=function(e){return h(e)===r},j.isProfiler=function(e){return h(e)===i},j.isStrictMode=function(e){return h(e)===o},j.isSuspense=function(e){return h(e)===f},j.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===n||e===s||e===i||e===o||e===f||e===p||"object"==typeof e&&null!==e&&(e.$$typeof===m||e.$$typeof===d||e.$$typeof===a||e.$$typeof===u||e.$$typeof===l||e.$$typeof===y||e.$$typeof===g||e.$$typeof===v||e.$$typeof===b)},j.typeOf=h}()),j)),g.exports}var R=function(){if(S)return P;S=1;var e=E(),t={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},r={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},n={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},o={};function i(r){return e.isMemo(r)?n:o[r.$$typeof]||t}o[e.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},o[e.Memo]=n;var a=Object.defineProperty,u=Object.getOwnPropertyNames,c=Object.getOwnPropertySymbols,s=Object.getOwnPropertyDescriptor,l=Object.getPrototypeOf,f=Object.prototype;return P=function e(t,n,o){if("string"!=typeof n){if(f){var p=l(n);p&&p!==f&&e(t,p,o)}var d=u(n);c&&(d=d.concat(c(n)));for(var m=i(t),b=i(n),y=0;y<d.length;++y){var g=d[y];if(!(r[g]||o&&o[g]||b&&b[g]||m&&m[g])){var v=s(n,g);try{a(t,g,v)}catch(e){}}}}return t},P}(),V=b(R);const T="unknown";var $,I;let F,C;function D(e){return F={route:"",url:e},F}function N(e){e.history,C=e.Route}function x(e){var r,n;return(null===(r=null==e?void 0:e.computedMatch)||void 0===r?void 0:r.isExact)&&(n=e.computedMatch.path,F&&(F.route=n)),t.createElement(C,Object.assign({},e))}function _(t){var n,o,a,u;const c=t.Route,s=null!==(n=c.displayName)&&void 0!==n?n:c.name;x.displayName=`faroRoute(${s})`,V(x,c),N(t),D(null===(o=r.globalObject.location)||void 0===o?void 0:o.href),null===(u=(a=t.history).listen)||void 0===u||u.call(a,(t,n)=>{var o;n!==e.NavigationType.Push&&n!==e.NavigationType.Pop||(i.pushEvent(r.EVENT_ROUTE_CHANGE,F,void 0,{skipDedupe:!0}),F=void 0,D(null===(o=r.globalObject.location)||void 0===o?void 0:o.href))})}e.ReactRouterVersion=void 0,($=e.ReactRouterVersion||(e.ReactRouterVersion={})).V4="v4",$.V5="v5",$.V6="v6",$.V6_data_router="v6_data_router",$.V7="v7",$.V7_data_router="v7_data_router",e.NavigationType=void 0,(I=e.NavigationType||(e.NavigationType={})).Pop="POP",I.Push="PUSH",I.Replace="REPLACE";let L,M,w,k,B,A=!1;function z(e){return e.split(/\\?\//).filter(e=>e.length>0&&","!==e).length}function U(e,t){if(!e||0===e.length)return t.pathname;const r=M(e,t);let n="";if(r)for(let e=0;e<r.length;e++){const o=r[e],i=o.route;if(i){if(i.index)return o.pathname;let e=i.path;if(e&&(e=e.startsWith("/")?e:`/${e}`,n+=e,o.pathname===t.pathname))return z(n)!==z(o.pathname)?e:n}}return t.pathname}function G(n){var o;const a=null==k?void 0:k(),u=null==B?void 0:B(),c=t.useMemo(()=>{var e;return null!==(e=null==L?void 0:L(n.children))&&void 0!==e?e:[]},[n.children]),s=t.useRef({});t.useEffect(()=>{var t,n;if(A&&(u===e.NavigationType.Push||u===e.NavigationType.Pop)){const e=U(c,a),o=null===(t=r.globalObject.location)||void 0===t?void 0:t.href;i.pushEvent(r.EVENT_ROUTE_CHANGE,Object.assign({toRoute:e,toUrl:null===(n=r.globalObject.location)||void 0===n?void 0:n.href},s.current)),s.current={fromRoute:e,fromUrl:o}}},[a,u,c]);const l=null!==(o=n.routesComponent)&&void 0!==o?o:w;return t.createElement(l,Object.assign({},n))}function W(e){var t;V(G,e.Routes),A=!0,L=(t=e).createRoutesFromChildren,M=t.matchRoutes,w=t.Routes,k=t.useLocation,B=t.useNavigationType}function K(e){A=!0,M=e.matchRoutes}class H extends r.BaseInstrumentation{constructor(e={}){super(),this.options=e,this.name="@grafana/faro-react",this.version=r.VERSION}initialize(){var t,r;t=this.internalLogger,r=this.api,o=t,i=r,function(t){var r;const n="Initializing React Router";switch(null===(r=t.router)||void 0===r?void 0:r.version){case e.ReactRouterVersion.V7:case e.ReactRouterVersion.V6:o.debug(`${n} ${t.router.version} instrumentation`),W(t.router.dependencies);break;case e.ReactRouterVersion.V7_data_router:case e.ReactRouterVersion.V6_data_router:o.debug(`${n} ${t.router.version} data router instrumentation`),K(t.router.dependencies);break;case e.ReactRouterVersion.V5:case e.ReactRouterVersion.V4:o.debug(`${n} ${t.router.version} instrumentation`),_(t.router.dependencies);break;default:o.debug("Skipping initialization of React Router instrumentation")}}(this.options)}}class q extends t.Component{get isOtelInitialized(){return!!(null==i?void 0:i.isOTELInitialized())}get otel(){return null==i?void 0:i.getOTEL()}get tracer(){var e;return null===(e=this.otel)||void 0===e?void 0:e.trace.getTracer("@grafana/faro-react",r.VERSION)}createSpan(e,t){var r,n;const o=this.tracer.startSpan(e,{startTime:null==t?void 0:t.startTime,attributes:Object.assign({"react.component.name":this.props.name},null!==(r=null==t?void 0:t.attributes)&&void 0!==r?r:{})});return null===(n=this.otel)||void 0===n||n.trace.setSpan(this.otel.context.active(),o),(null==t?void 0:t.endTime)&&o.end(t.endTime),o}createChildSpan(e,t,r){var n;let o;return null===(n=this.otel)||void 0===n||n.context.with(this.otel.trace.setSpan(this.otel.context.active(),t),()=>{o=this.createSpan(e,r)}),o}constructor(e){super(e),this.mountSpan=void 0,this.mountSpanEndTime=void 0,this.updateSpan=void 0,this.isOtelInitialized?this.mountSpan=this.createSpan("componentMount"):null==o||o.error('The Faro React Profiler requires tracing instrumentation. Please enable it in the "instrumentations" section of your config.')}componentDidMount(){this.isOtelInitialized&&this.mountSpan&&(this.mountSpanEndTime=Date.now(),this.mountSpan.end(this.mountSpanEndTime))}shouldComponentUpdate({updateProps:e}){if(this.isOtelInitialized&&this.mountSpan&&e!==this.props.updateProps){const t=Object.keys(e).filter(t=>e[t]!==this.props.updateProps[t]);t.length>0&&(this.updateSpan=this.createChildSpan("componentUpdate",this.mountSpan,{attributes:{"react.component.changed_props":t}}))}return!0}componentDidUpdate(){this.isOtelInitialized&&this.updateSpan&&(this.updateSpan.end(),this.updateSpan=void 0)}componentWillUnmount(){this.isOtelInitialized&&this.mountSpan&&this.createChildSpan("componentRender",this.mountSpan,{startTime:this.mountSpanEndTime,endTime:Date.now()})}render(){return this.props.children}}return Object.defineProperty(e,"BaseExtension",{enumerable:!0,get:function(){return r.BaseExtension}}),Object.defineProperty(e,"BaseInstrumentation",{enumerable:!0,get:function(){return r.BaseInstrumentation}}),Object.defineProperty(e,"BaseTransport",{enumerable:!0,get:function(){return r.BaseTransport}}),Object.defineProperty(e,"ConsoleInstrumentation",{enumerable:!0,get:function(){return r.ConsoleInstrumentation}}),Object.defineProperty(e,"ConsoleTransport",{enumerable:!0,get:function(){return r.ConsoleTransport}}),Object.defineProperty(e,"ErrorsInstrumentation",{enumerable:!0,get:function(){return r.ErrorsInstrumentation}}),Object.defineProperty(e,"FetchTransport",{enumerable:!0,get:function(){return r.FetchTransport}}),Object.defineProperty(e,"InternalLoggerLevel",{enumerable:!0,get:function(){return r.InternalLoggerLevel}}),Object.defineProperty(e,"LogLevel",{enumerable:!0,get:function(){return r.LogLevel}}),Object.defineProperty(e,"TransportItemType",{enumerable:!0,get:function(){return r.TransportItemType}}),Object.defineProperty(e,"VERSION",{enumerable:!0,get:function(){return r.VERSION}}),Object.defineProperty(e,"ViewInstrumentation",{enumerable:!0,get:function(){return r.ViewInstrumentation}}),Object.defineProperty(e,"WebVitalsInstrumentation",{enumerable:!0,get:function(){return r.WebVitalsInstrumentation}}),Object.defineProperty(e,"allLogLevels",{enumerable:!0,get:function(){return r.allLogLevels}}),Object.defineProperty(e,"browserMeta",{enumerable:!0,get:function(){return r.browserMeta}}),Object.defineProperty(e,"buildStackFrame",{enumerable:!0,get:function(){return r.buildStackFrame}}),Object.defineProperty(e,"createInternalLogger",{enumerable:!0,get:function(){return r.createInternalLogger}}),Object.defineProperty(e,"createPromiseBuffer",{enumerable:!0,get:function(){return r.createPromiseBuffer}}),Object.defineProperty(e,"createSession",{enumerable:!0,get:function(){return r.createSession}}),Object.defineProperty(e,"deepEqual",{enumerable:!0,get:function(){return r.deepEqual}}),Object.defineProperty(e,"defaultEventDomain",{enumerable:!0,get:function(){return r.defaultEventDomain}}),Object.defineProperty(e,"defaultExceptionType",{enumerable:!0,get:function(){return r.defaultExceptionType}}),Object.defineProperty(e,"defaultGlobalObjectKey",{enumerable:!0,get:function(){return r.defaultGlobalObjectKey}}),Object.defineProperty(e,"defaultInternalLoggerLevel",{enumerable:!0,get:function(){return r.defaultInternalLoggerLevel}}),Object.defineProperty(e,"defaultLogLevel",{enumerable:!0,get:function(){return r.defaultLogLevel}}),Object.defineProperty(e,"faro",{enumerable:!0,get:function(){return r.faro}}),Object.defineProperty(e,"genShortID",{enumerable:!0,get:function(){return r.genShortID}}),Object.defineProperty(e,"getCurrentTimestamp",{enumerable:!0,get:function(){return r.getCurrentTimestamp}}),Object.defineProperty(e,"getDataFromSafariExtensions",{enumerable:!0,get:function(){return r.getDataFromSafariExtensions}}),Object.defineProperty(e,"getInternalFaroFromGlobalObject",{enumerable:!0,get:function(){return r.getInternalFaroFromGlobalObject}}),Object.defineProperty(e,"getStackFramesFromError",{enumerable:!0,get:function(){return r.getStackFramesFromError}}),Object.defineProperty(e,"getTransportBody",{enumerable:!0,get:function(){return r.getTransportBody}}),Object.defineProperty(e,"getWebInstrumentations",{enumerable:!0,get:function(){return r.getWebInstrumentations}}),Object.defineProperty(e,"globalObject",{enumerable:!0,get:function(){return r.globalObject}}),Object.defineProperty(e,"initializeFaro",{enumerable:!0,get:function(){return r.initializeFaro}}),Object.defineProperty(e,"internalGlobalObjectKey",{enumerable:!0,get:function(){return r.internalGlobalObjectKey}}),Object.defineProperty(e,"isArray",{enumerable:!0,get:function(){return r.isArray}}),Object.defineProperty(e,"isBoolean",{enumerable:!0,get:function(){return r.isBoolean}}),Object.defineProperty(e,"isDomError",{enumerable:!0,get:function(){return r.isDomError}}),Object.defineProperty(e,"isDomException",{enumerable:!0,get:function(){return r.isDomException}}),Object.defineProperty(e,"isElement",{enumerable:!0,get:function(){return r.isElement}}),Object.defineProperty(e,"isElementDefined",{enumerable:!0,get:function(){return r.isElementDefined}}),Object.defineProperty(e,"isError",{enumerable:!0,get:function(){return r.isError}}),Object.defineProperty(e,"isErrorDefined",{enumerable:!0,get:function(){return r.isErrorDefined}}),Object.defineProperty(e,"isErrorEvent",{enumerable:!0,get:function(){return r.isErrorEvent}}),Object.defineProperty(e,"isEvent",{enumerable:!0,get:function(){return r.isEvent}}),Object.defineProperty(e,"isEventDefined",{enumerable:!0,get:function(){return r.isEventDefined}}),Object.defineProperty(e,"isFunction",{enumerable:!0,get:function(){return r.isFunction}}),Object.defineProperty(e,"isInstanceOf",{enumerable:!0,get:function(){return r.isInstanceOf}}),Object.defineProperty(e,"isInt",{enumerable:!0,get:function(){return r.isInt}}),Object.defineProperty(e,"isInternalFaroOnGlobalObject",{enumerable:!0,get:function(){return r.isInternalFaroOnGlobalObject}}),Object.defineProperty(e,"isMap",{enumerable:!0,get:function(){return r.isMap}}),Object.defineProperty(e,"isMapDefined",{enumerable:!0,get:function(){return r.isMapDefined}}),Object.defineProperty(e,"isNull",{enumerable:!0,get:function(){return r.isNull}}),Object.defineProperty(e,"isNumber",{enumerable:!0,get:function(){return r.isNumber}}),Object.defineProperty(e,"isObject",{enumerable:!0,get:function(){return r.isObject}}),Object.defineProperty(e,"isPrimitive",{enumerable:!0,get:function(){return r.isPrimitive}}),Object.defineProperty(e,"isRegExp",{enumerable:!0,get:function(){return r.isRegExp}}),Object.defineProperty(e,"isString",{enumerable:!0,get:function(){return r.isString}}),Object.defineProperty(e,"isSymbol",{enumerable:!0,get:function(){return r.isSymbol}}),Object.defineProperty(e,"isSyntheticEvent",{enumerable:!0,get:function(){return r.isSyntheticEvent}}),Object.defineProperty(e,"isThenable",{enumerable:!0,get:function(){return r.isThenable}}),Object.defineProperty(e,"isToString",{enumerable:!0,get:function(){return r.isToString}}),Object.defineProperty(e,"isTypeof",{enumerable:!0,get:function(){return r.isTypeof}}),Object.defineProperty(e,"isUndefined",{enumerable:!0,get:function(){return r.isUndefined}}),Object.defineProperty(e,"makeCoreConfig",{enumerable:!0,get:function(){return r.makeCoreConfig}}),Object.defineProperty(e,"noop",{enumerable:!0,get:function(){return r.noop}}),Object.defineProperty(e,"parseStacktrace",{enumerable:!0,get:function(){return r.parseStacktrace}}),Object.defineProperty(e,"setInternalFaroOnGlobalObject",{enumerable:!0,get:function(){return r.setInternalFaroOnGlobalObject}}),Object.defineProperty(e,"transportItemTypeToBodyKey",{enumerable:!0,get:function(){return r.transportItemTypeToBodyKey}}),Object.defineProperty(e,"userActionDataAttribute",{enumerable:!0,get:function(){return r.userActionDataAttribute}}),e.FaroErrorBoundary=m,e.FaroProfiler=q,e.FaroRoute=x,e.FaroRoutes=G,e.ReactIntegration=H,e.createReactRouterV4Options=function(t){return{version:e.ReactRouterVersion.V4,dependencies:t}},e.createReactRouterV5Options=function(t){return{version:e.ReactRouterVersion.V5,dependencies:t}},e.createReactRouterV6DataOptions=function(t){return{version:e.ReactRouterVersion.V6_data_router,dependencies:t}},e.createReactRouterV6Options=function(t){return{version:e.ReactRouterVersion.V6,dependencies:t}},e.createReactRouterV7DataOptions=function(t){return{version:e.ReactRouterVersion.V7_data_router,dependencies:t}},e.createReactRouterV7Options=function(t){return{version:e.ReactRouterVersion.V7,dependencies:t}},e.faroErrorBoundaryInitialState=n,e.getMajorReactVersion=p,e.isReactVersionAtLeast=d,e.isReactVersionAtLeast16=f,e.isReactVersionAtLeast17=l,e.isReactVersionAtLeast18=s,e.isReactVersionAtLeast19=c,e.reactVersion=a,e.reactVersionMajor=u,e.setReactRouterV4V5SSRDependencies=function(e){C=e.Route},e.setReactRouterV6SSRDependencies=function(e){w=e.Routes},e.setReactRouterV7SSRDependencies=function(e){w=e.Routes},e.withFaroErrorBoundary=function(e,r){var n,o;const i=null!==(o=null!==(n=e.displayName)&&void 0!==n?n:e.name)&&void 0!==o?o:T,a=n=>t.createElement(m,Object.assign({},r),t.createElement(e,Object.assign({},n)));return a.displayName=`faroErrorBoundary(${i})`,V(a,e),a},e.withFaroProfiler=function(e,r){var n,o,i;const a=null!==(i=null!==(o=null!==(n=null==r?void 0:r.name)&&void 0!==n?n:e.displayName)&&void 0!==o?o:e.name)&&void 0!==i?i:T,u=r=>t.createElement(q,{name:a,updateProps:r},t.createElement(e,Object.assign({},r)));return u.displayName=`faroProfiler(${a})`,V(u,e),u},e.withFaroRouterInstrumentation=function(t){let n={};return t.subscribe(o=>{var a,u;const c=o.historyAction,s=o.location,l=t.routes;if(A&&(c===e.NavigationType.Push||c===e.NavigationType.Pop)){const e=U(l,s),t=null===(a=r.globalObject.location)||void 0===a?void 0:a.href;i.pushEvent(r.EVENT_ROUTE_CHANGE,Object.assign({toRoute:e,toUrl:null===(u=r.globalObject.location)||void 0===u?void 0:u.href},n)),n={fromRoute:e,fromUrl:t}}}),t},e}({},React,GrafanaFaroWebSdk);
|
|
9
|
+
*/function R(){return O||(O=1,"production"===process.env.NODE_ENV?g.exports=function(){if(y)return v;y=1;var e="function"==typeof Symbol&&Symbol.for,t=e?Symbol.for("react.element"):60103,r=e?Symbol.for("react.portal"):60106,n=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,a=e?Symbol.for("react.provider"):60109,u=e?Symbol.for("react.context"):60110,c=e?Symbol.for("react.async_mode"):60111,s=e?Symbol.for("react.concurrent_mode"):60111,l=e?Symbol.for("react.forward_ref"):60112,f=e?Symbol.for("react.suspense"):60113,p=e?Symbol.for("react.suspense_list"):60120,d=e?Symbol.for("react.memo"):60115,m=e?Symbol.for("react.lazy"):60116,b=e?Symbol.for("react.block"):60121,g=e?Symbol.for("react.fundamental"):60117,h=e?Symbol.for("react.responder"):60118,O=e?Symbol.for("react.scope"):60119;function P(e){if("object"==typeof e&&null!==e){var p=e.$$typeof;switch(p){case t:switch(e=e.type){case c:case s:case n:case i:case o:case f:return e;default:switch(e=e&&e.$$typeof){case u:case l:case m:case d:case a:return e;default:return p}}case r:return p}}}function S(e){return P(e)===s}return v.AsyncMode=c,v.ConcurrentMode=s,v.ContextConsumer=u,v.ContextProvider=a,v.Element=t,v.ForwardRef=l,v.Fragment=n,v.Lazy=m,v.Memo=d,v.Portal=r,v.Profiler=i,v.StrictMode=o,v.Suspense=f,v.isAsyncMode=function(e){return S(e)||P(e)===c},v.isConcurrentMode=S,v.isContextConsumer=function(e){return P(e)===u},v.isContextProvider=function(e){return P(e)===a},v.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===t},v.isForwardRef=function(e){return P(e)===l},v.isFragment=function(e){return P(e)===n},v.isLazy=function(e){return P(e)===m},v.isMemo=function(e){return P(e)===d},v.isPortal=function(e){return P(e)===r},v.isProfiler=function(e){return P(e)===i},v.isStrictMode=function(e){return P(e)===o},v.isSuspense=function(e){return P(e)===f},v.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===n||e===s||e===i||e===o||e===f||e===p||"object"==typeof e&&null!==e&&(e.$$typeof===m||e.$$typeof===d||e.$$typeof===a||e.$$typeof===u||e.$$typeof===l||e.$$typeof===g||e.$$typeof===h||e.$$typeof===O||e.$$typeof===b)},v.typeOf=P,v}():g.exports=(h||(h=1,"production"!==process.env.NODE_ENV&&function(){var e="function"==typeof Symbol&&Symbol.for,t=e?Symbol.for("react.element"):60103,r=e?Symbol.for("react.portal"):60106,n=e?Symbol.for("react.fragment"):60107,o=e?Symbol.for("react.strict_mode"):60108,i=e?Symbol.for("react.profiler"):60114,a=e?Symbol.for("react.provider"):60109,u=e?Symbol.for("react.context"):60110,c=e?Symbol.for("react.async_mode"):60111,s=e?Symbol.for("react.concurrent_mode"):60111,l=e?Symbol.for("react.forward_ref"):60112,f=e?Symbol.for("react.suspense"):60113,p=e?Symbol.for("react.suspense_list"):60120,d=e?Symbol.for("react.memo"):60115,m=e?Symbol.for("react.lazy"):60116,b=e?Symbol.for("react.block"):60121,y=e?Symbol.for("react.fundamental"):60117,g=e?Symbol.for("react.responder"):60118,v=e?Symbol.for("react.scope"):60119;function h(e){if("object"==typeof e&&null!==e){var p=e.$$typeof;switch(p){case t:var b=e.type;switch(b){case c:case s:case n:case i:case o:case f:return b;default:var y=b&&b.$$typeof;switch(y){case u:case l:case m:case d:case a:return y;default:return p}}case r:return p}}}var O=c,P=s,S=u,R=a,E=t,V=l,$=n,T=m,I=d,F=r,C=i,N=o,x=f,D=!1;function _(e){return h(e)===s}j.AsyncMode=O,j.ConcurrentMode=P,j.ContextConsumer=S,j.ContextProvider=R,j.Element=E,j.ForwardRef=V,j.Fragment=$,j.Lazy=T,j.Memo=I,j.Portal=F,j.Profiler=C,j.StrictMode=N,j.Suspense=x,j.isAsyncMode=function(e){return D||(D=!0,console.warn("The ReactIs.isAsyncMode() alias has been deprecated, and will be removed in React 17+. Update your code to use ReactIs.isConcurrentMode() instead. It has the exact same API.")),_(e)||h(e)===c},j.isConcurrentMode=_,j.isContextConsumer=function(e){return h(e)===u},j.isContextProvider=function(e){return h(e)===a},j.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===t},j.isForwardRef=function(e){return h(e)===l},j.isFragment=function(e){return h(e)===n},j.isLazy=function(e){return h(e)===m},j.isMemo=function(e){return h(e)===d},j.isPortal=function(e){return h(e)===r},j.isProfiler=function(e){return h(e)===i},j.isStrictMode=function(e){return h(e)===o},j.isSuspense=function(e){return h(e)===f},j.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===n||e===s||e===i||e===o||e===f||e===p||"object"==typeof e&&null!==e&&(e.$$typeof===m||e.$$typeof===d||e.$$typeof===a||e.$$typeof===u||e.$$typeof===l||e.$$typeof===y||e.$$typeof===g||e.$$typeof===v||e.$$typeof===b)},j.typeOf=h}()),j)),g.exports}var E=function(){if(S)return P;S=1;var e=R(),t={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},r={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},n={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},o={};function i(r){return e.isMemo(r)?n:o[r.$$typeof]||t}o[e.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},o[e.Memo]=n;var a=Object.defineProperty,u=Object.getOwnPropertyNames,c=Object.getOwnPropertySymbols,s=Object.getOwnPropertyDescriptor,l=Object.getPrototypeOf,f=Object.prototype;return P=function e(t,n,o){if("string"!=typeof n){if(f){var p=l(n);p&&p!==f&&e(t,p,o)}var d=u(n);c&&(d=d.concat(c(n)));for(var m=i(t),b=i(n),y=0;y<d.length;++y){var g=d[y];if(!(r[g]||o&&o[g]||b&&b[g]||m&&m[g])){var v=s(n,g);try{a(t,g,v)}catch(e){}}}}return t},P}(),V=b(E);const $="unknown";var T,I;let F,C;function N(e){return F={route:"",url:e},F}function x(e){e.history,C=e.Route}function D(e){var r,n;return(null===(r=null==e?void 0:e.computedMatch)||void 0===r?void 0:r.isExact)&&(n=e.computedMatch.path,F&&(F.route=n)),t.createElement(C,Object.assign({},e))}function _(t){var n,o,a,u;const c=t.Route,s=null!==(n=c.displayName)&&void 0!==n?n:c.name;D.displayName=`faroRoute(${s})`,V(D,c),x(t),N(null===(o=r.globalObject.location)||void 0===o?void 0:o.href),null===(u=(a=t.history).listen)||void 0===u||u.call(a,(t,n)=>{var o;n!==e.NavigationType.Push&&n!==e.NavigationType.Pop||(i.pushEvent(r.EVENT_ROUTE_CHANGE,F,void 0,{skipDedupe:!0}),F=void 0,N(null===(o=r.globalObject.location)||void 0===o?void 0:o.href))})}e.ReactRouterVersion=void 0,(T=e.ReactRouterVersion||(e.ReactRouterVersion={})).V4="v4",T.V5="v5",T.V6="v6",T.V6_data_router="v6_data_router",T.V7="v7",T.V7_data_router="v7_data_router",e.NavigationType=void 0,(I=e.NavigationType||(e.NavigationType={})).Pop="POP",I.Push="PUSH",I.Replace="REPLACE";let L,M,w,k,B,A=!1;function z(e){return e.split(/\\?\//).filter(e=>e.length>0&&","!==e).length}function U(e,t){if(!e||0===e.length)return t.pathname;const r=M(e,t);let n="";if(r)for(let e=0;e<r.length;e++){const o=r[e],i=o.route;if(i){if(i.index)return o.pathname;let e=i.path;if(e&&(e=e.startsWith("/")?e:`/${e}`,n+=e,o.pathname===t.pathname))return z(n)!==z(o.pathname)?e:n}}return t.pathname}function G(n){var o;const a=null==k?void 0:k(),u=null==B?void 0:B(),c=t.useMemo(()=>{var e;return null!==(e=null==L?void 0:L(n.children))&&void 0!==e?e:[]},[n.children]),s=t.useRef({});t.useEffect(()=>{var t,n;if(A&&(u===e.NavigationType.Push||u===e.NavigationType.Pop)){const e=U(c,a),o=null===(t=r.globalObject.location)||void 0===t?void 0:t.href;i.pushEvent(r.EVENT_ROUTE_CHANGE,Object.assign({toRoute:e,toUrl:null===(n=r.globalObject.location)||void 0===n?void 0:n.href},s.current)),s.current={fromRoute:e,fromUrl:o}}},[a,u,c]);const l=null!==(o=n.routesComponent)&&void 0!==o?o:w;return t.createElement(l,Object.assign({},n))}function W(e){var t;V(G,e.Routes),A=!0,L=(t=e).createRoutesFromChildren,M=t.matchRoutes,w=t.Routes,k=t.useLocation,B=t.useNavigationType}function K(e){A=!0,M=e.matchRoutes}class H extends r.BaseInstrumentation{constructor(e={}){super(),this.options=e,this.name="@grafana/faro-react",this.version=r.VERSION}initialize(){var t,r;t=this.internalLogger,r=this.api,o=t,i=r,function(t){var r;const n="Initializing React Router";switch(null===(r=t.router)||void 0===r?void 0:r.version){case e.ReactRouterVersion.V7:case e.ReactRouterVersion.V6:o.debug(`${n} ${t.router.version} instrumentation`),W(t.router.dependencies);break;case e.ReactRouterVersion.V7_data_router:case e.ReactRouterVersion.V6_data_router:o.debug(`${n} ${t.router.version} data router instrumentation`),K(t.router.dependencies);break;case e.ReactRouterVersion.V5:case e.ReactRouterVersion.V4:o.debug(`${n} ${t.router.version} instrumentation`),_(t.router.dependencies);break;default:o.debug("Skipping initialization of React Router instrumentation")}}(this.options)}}class q extends t.Component{get isOtelInitialized(){return!!(null==i?void 0:i.isOTELInitialized())}get otel(){return null==i?void 0:i.getOTEL()}get tracer(){var e;return null===(e=this.otel)||void 0===e?void 0:e.trace.getTracer("@grafana/faro-react",r.VERSION)}createSpan(e,t){var r,n;const o=this.tracer.startSpan(e,{startTime:null==t?void 0:t.startTime,attributes:Object.assign({"react.component.name":this.props.name},null!==(r=null==t?void 0:t.attributes)&&void 0!==r?r:{})});return null===(n=this.otel)||void 0===n||n.trace.setSpan(this.otel.context.active(),o),(null==t?void 0:t.endTime)&&o.end(t.endTime),o}createChildSpan(e,t,r){var n;let o;return null===(n=this.otel)||void 0===n||n.context.with(this.otel.trace.setSpan(this.otel.context.active(),t),()=>{o=this.createSpan(e,r)}),o}constructor(e){super(e),this.mountSpan=void 0,this.renderSpan=void 0,this.updateSpan=void 0,this.isOtelInitialized?this.mountSpan=this.createSpan("componentMount"):null==o||o.error('The Faro React Profiler requires tracing instrumentation. Please enable it in the "instrumentations" section of your config.')}componentDidMount(){this.isOtelInitialized&&this.mountSpan&&(this.mountSpan.end(),this.renderSpan=this.createChildSpan("componentRender",this.mountSpan))}shouldComponentUpdate({updateProps:e}){if(this.isOtelInitialized&&this.mountSpan&&e!==this.props.updateProps){const t=Object.keys(e).filter(t=>e[t]!==this.props.updateProps[t]);t.length>0&&(this.updateSpan=this.createChildSpan("componentUpdate",this.mountSpan,{attributes:{"react.component.changed_props":t}}))}return!0}componentDidUpdate(){this.isOtelInitialized&&this.updateSpan&&(this.updateSpan.end(),this.updateSpan=void 0)}componentWillUnmount(){this.isOtelInitialized&&this.renderSpan&&(this.renderSpan.end(),this.renderSpan=void 0)}render(){return this.props.children}}return Object.defineProperty(e,"BaseExtension",{enumerable:!0,get:function(){return r.BaseExtension}}),Object.defineProperty(e,"BaseInstrumentation",{enumerable:!0,get:function(){return r.BaseInstrumentation}}),Object.defineProperty(e,"BaseTransport",{enumerable:!0,get:function(){return r.BaseTransport}}),Object.defineProperty(e,"ConsoleInstrumentation",{enumerable:!0,get:function(){return r.ConsoleInstrumentation}}),Object.defineProperty(e,"ConsoleTransport",{enumerable:!0,get:function(){return r.ConsoleTransport}}),Object.defineProperty(e,"ErrorsInstrumentation",{enumerable:!0,get:function(){return r.ErrorsInstrumentation}}),Object.defineProperty(e,"FetchTransport",{enumerable:!0,get:function(){return r.FetchTransport}}),Object.defineProperty(e,"InternalLoggerLevel",{enumerable:!0,get:function(){return r.InternalLoggerLevel}}),Object.defineProperty(e,"LogLevel",{enumerable:!0,get:function(){return r.LogLevel}}),Object.defineProperty(e,"TransportItemType",{enumerable:!0,get:function(){return r.TransportItemType}}),Object.defineProperty(e,"VERSION",{enumerable:!0,get:function(){return r.VERSION}}),Object.defineProperty(e,"ViewInstrumentation",{enumerable:!0,get:function(){return r.ViewInstrumentation}}),Object.defineProperty(e,"WebVitalsInstrumentation",{enumerable:!0,get:function(){return r.WebVitalsInstrumentation}}),Object.defineProperty(e,"allLogLevels",{enumerable:!0,get:function(){return r.allLogLevels}}),Object.defineProperty(e,"browserMeta",{enumerable:!0,get:function(){return r.browserMeta}}),Object.defineProperty(e,"buildStackFrame",{enumerable:!0,get:function(){return r.buildStackFrame}}),Object.defineProperty(e,"createInternalLogger",{enumerable:!0,get:function(){return r.createInternalLogger}}),Object.defineProperty(e,"createPromiseBuffer",{enumerable:!0,get:function(){return r.createPromiseBuffer}}),Object.defineProperty(e,"createSession",{enumerable:!0,get:function(){return r.createSession}}),Object.defineProperty(e,"deepEqual",{enumerable:!0,get:function(){return r.deepEqual}}),Object.defineProperty(e,"defaultEventDomain",{enumerable:!0,get:function(){return r.defaultEventDomain}}),Object.defineProperty(e,"defaultExceptionType",{enumerable:!0,get:function(){return r.defaultExceptionType}}),Object.defineProperty(e,"defaultGlobalObjectKey",{enumerable:!0,get:function(){return r.defaultGlobalObjectKey}}),Object.defineProperty(e,"defaultInternalLoggerLevel",{enumerable:!0,get:function(){return r.defaultInternalLoggerLevel}}),Object.defineProperty(e,"defaultLogLevel",{enumerable:!0,get:function(){return r.defaultLogLevel}}),Object.defineProperty(e,"faro",{enumerable:!0,get:function(){return r.faro}}),Object.defineProperty(e,"genShortID",{enumerable:!0,get:function(){return r.genShortID}}),Object.defineProperty(e,"getCurrentTimestamp",{enumerable:!0,get:function(){return r.getCurrentTimestamp}}),Object.defineProperty(e,"getDataFromSafariExtensions",{enumerable:!0,get:function(){return r.getDataFromSafariExtensions}}),Object.defineProperty(e,"getInternalFaroFromGlobalObject",{enumerable:!0,get:function(){return r.getInternalFaroFromGlobalObject}}),Object.defineProperty(e,"getStackFramesFromError",{enumerable:!0,get:function(){return r.getStackFramesFromError}}),Object.defineProperty(e,"getTransportBody",{enumerable:!0,get:function(){return r.getTransportBody}}),Object.defineProperty(e,"getWebInstrumentations",{enumerable:!0,get:function(){return r.getWebInstrumentations}}),Object.defineProperty(e,"globalObject",{enumerable:!0,get:function(){return r.globalObject}}),Object.defineProperty(e,"initializeFaro",{enumerable:!0,get:function(){return r.initializeFaro}}),Object.defineProperty(e,"internalGlobalObjectKey",{enumerable:!0,get:function(){return r.internalGlobalObjectKey}}),Object.defineProperty(e,"isArray",{enumerable:!0,get:function(){return r.isArray}}),Object.defineProperty(e,"isBoolean",{enumerable:!0,get:function(){return r.isBoolean}}),Object.defineProperty(e,"isDomError",{enumerable:!0,get:function(){return r.isDomError}}),Object.defineProperty(e,"isDomException",{enumerable:!0,get:function(){return r.isDomException}}),Object.defineProperty(e,"isElement",{enumerable:!0,get:function(){return r.isElement}}),Object.defineProperty(e,"isElementDefined",{enumerable:!0,get:function(){return r.isElementDefined}}),Object.defineProperty(e,"isError",{enumerable:!0,get:function(){return r.isError}}),Object.defineProperty(e,"isErrorDefined",{enumerable:!0,get:function(){return r.isErrorDefined}}),Object.defineProperty(e,"isErrorEvent",{enumerable:!0,get:function(){return r.isErrorEvent}}),Object.defineProperty(e,"isEvent",{enumerable:!0,get:function(){return r.isEvent}}),Object.defineProperty(e,"isEventDefined",{enumerable:!0,get:function(){return r.isEventDefined}}),Object.defineProperty(e,"isFunction",{enumerable:!0,get:function(){return r.isFunction}}),Object.defineProperty(e,"isInstanceOf",{enumerable:!0,get:function(){return r.isInstanceOf}}),Object.defineProperty(e,"isInt",{enumerable:!0,get:function(){return r.isInt}}),Object.defineProperty(e,"isInternalFaroOnGlobalObject",{enumerable:!0,get:function(){return r.isInternalFaroOnGlobalObject}}),Object.defineProperty(e,"isMap",{enumerable:!0,get:function(){return r.isMap}}),Object.defineProperty(e,"isMapDefined",{enumerable:!0,get:function(){return r.isMapDefined}}),Object.defineProperty(e,"isNull",{enumerable:!0,get:function(){return r.isNull}}),Object.defineProperty(e,"isNumber",{enumerable:!0,get:function(){return r.isNumber}}),Object.defineProperty(e,"isObject",{enumerable:!0,get:function(){return r.isObject}}),Object.defineProperty(e,"isPrimitive",{enumerable:!0,get:function(){return r.isPrimitive}}),Object.defineProperty(e,"isRegExp",{enumerable:!0,get:function(){return r.isRegExp}}),Object.defineProperty(e,"isString",{enumerable:!0,get:function(){return r.isString}}),Object.defineProperty(e,"isSymbol",{enumerable:!0,get:function(){return r.isSymbol}}),Object.defineProperty(e,"isSyntheticEvent",{enumerable:!0,get:function(){return r.isSyntheticEvent}}),Object.defineProperty(e,"isThenable",{enumerable:!0,get:function(){return r.isThenable}}),Object.defineProperty(e,"isToString",{enumerable:!0,get:function(){return r.isToString}}),Object.defineProperty(e,"isTypeof",{enumerable:!0,get:function(){return r.isTypeof}}),Object.defineProperty(e,"isUndefined",{enumerable:!0,get:function(){return r.isUndefined}}),Object.defineProperty(e,"makeCoreConfig",{enumerable:!0,get:function(){return r.makeCoreConfig}}),Object.defineProperty(e,"noop",{enumerable:!0,get:function(){return r.noop}}),Object.defineProperty(e,"parseStacktrace",{enumerable:!0,get:function(){return r.parseStacktrace}}),Object.defineProperty(e,"setInternalFaroOnGlobalObject",{enumerable:!0,get:function(){return r.setInternalFaroOnGlobalObject}}),Object.defineProperty(e,"transportItemTypeToBodyKey",{enumerable:!0,get:function(){return r.transportItemTypeToBodyKey}}),Object.defineProperty(e,"userActionDataAttribute",{enumerable:!0,get:function(){return r.userActionDataAttribute}}),e.FaroErrorBoundary=m,e.FaroProfiler=q,e.FaroRoute=D,e.FaroRoutes=G,e.ReactIntegration=H,e.createReactRouterV4Options=function(t){return{version:e.ReactRouterVersion.V4,dependencies:t}},e.createReactRouterV5Options=function(t){return{version:e.ReactRouterVersion.V5,dependencies:t}},e.createReactRouterV6DataOptions=function(t){return{version:e.ReactRouterVersion.V6_data_router,dependencies:t}},e.createReactRouterV6Options=function(t){return{version:e.ReactRouterVersion.V6,dependencies:t}},e.createReactRouterV7DataOptions=function(t){return{version:e.ReactRouterVersion.V7_data_router,dependencies:t}},e.createReactRouterV7Options=function(t){return{version:e.ReactRouterVersion.V7,dependencies:t}},e.faroErrorBoundaryInitialState=n,e.getMajorReactVersion=p,e.isReactVersionAtLeast=d,e.isReactVersionAtLeast16=f,e.isReactVersionAtLeast17=l,e.isReactVersionAtLeast18=s,e.isReactVersionAtLeast19=c,e.reactVersion=a,e.reactVersionMajor=u,e.setReactRouterV4V5SSRDependencies=function(e){C=e.Route},e.setReactRouterV6SSRDependencies=function(e){w=e.Routes},e.setReactRouterV7SSRDependencies=function(e){w=e.Routes},e.withFaroErrorBoundary=function(e,r){var n,o;const i=null!==(o=null!==(n=e.displayName)&&void 0!==n?n:e.name)&&void 0!==o?o:$,a=n=>t.createElement(m,Object.assign({},r),t.createElement(e,Object.assign({},n)));return a.displayName=`faroErrorBoundary(${i})`,V(a,e),a},e.withFaroProfiler=function(e,r){var n,o,i;const a=null!==(i=null!==(o=null!==(n=null==r?void 0:r.name)&&void 0!==n?n:e.displayName)&&void 0!==o?o:e.name)&&void 0!==i?i:$,u=r=>t.createElement(q,{name:a,updateProps:r},t.createElement(e,Object.assign({},r)));return u.displayName=`faroProfiler(${a})`,V(u,e),u},e.withFaroRouterInstrumentation=function(t){let n={};return t.subscribe(o=>{var a,u;const c=o.historyAction,s=o.location,l=t.routes;if(A&&(c===e.NavigationType.Push||c===e.NavigationType.Pop)){const e=U(l,s),t=null===(a=r.globalObject.location)||void 0===a?void 0:a.href;i.pushEvent(r.EVENT_ROUTE_CHANGE,Object.assign({toRoute:e,toUrl:null===(u=r.globalObject.location)||void 0===u?void 0:u.href},n)),n={fromRoute:e,fromUrl:t}}}),t},e}({},React,GrafanaFaroWebSdk);
|
|
@@ -8,7 +8,7 @@ export interface FaroProfilerProps {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class FaroProfiler extends Component<FaroProfilerProps> {
|
|
10
10
|
protected mountSpan: Span | undefined;
|
|
11
|
-
protected
|
|
11
|
+
protected renderSpan: Span | undefined;
|
|
12
12
|
protected updateSpan: Span | undefined;
|
|
13
13
|
private get isOtelInitialized();
|
|
14
14
|
private get otel();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -41,7 +41,7 @@ class FaroProfiler extends react_1.Component {
|
|
|
41
41
|
constructor(props) {
|
|
42
42
|
super(props);
|
|
43
43
|
this.mountSpan = undefined;
|
|
44
|
-
this.
|
|
44
|
+
this.renderSpan = undefined;
|
|
45
45
|
this.updateSpan = undefined;
|
|
46
46
|
if (this.isOtelInitialized) {
|
|
47
47
|
this.mountSpan = this.createSpan('componentMount');
|
|
@@ -52,8 +52,19 @@ class FaroProfiler extends react_1.Component {
|
|
|
52
52
|
}
|
|
53
53
|
componentDidMount() {
|
|
54
54
|
if (this.isOtelInitialized && this.mountSpan) {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Let OTel stamp the end via its hrTime helper (`performance.timeOrigin +
|
|
56
|
+
// performance.now()`), which is monotonic. Passing `Date.now()` here would mix the
|
|
57
|
+
// wall clock with the mount span's start time (auto-stamped by OTel from hrTime),
|
|
58
|
+
// making `endTime - startTime` drift with NTP / DST / manual clock changes.
|
|
59
|
+
this.mountSpan.end();
|
|
60
|
+
// Start the `componentRender` span immediately. It covers the live, mounted lifetime
|
|
61
|
+
// of the component and is ended in `componentWillUnmount`. By starting and ending it
|
|
62
|
+
// without explicit timestamps, both boundaries are stamped from OTel hrTime
|
|
63
|
+
// (monotonic). This avoids the wall-clock issues of the previous implementation
|
|
64
|
+
// which captured `Date.now()` here and subtracted it from another `Date.now()` at
|
|
65
|
+
// unmount — a problem that compounded over a long-lived component (router/app
|
|
66
|
+
// shells can live for the entire session).
|
|
67
|
+
this.renderSpan = this.createChildSpan('componentRender', this.mountSpan);
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
shouldComponentUpdate({ updateProps }) {
|
|
@@ -76,11 +87,10 @@ class FaroProfiler extends react_1.Component {
|
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
componentWillUnmount() {
|
|
79
|
-
if (this.isOtelInitialized && this.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
90
|
+
if (this.isOtelInitialized && this.renderSpan) {
|
|
91
|
+
// End via OTel hrTime (monotonic). See `componentDidMount` for rationale.
|
|
92
|
+
this.renderSpan.end();
|
|
93
|
+
this.renderSpan = undefined;
|
|
84
94
|
}
|
|
85
95
|
}
|
|
86
96
|
render() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FaroProfiler.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.tsx"],"names":[],"mappings":";;;AACA,+EAA+E;AAC/E,0EAA0E;AAC1E,6DAA6D;AAC7D,iCAAyC;AAGzC,wDAAgD;AAGhD,kDAAsD;AAQtD,MAAa,YAAa,SAAQ,iBAA4B;IAK5D,IAAY,iBAAiB;QAC3B,OAAO,CAAC,CAAC,CAAA,kBAAG,aAAH,kBAAG,uBAAH,kBAAG,CAAE,iBAAiB,EAAE,CAAA,CAAC;IACpC,CAAC;IAED,IAAY,IAAI;QACd,OAAO,kBAAG,aAAH,kBAAG,uBAAH,kBAAG,CAAE,OAAO,EAAG,CAAC;IACzB,CAAC;IAED,IAAY,MAAM;;QAChB,OAAO,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,EAAE,sBAAO,CAAE,CAAC;IACrE,CAAC;IAEO,UAAU,CAChB,QAAgB,EAChB,OAA2E;;QAE3E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;YAC3C,SAAS,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS;YAC7B,UAAU,kBACR,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IACpC,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,mCAAI,EAAE,CAAC,CAC/B;SACF,CAAC,CAAC;QAEH,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;QAE3D,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,eAAe,CACrB,QAAgB,EAChB,MAAY,EACZ,OAA2E;;QAE3E,IAAI,IAAU,CAAC;QAEf,MAAA,IAAI,CAAC,IAAI,0CAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE;YACxF,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,OAAO,IAAK,CAAC;IACf,CAAC;IAED,YAAY,KAAwB;QAClC,KAAK,CAAC,KAAK,CAAC,CAAC;QApDL,cAAS,GAAqB,SAAS,CAAC;QACxC,
|
|
1
|
+
{"version":3,"file":"FaroProfiler.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { Attributes, Span, Tracer } from '@opentelemetry/api';\n// React is required in scope for JSX transformation with the classic transform\n// @ts-expect-error - TS6133: React appears unused but is required for JSX\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport React, { Component } from 'react';\nimport type { ReactNode } from 'react';\n\nimport { VERSION } from '@grafana/faro-web-sdk';\nimport type { OTELApi } from '@grafana/faro-web-sdk';\n\nimport { api, internalLogger } from '../dependencies';\n\nexport interface FaroProfilerProps {\n children: ReactNode;\n name: string;\n updateProps: Record<string, unknown>;\n}\n\nexport class FaroProfiler extends Component<FaroProfilerProps> {\n protected mountSpan: Span | undefined = undefined;\n protected renderSpan: Span | undefined = undefined;\n protected updateSpan: Span | undefined = undefined;\n\n private get isOtelInitialized(): boolean {\n return !!api?.isOTELInitialized();\n }\n\n private get otel(): OTELApi | undefined {\n return api?.getOTEL()!;\n }\n\n private get tracer(): Tracer {\n return this.otel?.trace.getTracer('@grafana/faro-react', VERSION)!;\n }\n\n private createSpan(\n spanName: string,\n options?: { startTime?: number; endTime?: number; attributes?: Attributes }\n ): Span {\n const span = this.tracer.startSpan(spanName, {\n startTime: options?.startTime,\n attributes: {\n 'react.component.name': this.props.name,\n ...(options?.attributes ?? {}),\n },\n });\n\n this.otel?.trace.setSpan(this.otel.context.active(), span);\n\n if (options?.endTime) {\n span.end(options.endTime);\n }\n\n return span;\n }\n\n private createChildSpan(\n spanName: string,\n parent: Span,\n options?: { startTime?: number; endTime?: number; attributes?: Attributes }\n ): Span {\n let span: Span;\n\n this.otel?.context.with(this.otel.trace.setSpan(this.otel.context.active(), parent), () => {\n span = this.createSpan(spanName, options);\n });\n\n return span!;\n }\n\n constructor(props: FaroProfilerProps) {\n super(props);\n\n if (this.isOtelInitialized) {\n this.mountSpan = this.createSpan('componentMount');\n } else {\n internalLogger?.error(\n 'The Faro React Profiler requires tracing instrumentation. Please enable it in the \"instrumentations\" section of your config.'\n );\n }\n }\n\n override componentDidMount(): void {\n if (this.isOtelInitialized && this.mountSpan) {\n // Let OTel stamp the end via its hrTime helper (`performance.timeOrigin +\n // performance.now()`), which is monotonic. Passing `Date.now()` here would mix the\n // wall clock with the mount span's start time (auto-stamped by OTel from hrTime),\n // making `endTime - startTime` drift with NTP / DST / manual clock changes.\n this.mountSpan.end();\n\n // Start the `componentRender` span immediately. It covers the live, mounted lifetime\n // of the component and is ended in `componentWillUnmount`. By starting and ending it\n // without explicit timestamps, both boundaries are stamped from OTel hrTime\n // (monotonic). This avoids the wall-clock issues of the previous implementation\n // which captured `Date.now()` here and subtracted it from another `Date.now()` at\n // unmount — a problem that compounded over a long-lived component (router/app\n // shells can live for the entire session).\n this.renderSpan = this.createChildSpan('componentRender', this.mountSpan);\n }\n }\n\n override shouldComponentUpdate({ updateProps }: FaroProfilerProps): boolean {\n if (this.isOtelInitialized && this.mountSpan && updateProps !== this.props.updateProps) {\n const changedProps = Object.keys(updateProps).filter((key) => updateProps[key] !== this.props.updateProps[key]);\n\n if (changedProps.length > 0) {\n this.updateSpan = this.createChildSpan('componentUpdate', this.mountSpan, {\n attributes: {\n 'react.component.changed_props': changedProps,\n },\n });\n }\n }\n\n return true;\n }\n\n override componentDidUpdate(): void {\n if (this.isOtelInitialized && this.updateSpan) {\n this.updateSpan.end();\n this.updateSpan = undefined;\n }\n }\n\n override componentWillUnmount(): void {\n if (this.isOtelInitialized && this.renderSpan) {\n // End via OTel hrTime (monotonic). See `componentDidMount` for rationale.\n this.renderSpan.end();\n this.renderSpan = undefined;\n }\n }\n\n override render(): ReactNode {\n return this.props.children;\n }\n}\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const dependencies_1 = require("../dependencies");
|
|
4
|
+
const FaroProfiler_1 = require("./FaroProfiler");
|
|
5
|
+
describe('FaroProfiler', () => {
|
|
6
|
+
let mockSpan;
|
|
7
|
+
let mockChildSpan;
|
|
8
|
+
let startSpanCalls;
|
|
9
|
+
let setSpanMock;
|
|
10
|
+
let activeContextMock;
|
|
11
|
+
let withMock;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockSpan = { end: jest.fn() };
|
|
14
|
+
mockChildSpan = { end: jest.fn() };
|
|
15
|
+
startSpanCalls = [];
|
|
16
|
+
let callIdx = 0;
|
|
17
|
+
const startSpan = jest.fn((name, options) => {
|
|
18
|
+
startSpanCalls.push({ name, options });
|
|
19
|
+
// First call is the mount span (from constructor); subsequent calls are children.
|
|
20
|
+
return callIdx++ === 0 ? mockSpan : mockChildSpan;
|
|
21
|
+
});
|
|
22
|
+
setSpanMock = jest.fn();
|
|
23
|
+
activeContextMock = jest.fn(() => ({}));
|
|
24
|
+
withMock = jest.fn((_ctx, fn) => fn());
|
|
25
|
+
const otelMock = {
|
|
26
|
+
trace: {
|
|
27
|
+
getTracer: () => ({ startSpan }),
|
|
28
|
+
setSpan: setSpanMock,
|
|
29
|
+
},
|
|
30
|
+
context: {
|
|
31
|
+
active: activeContextMock,
|
|
32
|
+
with: withMock,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const apiMock = {
|
|
36
|
+
isOTELInitialized: () => true,
|
|
37
|
+
getOTEL: () => otelMock,
|
|
38
|
+
};
|
|
39
|
+
const internalLoggerMock = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };
|
|
40
|
+
(0, dependencies_1.setDependencies)(internalLoggerMock, apiMock);
|
|
41
|
+
});
|
|
42
|
+
it('does not pass Date.now() to span.end on mount (lets OTel stamp via monotonic hrTime)', () => {
|
|
43
|
+
const dateNowSpy = jest.spyOn(Date, 'now');
|
|
44
|
+
const profiler = new FaroProfiler_1.FaroProfiler({ name: 'C', updateProps: {}, children: null });
|
|
45
|
+
profiler.componentDidMount();
|
|
46
|
+
expect(mockSpan.end).toHaveBeenCalledTimes(1);
|
|
47
|
+
// Critical assertion: span.end was called with no wall-clock timestamp argument.
|
|
48
|
+
// OTel's web SDK stamps the end time via its hrTime helper (performance.timeOrigin +
|
|
49
|
+
// performance.now()), which is monotonic. Passing Date.now() here would mix clocks
|
|
50
|
+
// with the mount span's start (auto-stamped by OTel from hrTime).
|
|
51
|
+
expect(mockSpan.end).toHaveBeenCalledWith();
|
|
52
|
+
expect(dateNowSpy).not.toHaveBeenCalled();
|
|
53
|
+
dateNowSpy.mockRestore();
|
|
54
|
+
});
|
|
55
|
+
it('does not pass Date.now() to componentRender child span on unmount', () => {
|
|
56
|
+
const dateNowSpy = jest.spyOn(Date, 'now');
|
|
57
|
+
const profiler = new FaroProfiler_1.FaroProfiler({ name: 'C', updateProps: {}, children: null });
|
|
58
|
+
profiler.componentDidMount();
|
|
59
|
+
dateNowSpy.mockClear();
|
|
60
|
+
profiler.componentWillUnmount();
|
|
61
|
+
// A componentRender child span should have been created.
|
|
62
|
+
const renderCall = startSpanCalls.find((c) => c.name === 'componentRender');
|
|
63
|
+
expect(renderCall).toBeTruthy();
|
|
64
|
+
// Critical: neither startTime nor endTime should be sourced from Date.now() (wall clock).
|
|
65
|
+
expect(dateNowSpy).not.toHaveBeenCalled();
|
|
66
|
+
// Both timestamps should be undefined (let OTel stamp from hrTime) or, if explicitly
|
|
67
|
+
// provided, derived from a monotonic source. The simplest fix uses undefined.
|
|
68
|
+
if (renderCall.options) {
|
|
69
|
+
expect(renderCall.options.startTime).toBeUndefined();
|
|
70
|
+
expect(renderCall.options.endTime).toBeUndefined();
|
|
71
|
+
}
|
|
72
|
+
dateNowSpy.mockRestore();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=FaroProfiler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaroProfiler.test.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { setDependencies } from '../dependencies';\n\nimport { FaroProfiler } from './FaroProfiler';\n\ndescribe('FaroProfiler', () => {\n let mockSpan: { end: jest.Mock };\n let mockChildSpan: { end: jest.Mock };\n let startSpanCalls: Array<{ name: string; options: any }>;\n let setSpanMock: jest.Mock;\n let activeContextMock: jest.Mock;\n let withMock: jest.Mock;\n\n beforeEach(() => {\n mockSpan = { end: jest.fn() };\n mockChildSpan = { end: jest.fn() };\n startSpanCalls = [];\n\n let callIdx = 0;\n const startSpan = jest.fn((name: string, options: any) => {\n startSpanCalls.push({ name, options });\n // First call is the mount span (from constructor); subsequent calls are children.\n return callIdx++ === 0 ? mockSpan : mockChildSpan;\n });\n\n setSpanMock = jest.fn();\n activeContextMock = jest.fn(() => ({}) as any);\n withMock = jest.fn((_ctx: any, fn: () => void) => fn());\n\n const otelMock: any = {\n trace: {\n getTracer: () => ({ startSpan }),\n setSpan: setSpanMock,\n },\n context: {\n active: activeContextMock,\n with: withMock,\n },\n };\n\n const apiMock: any = {\n isOTELInitialized: () => true,\n getOTEL: () => otelMock,\n };\n\n const internalLoggerMock: any = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };\n\n setDependencies(internalLoggerMock, apiMock);\n });\n\n it('does not pass Date.now() to span.end on mount (lets OTel stamp via monotonic hrTime)', () => {\n const dateNowSpy = jest.spyOn(Date, 'now');\n\n const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null as any });\n profiler.componentDidMount();\n\n expect(mockSpan.end).toHaveBeenCalledTimes(1);\n // Critical assertion: span.end was called with no wall-clock timestamp argument.\n // OTel's web SDK stamps the end time via its hrTime helper (performance.timeOrigin +\n // performance.now()), which is monotonic. Passing Date.now() here would mix clocks\n // with the mount span's start (auto-stamped by OTel from hrTime).\n expect(mockSpan.end).toHaveBeenCalledWith();\n expect(dateNowSpy).not.toHaveBeenCalled();\n\n dateNowSpy.mockRestore();\n });\n\n it('does not pass Date.now() to componentRender child span on unmount', () => {\n const dateNowSpy = jest.spyOn(Date, 'now');\n\n const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null as any });\n profiler.componentDidMount();\n\n dateNowSpy.mockClear();\n profiler.componentWillUnmount();\n\n // A componentRender child span should have been created.\n const renderCall = startSpanCalls.find((c) => c.name === 'componentRender');\n expect(renderCall).toBeTruthy();\n\n // Critical: neither startTime nor endTime should be sourced from Date.now() (wall clock).\n expect(dateNowSpy).not.toHaveBeenCalled();\n\n // Both timestamps should be undefined (let OTel stamp from hrTime) or, if explicitly\n // provided, derived from a monotonic source. The simplest fix uses undefined.\n if (renderCall!.options) {\n expect(renderCall!.options.startTime).toBeUndefined();\n expect(renderCall!.options.endTime).toBeUndefined();\n }\n\n dateNowSpy.mockRestore();\n });\n});\n"]}
|
|
@@ -38,7 +38,7 @@ export class FaroProfiler extends Component {
|
|
|
38
38
|
constructor(props) {
|
|
39
39
|
super(props);
|
|
40
40
|
this.mountSpan = undefined;
|
|
41
|
-
this.
|
|
41
|
+
this.renderSpan = undefined;
|
|
42
42
|
this.updateSpan = undefined;
|
|
43
43
|
if (this.isOtelInitialized) {
|
|
44
44
|
this.mountSpan = this.createSpan('componentMount');
|
|
@@ -49,8 +49,19 @@ export class FaroProfiler extends Component {
|
|
|
49
49
|
}
|
|
50
50
|
componentDidMount() {
|
|
51
51
|
if (this.isOtelInitialized && this.mountSpan) {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// Let OTel stamp the end via its hrTime helper (`performance.timeOrigin +
|
|
53
|
+
// performance.now()`), which is monotonic. Passing `Date.now()` here would mix the
|
|
54
|
+
// wall clock with the mount span's start time (auto-stamped by OTel from hrTime),
|
|
55
|
+
// making `endTime - startTime` drift with NTP / DST / manual clock changes.
|
|
56
|
+
this.mountSpan.end();
|
|
57
|
+
// Start the `componentRender` span immediately. It covers the live, mounted lifetime
|
|
58
|
+
// of the component and is ended in `componentWillUnmount`. By starting and ending it
|
|
59
|
+
// without explicit timestamps, both boundaries are stamped from OTel hrTime
|
|
60
|
+
// (monotonic). This avoids the wall-clock issues of the previous implementation
|
|
61
|
+
// which captured `Date.now()` here and subtracted it from another `Date.now()` at
|
|
62
|
+
// unmount — a problem that compounded over a long-lived component (router/app
|
|
63
|
+
// shells can live for the entire session).
|
|
64
|
+
this.renderSpan = this.createChildSpan('componentRender', this.mountSpan);
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
shouldComponentUpdate({ updateProps }) {
|
|
@@ -73,11 +84,10 @@ export class FaroProfiler extends Component {
|
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
componentWillUnmount() {
|
|
76
|
-
if (this.isOtelInitialized && this.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
87
|
+
if (this.isOtelInitialized && this.renderSpan) {
|
|
88
|
+
// End via OTel hrTime (monotonic). See `componentDidMount` for rationale.
|
|
89
|
+
this.renderSpan.end();
|
|
90
|
+
this.renderSpan = undefined;
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
render() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FaroProfiler.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.tsx"],"names":[],"mappings":"AACA,+EAA+E;AAC/E,0EAA0E;AAC1E,6DAA6D;AAC7D,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGzC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGhD,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQtD,MAAM,OAAO,YAAa,SAAQ,SAA4B;IAK5D,IAAY,iBAAiB;QAC3B,OAAO,CAAC,CAAC,CAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,iBAAiB,EAAE,CAAA,CAAC;IACpC,CAAC;IAED,IAAY,IAAI;QACd,OAAO,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,OAAO,EAAG,CAAC;IACzB,CAAC;IAED,IAAY,MAAM;;QAChB,OAAO,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,CAAC,SAAS,CAAC,qBAAqB,EAAE,OAAO,CAAE,CAAC;IACrE,CAAC;IAEO,UAAU,CAChB,QAAgB,EAChB,OAA2E;;QAE3E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;YAC3C,SAAS,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,SAAS;YAC7B,UAAU,kBACR,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IACpC,CAAC,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,mCAAI,EAAE,CAAC,CAC/B;SACF,CAAC,CAAC;QAEH,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;QAE3D,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,eAAe,CACrB,QAAgB,EAChB,MAAY,EACZ,OAA2E;;QAE3E,IAAI,IAAU,CAAC;QAEf,MAAA,IAAI,CAAC,IAAI,0CAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE;YACxF,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,OAAO,IAAK,CAAC;IACf,CAAC;IAED,YAAY,KAAwB;QAClC,KAAK,CAAC,KAAK,CAAC,CAAC;QApDL,cAAS,GAAqB,SAAS,CAAC;QACxC,
|
|
1
|
+
{"version":3,"file":"FaroProfiler.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { Attributes, Span, Tracer } from '@opentelemetry/api';\n// React is required in scope for JSX transformation with the classic transform\n// @ts-expect-error - TS6133: React appears unused but is required for JSX\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport React, { Component } from 'react';\nimport type { ReactNode } from 'react';\n\nimport { VERSION } from '@grafana/faro-web-sdk';\nimport type { OTELApi } from '@grafana/faro-web-sdk';\n\nimport { api, internalLogger } from '../dependencies';\n\nexport interface FaroProfilerProps {\n children: ReactNode;\n name: string;\n updateProps: Record<string, unknown>;\n}\n\nexport class FaroProfiler extends Component<FaroProfilerProps> {\n protected mountSpan: Span | undefined = undefined;\n protected renderSpan: Span | undefined = undefined;\n protected updateSpan: Span | undefined = undefined;\n\n private get isOtelInitialized(): boolean {\n return !!api?.isOTELInitialized();\n }\n\n private get otel(): OTELApi | undefined {\n return api?.getOTEL()!;\n }\n\n private get tracer(): Tracer {\n return this.otel?.trace.getTracer('@grafana/faro-react', VERSION)!;\n }\n\n private createSpan(\n spanName: string,\n options?: { startTime?: number; endTime?: number; attributes?: Attributes }\n ): Span {\n const span = this.tracer.startSpan(spanName, {\n startTime: options?.startTime,\n attributes: {\n 'react.component.name': this.props.name,\n ...(options?.attributes ?? {}),\n },\n });\n\n this.otel?.trace.setSpan(this.otel.context.active(), span);\n\n if (options?.endTime) {\n span.end(options.endTime);\n }\n\n return span;\n }\n\n private createChildSpan(\n spanName: string,\n parent: Span,\n options?: { startTime?: number; endTime?: number; attributes?: Attributes }\n ): Span {\n let span: Span;\n\n this.otel?.context.with(this.otel.trace.setSpan(this.otel.context.active(), parent), () => {\n span = this.createSpan(spanName, options);\n });\n\n return span!;\n }\n\n constructor(props: FaroProfilerProps) {\n super(props);\n\n if (this.isOtelInitialized) {\n this.mountSpan = this.createSpan('componentMount');\n } else {\n internalLogger?.error(\n 'The Faro React Profiler requires tracing instrumentation. Please enable it in the \"instrumentations\" section of your config.'\n );\n }\n }\n\n override componentDidMount(): void {\n if (this.isOtelInitialized && this.mountSpan) {\n // Let OTel stamp the end via its hrTime helper (`performance.timeOrigin +\n // performance.now()`), which is monotonic. Passing `Date.now()` here would mix the\n // wall clock with the mount span's start time (auto-stamped by OTel from hrTime),\n // making `endTime - startTime` drift with NTP / DST / manual clock changes.\n this.mountSpan.end();\n\n // Start the `componentRender` span immediately. It covers the live, mounted lifetime\n // of the component and is ended in `componentWillUnmount`. By starting and ending it\n // without explicit timestamps, both boundaries are stamped from OTel hrTime\n // (monotonic). This avoids the wall-clock issues of the previous implementation\n // which captured `Date.now()` here and subtracted it from another `Date.now()` at\n // unmount — a problem that compounded over a long-lived component (router/app\n // shells can live for the entire session).\n this.renderSpan = this.createChildSpan('componentRender', this.mountSpan);\n }\n }\n\n override shouldComponentUpdate({ updateProps }: FaroProfilerProps): boolean {\n if (this.isOtelInitialized && this.mountSpan && updateProps !== this.props.updateProps) {\n const changedProps = Object.keys(updateProps).filter((key) => updateProps[key] !== this.props.updateProps[key]);\n\n if (changedProps.length > 0) {\n this.updateSpan = this.createChildSpan('componentUpdate', this.mountSpan, {\n attributes: {\n 'react.component.changed_props': changedProps,\n },\n });\n }\n }\n\n return true;\n }\n\n override componentDidUpdate(): void {\n if (this.isOtelInitialized && this.updateSpan) {\n this.updateSpan.end();\n this.updateSpan = undefined;\n }\n }\n\n override componentWillUnmount(): void {\n if (this.isOtelInitialized && this.renderSpan) {\n // End via OTel hrTime (monotonic). See `componentDidMount` for rationale.\n this.renderSpan.end();\n this.renderSpan = undefined;\n }\n }\n\n override render(): ReactNode {\n return this.props.children;\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { setDependencies } from '../dependencies';
|
|
2
|
+
import { FaroProfiler } from './FaroProfiler';
|
|
3
|
+
describe('FaroProfiler', () => {
|
|
4
|
+
let mockSpan;
|
|
5
|
+
let mockChildSpan;
|
|
6
|
+
let startSpanCalls;
|
|
7
|
+
let setSpanMock;
|
|
8
|
+
let activeContextMock;
|
|
9
|
+
let withMock;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockSpan = { end: jest.fn() };
|
|
12
|
+
mockChildSpan = { end: jest.fn() };
|
|
13
|
+
startSpanCalls = [];
|
|
14
|
+
let callIdx = 0;
|
|
15
|
+
const startSpan = jest.fn((name, options) => {
|
|
16
|
+
startSpanCalls.push({ name, options });
|
|
17
|
+
// First call is the mount span (from constructor); subsequent calls are children.
|
|
18
|
+
return callIdx++ === 0 ? mockSpan : mockChildSpan;
|
|
19
|
+
});
|
|
20
|
+
setSpanMock = jest.fn();
|
|
21
|
+
activeContextMock = jest.fn(() => ({}));
|
|
22
|
+
withMock = jest.fn((_ctx, fn) => fn());
|
|
23
|
+
const otelMock = {
|
|
24
|
+
trace: {
|
|
25
|
+
getTracer: () => ({ startSpan }),
|
|
26
|
+
setSpan: setSpanMock,
|
|
27
|
+
},
|
|
28
|
+
context: {
|
|
29
|
+
active: activeContextMock,
|
|
30
|
+
with: withMock,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const apiMock = {
|
|
34
|
+
isOTELInitialized: () => true,
|
|
35
|
+
getOTEL: () => otelMock,
|
|
36
|
+
};
|
|
37
|
+
const internalLoggerMock = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };
|
|
38
|
+
setDependencies(internalLoggerMock, apiMock);
|
|
39
|
+
});
|
|
40
|
+
it('does not pass Date.now() to span.end on mount (lets OTel stamp via monotonic hrTime)', () => {
|
|
41
|
+
const dateNowSpy = jest.spyOn(Date, 'now');
|
|
42
|
+
const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null });
|
|
43
|
+
profiler.componentDidMount();
|
|
44
|
+
expect(mockSpan.end).toHaveBeenCalledTimes(1);
|
|
45
|
+
// Critical assertion: span.end was called with no wall-clock timestamp argument.
|
|
46
|
+
// OTel's web SDK stamps the end time via its hrTime helper (performance.timeOrigin +
|
|
47
|
+
// performance.now()), which is monotonic. Passing Date.now() here would mix clocks
|
|
48
|
+
// with the mount span's start (auto-stamped by OTel from hrTime).
|
|
49
|
+
expect(mockSpan.end).toHaveBeenCalledWith();
|
|
50
|
+
expect(dateNowSpy).not.toHaveBeenCalled();
|
|
51
|
+
dateNowSpy.mockRestore();
|
|
52
|
+
});
|
|
53
|
+
it('does not pass Date.now() to componentRender child span on unmount', () => {
|
|
54
|
+
const dateNowSpy = jest.spyOn(Date, 'now');
|
|
55
|
+
const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null });
|
|
56
|
+
profiler.componentDidMount();
|
|
57
|
+
dateNowSpy.mockClear();
|
|
58
|
+
profiler.componentWillUnmount();
|
|
59
|
+
// A componentRender child span should have been created.
|
|
60
|
+
const renderCall = startSpanCalls.find((c) => c.name === 'componentRender');
|
|
61
|
+
expect(renderCall).toBeTruthy();
|
|
62
|
+
// Critical: neither startTime nor endTime should be sourced from Date.now() (wall clock).
|
|
63
|
+
expect(dateNowSpy).not.toHaveBeenCalled();
|
|
64
|
+
// Both timestamps should be undefined (let OTel stamp from hrTime) or, if explicitly
|
|
65
|
+
// provided, derived from a monotonic source. The simplest fix uses undefined.
|
|
66
|
+
if (renderCall.options) {
|
|
67
|
+
expect(renderCall.options.startTime).toBeUndefined();
|
|
68
|
+
expect(renderCall.options.endTime).toBeUndefined();
|
|
69
|
+
}
|
|
70
|
+
dateNowSpy.mockRestore();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=FaroProfiler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaroProfiler.test.js","sourceRoot":"","sources":["../../../src/profiler/FaroProfiler.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import { setDependencies } from '../dependencies';\n\nimport { FaroProfiler } from './FaroProfiler';\n\ndescribe('FaroProfiler', () => {\n let mockSpan: { end: jest.Mock };\n let mockChildSpan: { end: jest.Mock };\n let startSpanCalls: Array<{ name: string; options: any }>;\n let setSpanMock: jest.Mock;\n let activeContextMock: jest.Mock;\n let withMock: jest.Mock;\n\n beforeEach(() => {\n mockSpan = { end: jest.fn() };\n mockChildSpan = { end: jest.fn() };\n startSpanCalls = [];\n\n let callIdx = 0;\n const startSpan = jest.fn((name: string, options: any) => {\n startSpanCalls.push({ name, options });\n // First call is the mount span (from constructor); subsequent calls are children.\n return callIdx++ === 0 ? mockSpan : mockChildSpan;\n });\n\n setSpanMock = jest.fn();\n activeContextMock = jest.fn(() => ({}) as any);\n withMock = jest.fn((_ctx: any, fn: () => void) => fn());\n\n const otelMock: any = {\n trace: {\n getTracer: () => ({ startSpan }),\n setSpan: setSpanMock,\n },\n context: {\n active: activeContextMock,\n with: withMock,\n },\n };\n\n const apiMock: any = {\n isOTELInitialized: () => true,\n getOTEL: () => otelMock,\n };\n\n const internalLoggerMock: any = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };\n\n setDependencies(internalLoggerMock, apiMock);\n });\n\n it('does not pass Date.now() to span.end on mount (lets OTel stamp via monotonic hrTime)', () => {\n const dateNowSpy = jest.spyOn(Date, 'now');\n\n const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null as any });\n profiler.componentDidMount();\n\n expect(mockSpan.end).toHaveBeenCalledTimes(1);\n // Critical assertion: span.end was called with no wall-clock timestamp argument.\n // OTel's web SDK stamps the end time via its hrTime helper (performance.timeOrigin +\n // performance.now()), which is monotonic. Passing Date.now() here would mix clocks\n // with the mount span's start (auto-stamped by OTel from hrTime).\n expect(mockSpan.end).toHaveBeenCalledWith();\n expect(dateNowSpy).not.toHaveBeenCalled();\n\n dateNowSpy.mockRestore();\n });\n\n it('does not pass Date.now() to componentRender child span on unmount', () => {\n const dateNowSpy = jest.spyOn(Date, 'now');\n\n const profiler = new FaroProfiler({ name: 'C', updateProps: {}, children: null as any });\n profiler.componentDidMount();\n\n dateNowSpy.mockClear();\n profiler.componentWillUnmount();\n\n // A componentRender child span should have been created.\n const renderCall = startSpanCalls.find((c) => c.name === 'componentRender');\n expect(renderCall).toBeTruthy();\n\n // Critical: neither startTime nor endTime should be sourced from Date.now() (wall clock).\n expect(dateNowSpy).not.toHaveBeenCalled();\n\n // Both timestamps should be undefined (let OTel stamp from hrTime) or, if explicitly\n // provided, derived from a monotonic source. The simplest fix uses undefined.\n if (renderCall!.options) {\n expect(renderCall!.options.startTime).toBeUndefined();\n expect(renderCall!.options.endTime).toBeUndefined();\n }\n\n dateNowSpy.mockRestore();\n });\n});\n"]}
|
|
@@ -8,7 +8,7 @@ export interface FaroProfilerProps {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class FaroProfiler extends Component<FaroProfilerProps> {
|
|
10
10
|
protected mountSpan: Span | undefined;
|
|
11
|
-
protected
|
|
11
|
+
protected renderSpan: Span | undefined;
|
|
12
12
|
protected updateSpan: Span | undefined;
|
|
13
13
|
private get isOtelInitialized();
|
|
14
14
|
private get otel();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/faro-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Faro package that enables easier integration in projects built with React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"observability",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"quality:circular-deps": "madge --circular ."
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@grafana/faro-web-sdk": "^2.
|
|
59
|
-
"@grafana/faro-web-tracing": "^2.
|
|
58
|
+
"@grafana/faro-web-sdk": "^2.6.0",
|
|
59
|
+
"@grafana/faro-web-tracing": "^2.6.0",
|
|
60
60
|
"hoist-non-react-statics": "^3.3.2"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@types/react": "19.2.14",
|
|
65
65
|
"react": "19.2.5",
|
|
66
66
|
"react-dom": "19.2.5",
|
|
67
|
-
"react-router": "7.
|
|
67
|
+
"react-router": "7.15.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
@@ -86,5 +86,5 @@
|
|
|
86
86
|
"publishConfig": {
|
|
87
87
|
"access": "public"
|
|
88
88
|
},
|
|
89
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "477938cfe351474ca7a609883e84a8eaee6c3457"
|
|
90
90
|
}
|