import{captureException,continueTrace,SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,setHttpStatus,startSpan,withIsolationScope,}from'https://deno.land/x/sentry@8.8.0/index.mjs'importtype{Integration,IntegrationFn,SpanAttributes}from'npm:@sentry/types@8.8.0'typePartialURL={host?: stringpath?: stringprotocol?: stringrelative?: stringsearch?: stringhash?: string}/** * Parses string form of URL into an object * // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B * // intentionally using regex and not <a/> href parsing trick because React Native and other * // environments where DOM might not be available * @returns parsed URL object */exportfunctionparseUrl(url: string): PartialURL{if(!url){return{}}constmatch=url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/)if(!match){return{}}// coerce to undefined values to empty string so we don't get 'undefined'constquery=match[6]||''constfragment=match[8]||''return{host: match[4],path: match[5],protocol: match[2],search: query,hash: fragment,relative: match[5]+query+fragment,// everything minus origin}}/** * Takes a URL object and returns a sanitized string which is safe to use as span name * see: https://develop.sentry.dev/sdk/data-handling/#structuring-data */functiongetSanitizedUrlString(url: PartialURL): string{const{ protocol, host, path }=urlconstfilteredHost=(host&&host// Always filter out authority.replace(/^.*@/,'[filtered]:[filtered]@')// Don't show standard :80 (http) and :443 (https) ports to reduce the noise// TODO: Use new URL global if it exists.replace(/(:80)$/,'').replace(/(:443)$/,''))||''return`${protocol ? `${protocol}://` : ''}${filteredHost}${path}`}functiondefineIntegration<FnextendsIntegrationFn>(fn: Fn): (...args: Parameters<Fn>)=>Integration{returnfn}typeRawHandler=(request: Request,info: Deno.ServeHandlerInfo)=>Response|Promise<Response>constINTEGRATION_NAME='DenoServer'const_denoServerIntegration=(()=>{return{name: INTEGRATION_NAME,setupOnce(){instrumentDenoServe()},}})satisfiesIntegrationFn/** * Instruments `Deno.serve` to automatically create transactions and capture errors. * * ```js * Sentry.init({ * integrations: [ * Sentry.denoServerIntegration(), * ], * }) * ``` */exportconstdenoServerIntegration=defineIntegration(_denoServerIntegration)/** * Instruments Deno.serve by patching it's options. */exportfunctioninstrumentDenoServe(): void{Deno.serve=newProxy(Deno.serve,{apply(serveTarget,serveThisArg,serveArgs: any){const[arg1,arg2]=serveArgslethandler: RawHandler|undefinedlettype=0if(typeofarg1==='function'){handler=arg1type=1}elseif(typeofarg2==='function'){handler=arg2type=2}elseif(arg1&&typeofarg1==='object'&&'handler'inarg1&&typeofarg1.handler==='function'){handler=arg1.handlertype=3}elseif(arg2&&typeofarg2==='object'&&'handler'inarg2&&typeofarg2.handler==='function'){handler=arg2.handlertype=4}if(handler){handler=instrumentDenoServeOptions(handler)if(type===1){serveArgs[0]=handler}elseif(type===2){serveArgs[1]=handler}elseif(type===3){serveArgs[0].handler=handler}elseif(type===4){serveArgs[1].handler=handler}}returnserveTarget.apply(serveThisArg,serveArgs)},})}/** * Instruments Deno.serve `fetch` option to automatically create spans and capture errors. */functioninstrumentDenoServeOptions(handler: RawHandler): RawHandler{returnnewProxy(handler,{apply(fetchTarget,fetchThisArg,fetchArgs: Parameters<typeofhandler>){returnwithIsolationScope((isolationScope)=>{constrequest=fetchArgs[0]constupperCaseMethod=request.method.toUpperCase()if(upperCaseMethod==='OPTIONS'||upperCaseMethod==='HEAD'){returnfetchTarget.apply(fetchThisArg,fetchArgs)}constparsedUrl=parseUrl(request.url)constattributes: SpanAttributes={[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.deno.serve','http.request.method': request.method||'GET',[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',}if(parsedUrl.search){attributes['http.query']=parsedUrl.search}consturl=getSanitizedUrlString(parsedUrl)isolationScope.setSDKProcessingMetadata({request: {
url,method: request.method,headers: Object.fromEntries(request.headers),},})returncontinueTrace({sentryTrace: request.headers.get('sentry-trace')||'',baggage: request.headers.get('baggage'),},()=>{returnstartSpan({
attributes,op: 'http.server',name: `${request.method}${parsedUrl.path||'/'}`,},async(span)=>{try{constresponse=await(fetchTarget.apply(fetchThisArg,fetchArgs)asReturnType<typeofhandler>)if(response&&response.status){setHttpStatus(span,response.status)isolationScope.setContext('response',{headers: Object.fromEntries(response.headers),status_code: response.status,})}returnresponse}catch(e){captureException(e,{mechanism: {type: 'deno',handled: false,data: {function: 'serve',},},})throwe}},)})})},})}
Problem Statement
similar to https://github.com/getsentry/sentry-javascript/blob/develop/packages/bun/src/integrations/bunserver.ts
Solution Brainstorm
Rough working implementation:
needs
requestDataIntegration