UNPKG

observ-metrics

Version:

Frontend monitoring library that eliminates noise and adds business context to OpenTelemetry data

3 lines (2 loc) 29.3 kB
"use strict";var e=require("@opentelemetry/sdk-trace-web"),t=require("@opentelemetry/resources"),s=require("@opentelemetry/semantic-conventions"),i=require("@opentelemetry/auto-instrumentations-web"),n=require("@opentelemetry/api");class r{constructor(e){this.config=e,this.initializePatterns()}initializePatterns(){this.botPatterns=[/bot/i,/crawler/i,/spider/i,/scraper/i,/phantom/i,/headless/i,/selenium/i,/puppeteer/i,/playwright/i,/googlebot/i,/bingbot/i,/slurp/i,/facebookexternalhit/i,/twitterbot/i,/linkedinbot/i,/whatsapp/i,/telegrambot/i],this.extensionPatterns=[/chrome-extension:/i,/moz-extension:/i,/safari-extension:/i,/extension\//i,/addon/i,/greasemonkey/i,/tampermonkey/i,/adblock/i,/ublock/i,/ghostery/i,/privacy/i,/disconnect/i],this.noisePatterns=[/\.css(\?|$)/,/\.js(\?|$)/,/\.png(\?|$)/,/\.jpg(\?|$)/,/\.gif(\?|$)/,/\.svg(\?|$)/,/\.woff(\?|$)/,/\.ttf(\?|$)/,/\.ico(\?|$)/,/favicon/,/manifest\.json/,/robots\.txt/,/sw\.js/,/service-worker/,/workbox/]}shouldProcess(e,t){if("error"===e.severity||"critical"===e.severity)return!0;if(this.config.enableBotDetection&&this.isBotSession(t))return!1;if(!this.isDomainAllowed(e))return!1;if(this.isNoiseEvent(e))return!1;if(this.config.excludeExtensions&&this.isExtensionEvent(e))return!1;if(this.config.customFilters)for(const s of this.config.customFilters)if(!s(e,t))return!1;return!(Math.random()>this.config.samplingRate)}isBotSession(e){const t=navigator.userAgent||"";return!!this.botPatterns.some(e=>e.test(t))||(!!this.hasHeadlessIndicators()||(!!this.hasAutomationIndicators()||!!this.hasUnusualSessionCharacteristics(e)))}hasHeadlessIndicators(){return"undefined"!=typeof window&&(!(!window.chrome||window.chrome.runtime)||(!(!window.callPhantom&&!window._phantom)||(!("undefined"==typeof window||window.outerHeight&&window.outerWidth)||!!navigator.webdriver)))}hasAutomationIndicators(){return"undefined"!=typeof window&&(!!window.document.$cdc_asdjflasutopfhvcZLmcfl_||(!!window.document.$chrome_asyncScriptInfo||!!navigator.permissions?.query?.toString().includes("NotSupportedError")))}hasUnusualSessionCharacteristics(e){return 0===screen.width||0===screen.height}isDomainAllowed(e){if(!this.config.domainWhitelist||0===this.config.domainWhitelist.length)return!0;const t="undefined"!=typeof window&&window.location?window.location.hostname:"localhost";return this.config.domainWhitelist.includes(t)}isNoiseEvent(e){const t=e.attributes["http.url"]||e.attributes.url||"";return this.noisePatterns.some(e=>e.test(t))}isExtensionEvent(e){const t=e.attributes["http.url"]||e.attributes.url||"",s=e.attributes["error.stack"]||"";return this.extensionPatterns.some(e=>e.test(t)||e.test(s))}isRealUserSession(e){return"undefined"!=typeof window&&(!this.isBotSession(e||this.createDefaultContext())&&this.hasRealUserIndicators())}hasRealUserIndicators(){if("ontouchstart"in window)return!0;if("undefined"!=typeof window&&window.innerWidth>0&&window.innerHeight>0)return!0;(new Date).getTimezoneOffset();return!0}createDefaultContext(){return{sessionId:`session_${Date.now()}`,userSegment:"anonymous",isAuthenticated:!1,deviceType:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop"}}getStats(){return{botPatternsCount:this.botPatterns.length,extensionPatternsCount:this.extensionPatterns.length,noisePatternsCount:this.noisePatterns.length,samplingRate:this.config.samplingRate,config:{enableBotDetection:this.config.enableBotDetection,domainWhitelist:this.config.domainWhitelist,excludeExtensions:this.config.excludeExtensions}}}addCustomFilter(e){this.config.customFilters||(this.config.customFilters=[]),this.config.customFilters.push(e)}updateConfig(e){this.config={...this.config,...e},void 0===e.samplingRate&&void 0===e.enableBotDetection&&void 0===e.excludeExtensions||this.initializePatterns()}}class o{constructor(e,t,s){this.domain=e,this.userContext=t,this.onEvent=s,this.domainMetrics=new Map,this.tracer=n.trace.getTracer(`observ-metrics-${e.name}`),this.meter=n.metrics.getMeter(`observ-metrics-${e.name}`),this.initializeMetrics()}initializeMetrics(){this.domainMetrics.set("api_calls",this.meter.createCounter(`${this.domain.name}_api_calls_total`,{description:`Total API calls for ${this.domain.name} domain`})),this.domainMetrics.set("api_duration",this.meter.createHistogram(`${this.domain.name}_api_duration_ms`,{description:`API call duration for ${this.domain.name} domain`})),this.domainMetrics.set("errors",this.meter.createCounter(`${this.domain.name}_errors_total`,{description:`Total errors for ${this.domain.name} domain`})),this.domainMetrics.set("journey_steps",this.meter.createCounter(`${this.domain.name}_journey_steps_total`,{description:`Total journey steps for ${this.domain.name} domain`})),this.domainMetrics.set("journey_duration",this.meter.createHistogram(`${this.domain.name}_journey_duration_ms`,{description:`Journey step duration for ${this.domain.name} domain`})),this.domainMetrics.set("business_metrics",this.meter.createHistogram(`${this.domain.name}_business_metrics`,{description:`Business metrics for ${this.domain.name} domain`}))}async instrumentApiCall(e,t,s="GET",i={}){const n=`${this.domain.name}.${e}`,r=this.tracer.startSpan(n),o=Date.now();try{const a={"domain.name":this.domain.name,"domain.priority":this.domain.priority,"domain.sla_target_ms":this.domain.slaTarget,"domain.error_threshold":this.domain.errorThreshold,"api.name":e,"http.method":s,"http.url":t,"user.session_id":this.userContext.sessionId,"user.segment":this.userContext.userSegment,"user.authenticated":this.userContext.isAuthenticated,"user.device_type":this.userContext.deviceType,...i.journeyName&&{"journey.name":i.journeyName,"journey.step":i.stepName||"unknown"},...i.customAttributes,...this.domain.customAttributes};r.setAttributes(a);const u=await this.executeApiCall(t,s),c=Date.now()-o;this.domainMetrics.get("api_calls")?.add(1,{domain:this.domain.name,api_name:e,method:s,status:u.status.toString(),user_segment:this.userContext.userSegment}),this.domainMetrics.get("api_duration")?.record(c,{domain:this.domain.name,api_name:e,method:s,user_segment:this.userContext.userSegment});const m=c>this.domain.slaTarget;return m&&r.setAttributes({"sla.violated":!0,"sla.target_ms":this.domain.slaTarget,"sla.actual_ms":c,"sla.violation_severity":this.getSlaViolationSeverity(c)}),r.setAttributes({"http.status_code":u.status,"http.response_time_ms":c,"api.success":u.success}),this.emitTelemetryEvent({eventType:"span",name:n,attributes:a,businessContext:this.createBusinessContext(e,i),severity:m?"warn":"info"}),{success:!0,duration:c,customMetrics:{sla_violated:m?1:0,response_status:u.status}}}catch(t){const s=Date.now()-o;return r.recordException(t),r.setStatus({code:2,message:t.message}),this.domainMetrics.get("errors")?.add(1,{domain:this.domain.name,api_name:e,error_type:t.name,user_segment:this.userContext.userSegment}),this.emitTelemetryEvent({eventType:"error",name:`${n}_error`,attributes:{"error.type":t.name,"error.message":t.message,"error.stack":t.stack},businessContext:this.createBusinessContext(e,i),severity:"error"}),{success:!1,duration:s,error:t,customMetrics:{error_occurred:1}}}finally{r.end()}}async instrumentUserJourney(e,t,s){const i=`${this.domain.name}.journey.${e}.${t}`,n=this.tracer.startSpan(i),r=Date.now();try{const o={"domain.name":this.domain.name,"journey.name":e,"journey.step":t,"journey.step_number":this.getStepNumber(e,t),"user.session_id":this.userContext.sessionId,"user.segment":this.userContext.userSegment,"conversion.tracking":!0};n.setAttributes(o);await s();const a=Date.now()-r;return this.domainMetrics.get("journey_steps")?.add(1,{domain:this.domain.name,journey:e,step:t,success:"true",user_segment:this.userContext.userSegment}),this.domainMetrics.get("journey_duration")?.record(a,{domain:this.domain.name,journey:e,step:t,user_segment:this.userContext.userSegment}),this.emitTelemetryEvent({eventType:"span",name:i,attributes:o,businessContext:{domain:this.domain.name,userJourney:e,businessImpact:this.getJourneyBusinessImpact(e)},severity:"info"}),{success:!0,duration:a,customMetrics:{journey_step_completed:1,step_duration_ms:a}}}catch(s){const i=Date.now()-r;return n.recordException(s),n.setStatus({code:2,message:s.message}),this.domainMetrics.get("journey_steps")?.add(1,{domain:this.domain.name,journey:e,step:t,success:"false",error_type:s.name,user_segment:this.userContext.userSegment}),{success:!1,duration:i,error:s,customMetrics:{journey_step_failed:1}}}finally{n.end()}}recordBusinessMetric(e,t,s={}){const i={domain:this.domain.name,metric_name:e,user_segment:this.userContext.userSegment,...s};this.domainMetrics.get("business_metrics")?.record(t,i),this.emitTelemetryEvent({eventType:"metric",name:`${this.domain.name}.business.${e}`,attributes:{"metric.name":e,"metric.value":t,...i},businessContext:{domain:this.domain.name,businessImpact:this.getMetricBusinessImpact(e)},severity:"info"})}trackError(e,t={}){const s=this.tracer.startSpan(`${this.domain.name}.error`);try{s.recordException(e),s.setAttributes({"domain.name":this.domain.name,"error.type":e.name,"error.message":e.message,"user.session_id":this.userContext.sessionId,...t}),this.domainMetrics.get("errors")?.add(1,{domain:this.domain.name,error_type:e.name,user_segment:this.userContext.userSegment}),this.emitTelemetryEvent({eventType:"error",name:`${this.domain.name}.error`,attributes:{"error.type":e.name,"error.message":e.message,"error.stack":e.stack,...t},businessContext:{domain:this.domain.name,businessImpact:"reliability"},severity:"error"})}finally{s.end()}}async executeApiCall(e,t){const s=("critical"===this.domain.priority?100:200)+300*Math.random();if(await new Promise(e=>setTimeout(e,s)),Math.random()<this.domain.errorThreshold)throw new Error(`API call failed: ${e}`);return{status:200,success:!0,data:{message:"Success"}}}createBusinessContext(e,t){return{domain:this.domain.name,feature:e,userJourney:t.journeyName,businessImpact:this.getApiBusinessImpact(e)}}getApiBusinessImpact(e){return{login:"engagement",register:"engagement",checkout:"revenue",payment:"revenue",search:"engagement",cart:"revenue"}[e]||"performance"}getJourneyBusinessImpact(e){return{user_login_flow:"engagement",purchase_flow:"revenue",product_discovery:"engagement"}[e]||"engagement"}getMetricBusinessImpact(e){return e.includes("revenue")||e.includes("purchase")?"revenue":e.includes("login")||e.includes("engagement")?"engagement":e.includes("latency")||e.includes("performance")?"performance":"reliability"}getSlaViolationSeverity(e){const t=e/this.domain.slaTarget;return t>3?"critical":t>2?"high":t>1.5?"medium":"low"}getStepNumber(e,t){return({user_login_flow:["load_login_page","enter_credentials","submit_login","redirect_dashboard"],purchase_flow:["add_to_cart","view_cart","enter_shipping","select_payment","complete_purchase"],product_discovery:["search_query","view_results","filter_results","select_product"]}[e]||[]).indexOf(t)+1}emitTelemetryEvent(e){if(this.onEvent){const t={id:`${Date.now()}_${Math.random().toString(36).substr(2,9)}`,timestamp:(new Date).toISOString(),domain:this.domain.name,eventType:"span",name:"",attributes:{},businessContext:{domain:this.domain.name,businessImpact:"performance"},...e};this.onEvent(t)}}getStats(){return{domain:this.domain.name,priority:this.domain.priority,slaTarget:this.domain.slaTarget,errorThreshold:this.domain.errorThreshold,features:this.domain.features,userContext:{segment:this.userContext.userSegment,authenticated:this.userContext.isAuthenticated,deviceType:this.userContext.deviceType}}}}class a{constructor(){this.name="datadog",this.batchedEvents=[],this.config={platform:"datadog",batchSize:100,flushInterval:5e3}}configure(e){this.config={...this.config,...e},this.config.apiKey||console.warn("[DatadogExporter] No API key provided. Events will be logged to console."),"undefined"!=typeof window&&this.config.apiKey&&this.initializeDatadogRUM(),this.startFlushTimer()}initializeDatadogRUM(){console.log("[DatadogExporter] Initializing Datadog RUM with enhanced business context")}async export(e){this.batchedEvents.push(...e),this.batchedEvents.length>=(this.config.batchSize||100)&&await this.flush()}async flush(){if(0===this.batchedEvents.length)return;const e=[...this.batchedEvents];this.batchedEvents=[];try{this.config.apiKey&&this.config.endpoint?await this.sendToDatadog(e):this.logToConsole(e)}catch(t){console.error("[DatadogExporter] Failed to export events:",t),this.batchedEvents.unshift(...e.slice(0,50))}}async sendToDatadog(e){const t=e.map(e=>this.convertToDatadogFormat(e)),s=await fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/json","DD-API-KEY":this.config.apiKey,...this.config.customHeaders},body:JSON.stringify(t)});if(!s.ok)throw new Error(`Datadog API error: ${s.status} ${s.statusText}`);console.log(`[DatadogExporter] Successfully exported ${e.length} events to Datadog`)}convertToDatadogFormat(e){const t={timestamp:new Date(e.timestamp).getTime(),source:"observ-metrics",service:"frontend-app",host:"undefined"!=typeof window&&window.location?window.location.hostname:"unknown",tags:[`domain:${e.domain}`,`business_impact:${e.businessContext.businessImpact}`,`user_segment:${e.attributes["user.segment"]||"unknown"}`,`device_type:${e.attributes["user.device_type"]||"unknown"}`,`priority:${e.attributes["domain.priority"]||"medium"}`,...e.businessContext.feature?[`feature:${e.businessContext.feature}`]:[],...e.businessContext.userJourney?[`journey:${e.businessContext.userJourney}`]:[]]};switch(e.eventType){case"span":return{...t,message:`${e.name} - ${e.attributes["api.name"]||"Unknown API"}`,level:this.mapSeverityToDatadogLevel(e.severity),span:{trace_id:e.attributes["trace.id"],span_id:e.attributes["span.id"],service:e.domain,name:e.name,resource:e.attributes["http.url"]||e.name,duration:e.attributes["http.response_time_ms"]||0,meta:{"business.domain":e.domain,"business.impact":e.businessContext.businessImpact,"business.feature":e.businessContext.feature,"business.journey":e.businessContext.userJourney,"sla.target":e.attributes["domain.sla_target_ms"],"sla.violated":e.attributes["sla.violated"],...this.extractRelevantAttributes(e.attributes)}}};case"metric":return{...t,metric:e.attributes["metric.name"],points:[[t.timestamp/1e3,e.attributes["metric.value"]]],type:"gauge",tags:[...t.tags,"metric_type:business",`impact_category:${e.businessContext.businessImpact}`]};case"error":return{...t,message:e.attributes["error.message"]||"Unknown error",level:"error",error:{kind:e.attributes["error.type"],message:e.attributes["error.message"],stack:e.attributes["error.stack"]},attributes:{"business.domain":e.domain,"business.impact":e.businessContext.businessImpact,"business.feature":e.businessContext.feature,"error.business_critical":this.isBusinessCriticalError(e),...this.extractRelevantAttributes(e.attributes)}};default:return{...t,message:e.name,level:this.mapSeverityToDatadogLevel(e.severity),attributes:e.attributes}}}mapSeverityToDatadogLevel(e){return{critical:"error",error:"error",warn:"warn",info:"info"}[e||"info"]||"info"}isBusinessCriticalError(e){return["authentication","ecommerce"].includes(e.domain)||e.businessContext.feature&&["login","checkout","payment"].includes(e.businessContext.feature)}extractRelevantAttributes(e){const t=["user.session_id","user.segment","user.authenticated","journey.name","journey.step","http.method","http.status_code","http.response_time_ms","api.name","api.success","domain.priority","domain.sla_target_ms"],s={};for(const i of t)void 0!==e[i]&&(s[i]=e[i]);return s}logToConsole(e){console.group(`[DatadogExporter] Exporting ${e.length} events (console fallback)`),e.forEach(e=>{const t=this.mapSeverityToLogLevel(e.severity),s=`[${e.domain}:${e.businessContext.businessImpact}]`;console[t](`${s} ${e.name}`,{timestamp:e.timestamp,eventType:e.eventType,attributes:this.extractRelevantAttributes(e.attributes),businessContext:e.businessContext})}),console.groupEnd()}mapSeverityToLogLevel(e){return{critical:"error",error:"error",warn:"warn",info:"info"}[e||"info"]||"log"}startFlushTimer(){this.flushTimer&&clearInterval(this.flushTimer),this.flushTimer=setInterval(()=>{this.batchedEvents.length>0&&this.flush()},this.config.flushInterval||5e3)}async forceFlush(){await this.flush()}destroy(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.batchedEvents.length>0&&this.flush()}}class u{constructor(){this.name="newrelic",this.batchedEvents=[],this.config={platform:"newrelic",batchSize:50,flushInterval:1e4}}configure(e){this.config={...this.config,...e},this.config.apiKey||console.warn("[NewRelicExporter] No API key provided. Events will be logged to console."),"undefined"!=typeof window&&this.config.apiKey&&this.initializeNewRelicBrowser(),this.startFlushTimer()}initializeNewRelicBrowser(){console.log("[NewRelicExporter] Initializing New Relic Browser with business context")}async export(e){this.batchedEvents.push(...e),this.batchedEvents.length>=(this.config.batchSize||50)&&await this.flush()}async flush(){if(0===this.batchedEvents.length)return;const e=[...this.batchedEvents];this.batchedEvents=[];try{this.config.apiKey?await this.sendToNewRelic(e):this.logToConsole(e)}catch(t){console.error("[NewRelicExporter] Failed to export events:",t),this.batchedEvents.unshift(...e.slice(0,25))}}async sendToNewRelic(e){const t=e.filter(e=>"span"===e.eventType),s=e.filter(e=>"metric"===e.eventType||"log"===e.eventType),i=e.filter(e=>"error"===e.eventType);t.length>0&&await this.sendSpansToNewRelic(t),s.length>0&&await this.sendCustomEventsToNewRelic(s),i.length>0&&await this.sendErrorsToNewRelic(i)}async sendSpansToNewRelic(e){const t=e.map(e=>this.convertToNewRelicSpan(e)),s=await fetch(`${this.config.endpoint}/trace/v1`,{method:"POST",headers:{"Content-Type":"application/json","Api-Key":this.config.apiKey,"Data-Format":"newrelic","Data-Format-Version":"1",...this.config.customHeaders},body:JSON.stringify([{common:{attributes:{"service.name":"observ-metrics-app","service.version":"1.0.0"}},spans:t}])});if(!s.ok)throw new Error(`New Relic Trace API error: ${s.status}`)}async sendCustomEventsToNewRelic(e){const t=e.map(e=>this.convertToNewRelicCustomEvent(e)),s=await fetch(`${this.config.endpoint}/v1/accounts/${this.config.accountId}/events`,{method:"POST",headers:{"Content-Type":"application/json","Api-Key":this.config.apiKey,...this.config.customHeaders},body:JSON.stringify(t)});if(!s.ok)throw new Error(`New Relic Events API error: ${s.status}`)}async sendErrorsToNewRelic(e){const t=e.map(e=>this.convertToNewRelicError(e)),s=await fetch(`${this.config.endpoint}/v1/accounts/${this.config.accountId}/events`,{method:"POST",headers:{"Content-Type":"application/json","Api-Key":this.config.apiKey,...this.config.customHeaders},body:JSON.stringify(t)});if(!s.ok)throw new Error(`New Relic Error API error: ${s.status}`)}convertToNewRelicSpan(e){return{id:e.id,"trace.id":e.attributes["trace.id"]||e.id,timestamp:1e3*new Date(e.timestamp).getTime(),name:e.name,"service.name":e.domain,"duration.ms":e.attributes["http.response_time_ms"]||0,attributes:{"business.domain":e.domain,"business.impact":e.businessContext.businessImpact,"business.feature":e.businessContext.feature,"business.priority":e.attributes["domain.priority"],"user.segment":e.attributes["user.segment"],"user.authenticated":e.attributes["user.authenticated"],"user.device":e.attributes["user.device_type"],"journey.name":e.businessContext.userJourney,"journey.step":e.attributes["journey.step"],"http.method":e.attributes["http.method"],"http.status_code":e.attributes["http.status_code"],"http.url":e.attributes["http.url"],"sla.target":e.attributes["domain.sla_target_ms"],"sla.violated":e.attributes["sla.violated"],"sla.violation_severity":e.attributes["sla.violation_severity"],...this.extractBusinessMetrics(e.attributes)}}}convertToNewRelicCustomEvent(e){return{eventType:"metric"===e.eventType?"BusinessMetric":"ObservMetricsEvent",timestamp:new Date(e.timestamp).getTime(),name:e.name,domain:e.domain,businessImpact:e.businessContext.businessImpact,..."metric"===e.eventType&&{metricName:e.attributes["metric.name"],metricValue:e.attributes["metric.value"],metricType:"business"},userSegment:e.attributes["user.segment"],userAuthenticated:e.attributes["user.authenticated"],deviceType:e.attributes["user.device_type"],journeyName:e.businessContext.userJourney,journeyStep:e.attributes["journey.step"],feature:e.businessContext.feature,priority:e.attributes["domain.priority"],...this.extractRelevantAttributes(e.attributes)}}convertToNewRelicError(e){return{eventType:"JavaScriptError",timestamp:new Date(e.timestamp).getTime(),errorClass:e.attributes["error.type"],errorMessage:e.attributes["error.message"],stackTrace:e.attributes["error.stack"],businessDomain:e.domain,businessImpact:e.businessContext.businessImpact,businessFeature:e.businessContext.feature,businessCritical:this.isBusinessCriticalError(e),userSegment:e.attributes["user.segment"],userAuthenticated:e.attributes["user.authenticated"],userSession:e.attributes["user.session_id"],journeyName:e.businessContext.userJourney,journeyStep:e.attributes["journey.step"],url:e.attributes["http.url"]||("undefined"!=typeof window&&window.location?window.location.href:"unknown"),userAgent:navigator.userAgent,domainPriority:e.attributes["domain.priority"],...this.extractRelevantAttributes(e.attributes)}}isBusinessCriticalError(e){return["authentication","ecommerce"].includes(e.domain)||e.businessContext.feature&&["login","checkout","payment"].includes(e.businessContext.feature)}extractBusinessMetrics(e){const t={};return e["sla.violated"]&&(t.sla_violation=1,t.sla_violation_ms=e["sla.actual_ms"]-e["sla.target_ms"]),e["http.response_time_ms"]&&(t.response_time_category=this.categorizeResponseTime(e["http.response_time_ms"])),e["journey.step"]&&(t.journey_step_completed=1),t}categorizeResponseTime(e){return e<100?"excellent":e<300?"good":e<1e3?"acceptable":e<3e3?"slow":"very_slow"}extractRelevantAttributes(e){const t=["api.name","api.success","http.method","http.status_code","domain.sla_target_ms","domain.error_threshold"],s={};for(const i of t)if(void 0!==e[i]){s[i.replace(/\./g,"_")]=e[i]}return s}logToConsole(e){console.group(`[NewRelicExporter] Exporting ${e.length} events (console fallback)`),e.forEach(e=>{const t=`[${e.domain}:${e.businessContext.businessImpact}]`,s={eventType:e.eventType,name:e.name,timestamp:e.timestamp,businessContext:e.businessContext,relevantAttributes:this.extractRelevantAttributes(e.attributes)};"error"===e.severity||"critical"===e.severity?console.error(`${t} ${e.name}`,s):"warn"===e.severity?console.warn(`${t} ${e.name}`,s):console.info(`${t} ${e.name}`,s)}),console.groupEnd()}startFlushTimer(){this.flushTimer&&clearInterval(this.flushTimer),this.flushTimer=setInterval(()=>{this.batchedEvents.length>0&&this.flush()},this.config.flushInterval||1e4)}async forceFlush(){await this.flush()}destroy(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),this.batchedEvents.length>0&&this.flush()}}class c{constructor(e){this.config=e,this.instrumentors=new Map,this.exporters=[],this.events=[],this.isInitialized=!1,this.config={userContext:{},...e,platform:{platform:"console",...e.platform}},this.Filter=new r(this.config.filtering),this.setupExporters()}async initialize(){if(this.isInitialized)console.warn("[ObservMetrics] Already initialized");else if(this.Filter.isRealUserSession())try{await this.initializeOpenTelemetry(),this.initializeDomainInstrumentors(),this.isInitialized=!0,this.config.debug&&(console.log("[ObservMetrics] Successfully initialized with business context monitoring"),this.logConfiguration())}catch(e){throw console.error("[ObservMetrics] Failed to initialize:",e),e}else this.config.debug&&console.log("[ObservMetrics] Bot/automated session detected, skipping initialization")}async initializeOpenTelemetry(){const n=new e.WebTracerProvider({resource:new t.Resource({[s.SemanticResourceAttributes.SERVICE_NAME]:"observ-metrics-app",[s.SemanticResourceAttributes.SERVICE_VERSION]:"1.0.0",[s.SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]:process.env.NODE_ENV||"development"})}),r=i.getWebAutoInstrumentations({"@opentelemetry/instrumentation-fetch":{requestHook:(e,t)=>{const s=this.createEventFromSpan(e,"fetch");this.Filter.shouldProcess(s,this.getCurrentUserContext())?this.enrichSpanWithBusinessContext(e,t):e.end()}},"@opentelemetry/instrumentation-xml-http-request":{requestHook:(e,t)=>{const s=this.createEventFromSpan(e,"xhr");this.Filter.shouldProcess(s,this.getCurrentUserContext())?this.enrichSpanWithBusinessContext(e,t):e.end()}}});n.register(),r.forEach(e=>{"enable"in e&&e.enable()})}setupExporters(){switch(this.config.platform.platform){case"datadog":const e=new a;e.configure(this.config.platform),this.exporters.push(e);break;case"newrelic":const t=new u;t.configure(this.config.platform),this.exporters.push(t);break;default:this.exporters.push({name:"console",configure:()=>{},export:async e=>{console.group(`[ObservMetrics] ${e.length} business events`),e.forEach(e=>{const t=`[${e.domain}:${e.businessContext.businessImpact}]`;console.log(`${t} ${e.name}`,e.attributes)}),console.groupEnd()}})}}initializeDomainInstrumentors(){this.config.domains.forEach(e=>{const t=new o(e,this.getCurrentUserContext(),e=>this.handleTelemetryEvent(e));this.instrumentors.set(e.name,t)})}getDomainInstrumentor(e){const t=this.instrumentors.get(e);if(!t)throw new Error(`Domain instrumentor not found: ${e}. Available domains: ${Array.from(this.instrumentors.keys()).join(", ")}`);return t}auth(){return this.getDomainInstrumentor("authentication")||this.getDomainInstrumentor("auth")}ecommerce(){return this.getDomainInstrumentor("ecommerce")||this.getDomainInstrumentor("commerce")}content(){return this.getDomainInstrumentor("content")}updateUserContext(e){this.config.userContext={...this.config.userContext,...e},this.instrumentors.forEach(e=>{const t=this.config.domains.find(t=>t.name===e.getStats().domain);if(t){const e=new o(t,this.getCurrentUserContext(),e=>this.handleTelemetryEvent(e));this.instrumentors.set(t.name,e)}})}addFilter(e){this.Filter.addCustomFilter(e)}getStats(){return{initialized:this.isInitialized,domains:Array.from(this.instrumentors.keys()),eventsProcessed:this.events.length,filterStats:this.Filter.getStats(),userContext:this.getCurrentUserContext(),exporters:this.exporters.map(e=>e.name)}}handleTelemetryEvent(e){const t=this.getCurrentUserContext();this.Filter.shouldProcess(e,t)?(this.events.push(e),this.exporters.forEach(async t=>{try{await t.export([e])}catch(e){console.error(`[ObservMetrics] Export failed for ${t.name}:`,e)}})):this.config.debug&&console.log(`[ObservMetrics] Event filtered out: ${e.name}`)}getCurrentUserContext(){return{sessionId:`session_${Date.now()}`,userSegment:"anonymous",isAuthenticated:!1,deviceType:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop",...this.config.userContext}}createEventFromSpan(e,t){return{id:`${Date.now()}_${Math.random().toString(36).substr(2,9)}`,timestamp:(new Date).toISOString(),domain:"unknown",eventType:"span",name:e.name||t,attributes:e.attributes||{},businessContext:{domain:"unknown",businessImpact:"performance"}}}enrichSpanWithBusinessContext(e,t){const s=t.url||t.requestURL||"",i=this.inferDomainFromUrl(s);i&&e.setAttributes({"business.domain":i.name,"business.priority":i.priority,"business.sla_target":i.slaTarget})}inferDomainFromUrl(e){return e.includes("/auth/")||e.includes("/login")||e.includes("/register")?this.config.domains.find(e=>"authentication"===e.name)||null:e.includes("/cart")||e.includes("/checkout")||e.includes("/payment")?this.config.domains.find(e=>"ecommerce"===e.name)||null:(e.includes("/search")||e.includes("/products")||e.includes("/content"))&&this.config.domains.find(e=>"content"===e.name)||null}logConfiguration(){console.group("[ObservMetrics] Configuration"),console.log("Domains:",this.config.domains.map(e=>`${e.name} (${e.priority})`)),console.log("Platform:",this.config.platform.platform),console.log("Filtering:",{botDetection:this.config.filtering.enableBotDetection,domainWhitelist:this.config.filtering.domainWhitelist,samplingRate:this.config.filtering.samplingRate}),console.log("User Context:",this.getCurrentUserContext()),console.groupEnd()}destroy(){this.exporters.forEach(e=>{"destroy"in e&&"function"==typeof e.destroy&&e.destroy()}),this.instrumentors.clear(),this.exporters=[],this.events=[],this.isInitialized=!1}}const m={ecommerce:{domains:[{name:"authentication",priority:"critical",slaTarget:2e3,errorThreshold:.1,features:["login","register","profile"]},{name:"ecommerce",priority:"critical",slaTarget:3e3,errorThreshold:.05,features:["cart","checkout","payment"]},{name:"content",priority:"medium",slaTarget:2e3,errorThreshold:1,features:["search","browse","recommendations"]}],filtering:{enableBotDetection:!0,domainWhitelist:["undefined"!=typeof window&&window.location?window.location.hostname:"localhost"],errorThreshold:5,samplingRate:1,excludeExtensions:!0,excludeThirdPartyErrors:!0}}};exports.DatadogExporter=a,exports.DomainInstrumentor=o,exports.Filter=r,exports.NewRelicExporter=u,exports.ObservMetrics=c,exports.createObservMetrics=function(e){return new c(e)},exports.defaultConfigs=m; //# sourceMappingURL=index.js.map