Repository URL to install this package:
|
Version:
0.8.2 ▾
|
// (c) Copyright 2023 Supertenant Ltd. - all rights reserved.
// See LICENSE file in project root for license terms.
"use strict";const atMostOnce=require("@supertenant/core").util["atMostOnce"],fs=require("fs"),http=require("@supertenant/core").uninstrumentedHttp["http"],pathUtil=require("path"),propertySizes=require("@supertenant/core").util["propertySizes"];let logger;logger=require("./logger").getLogger("agentConnection",newLogger=>{logger=newLogger});const circularReferenceRemover=require("./util/removeCircular"),agentOpts=require("./agent/opts"),pidStore=require("./pidStore"),cmdline=require("./cmdline"),cpuSetFileContent=getCpuSetFileContent(),paddingForInodeAndFileDescriptor=200,maxContentLength=5242880;let maxContentErrorHasBeenLogged=!1,isConnected=!1;function checkWhetherResponseForPathIsOkay(path,cb){cb=atMostOnce("callback for checkWhetherResponseForPathIsOkay",cb);const req=http.request({host:agentOpts.host,port:agentOpts.port,path:path,agent:http.agent,method:"HEAD"},res=>{isConnected=199<res.statusCode&&res.statusCode<300,cb(isConnected),res.resume()});req.setTimeout(agentOpts.requestTimeout,function(){isConnected=!1,cb(isConnected),req.abort()}),req.on("error",()=>{isConnected=!1,cb(isConnected)}),req.end()}function sendData(path,data,cb,ignore404=!1){cb=atMostOnce("callback for sendData: "+path,cb);var error,data=JSON.stringify(data,circularReferenceRemover()),data=("function"==typeof logger.trace?logger.trace("Sending data to %s",path):logger.debug("Sending data to %s",path),Buffer.from(data,"utf8"));if(data.length>maxContentLength)return error=new PayloadTooLargeError(`Request payload is too large. Will not send data to agent. (POST ${path})`),setImmediate(cb.bind(null,error));const req=http.request({host:agentOpts.host,port:agentOpts.port,path:path,method:"POST",agent:http.agent,headers:{"Content-Type":"application/json; charset=UTF-8","Content-Length":data.length}},res=>{var statusCodeError;if((res.statusCode<200||300<=res.statusCode)&&(404!==res.statusCode||!ignore404))return(statusCodeError=new Error(`Failed to send data to agent via POST ${path}. Got status code ${res.statusCode}.`)).statusCode=res.statusCode,void cb(statusCodeError);res.setEncoding("utf8");let responseBody="";res.on("data",chunk=>{responseBody+=chunk}),res.on("end",()=>{cb(null,responseBody)})});req.setTimeout(agentOpts.requestTimeout,function(){cb(new Error(`Failed to send data to agent via POST ${path}. Ran into a timeout.`)),req.abort()}),req.on("error",err=>{cb(new Error(`Send data to agent via POST ${path}. Request failed: `+err.message))}),req.write(data),req.end()}function getCpuSetFileContent(){try{var cpuSetPath=`/proc/${process.pid}/cpuset`,content=fs.readFileSync(cpuSetPath,{encoding:"utf-8"});return content&&2e3<=content.length?null:content}catch(err){return"ENOENT"!==err.code&&logger.warn("cpuset file could not be read. Reason: %s",err.message),null}}function PayloadTooLargeError(message){Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=message}function logLargeSpans(spans){maxContentErrorHasBeenLogged=!0;spans=spans.map(span=>({span:span,length:JSON.stringify(span).length})).sort((s1,s2)=>s2.length-s1.length).slice(0,4).map(s=>`span name: ${s.span.n}, largest attribute: `+propertySizes(s.span).sort((p1,p2)=>p2.length-p1.length).slice(0,1).map(p=>`${p.property} (${p.length} bytes)`));logger.warn("A batch of spans have been rejected because they are too large to be sent to the agent. Here are the top five largest spans of the rejected batch and their largest attribute. This detailed information will only be logged once. "+spans.join("; "))}exports.AgentEventSeverity={SLI_EVENT:-4,INFO:-2,CHANGE:-1,WARNING:5,CRITICAL:10},exports.announceNodeCollector=function(cb){cb=atMostOnce("callback for announceNodeCollector",cb);const payload={pid:pidStore.pid,pidFromParentNS:pidStore.pid!=process.pid,spacer:""};var processCmdline=cmdline.getCmdline();processCmdline.name&&processCmdline.args&&(payload.name=processCmdline.name,payload.args=processCmdline.args),cpuSetFileContent&&(payload.cpuSetFileContent=cpuSetFileContent);let payloadStr=JSON.stringify(payload);const contentLength=Buffer.from(payloadStr,"utf8").length+paddingForInodeAndFileDescriptor,req=http.request({host:agentOpts.host,port:agentOpts.port,path:"/com.instana.plugin.nodejs.discovery",method:"PUT",agent:http.agent,headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8","Content-Length":contentLength}},res=>{if(res.statusCode<200||300<=res.statusCode)cb(new Error("Announce to agent failed with status code "+res.statusCode));else{res.setEncoding("utf8");let responseBody="";res.on("data",chunk=>{responseBody+=chunk}),res.on("end",()=>{cb(null,responseBody)})}});req.setTimeout(agentOpts.requestTimeout,function(){cb(new Error("Announce request to agent failed due to timeout")),req.abort()}),req.on("error",err=>{cb(new Error("Announce request to agent failed due to: "+err.message))}),req.on("socket",socket=>{if(null!=socket._handle&&null!=socket._handle.fd){payload.fd=String(socket._handle.fd);try{var linkPathPrefix=pathUtil.join("/proc",String(process.pid),"fd")+"/",linkPath=pathUtil.join(linkPathPrefix,payload.fd);payload.inode=fs.readlinkSync(linkPath),"string"==typeof payload.inode&&0===payload.inode.indexOf(linkPathPrefix)&&(payload.inode=payload.inode.substring(linkPathPrefix.length))}catch(e){logger.debug("Failed to retrieve inode for file descriptor %s: %s",payload.fd,e.message)}}payloadStr=JSON.stringify(payload);socket=Buffer.from(payloadStr,"utf8").length;if(socket<contentLength){var missingChars=contentLength-socket;for(let i=0;i<missingChars;i++)payload.spacer+=" "}req.write(Buffer.from(JSON.stringify(payload),"utf8")),req.end()})},exports.checkWhetherAgentIsReadyToAcceptData=function(cb){checkWhetherResponseForPathIsOkay("/com.instana.plugin.nodejs."+pidStore.pid,cb)},exports.sendMetrics=function(data,cb){cb=atMostOnce("callback for sendMetrics",cb),sendData("/com.instana.plugin.nodejs."+pidStore.pid,data,(err,body)=>{if(err)cb(err,null);else{try{body=JSON.parse(body)}catch(e){body=[]}cb(null,body)}})},exports.sendSpans=function(spans,cb){var callback=atMostOnce("callback for sendSpans",err=>{err&&!maxContentErrorHasBeenLogged&&err instanceof PayloadTooLargeError&&logLargeSpans(spans),cb(err)});sendData("/com.instana.plugin.nodejs/traces."+pidStore.pid,spans,callback,!0)},exports.sendProfiles=function(profiles,cb){var callback=atMostOnce("callback for sendProfiles",err=>{err&&err instanceof PayloadTooLargeError?logger.warn("Profiles are too too large to be sent."):err&&404===err.statusCode&&logger.warn("You have enabled autoProfiling but the Instana agent this process reports to does not yet support autoProfiling for Node.js. Please update the Instana agent. (Node.js Sensor 1.2.14 or newer is required.)"),cb(err)});sendData("/com.instana.plugin.nodejs/profiles."+pidStore.pid,profiles,callback)},exports.sendEvent=function(eventData,cb){sendData("/com.instana.plugin.generic.event",eventData,atMostOnce("callback for sendEvent",(err,responseBody)=>{cb(err,responseBody)}))},exports.sendAgentMonitoringEvent=function(code,category,cb){sendData("/com.instana.plugin.generic.agent-monitoring-event",{plugin:"com.instana.forge.infrastructure.runtime.nodejs.NodeJsRuntimePlatform",pid:pidStore.pid,code:code,duration:66e4,category:category},atMostOnce("callback for sendAgentMonitoringEvent",(err,responseBody)=>{cb(err,responseBody)}))},exports.sendAgentResponseToAgent=function(messageId,response,cb){cb=atMostOnce("callback for sendAgentResponseToAgent",cb),sendData(`/com.instana.plugin.nodejs/response.${pidStore.pid}?messageId=`+encodeURIComponent(messageId),response,cb)},exports.sendTracingMetricsToAgent=function(tracingMetrics,cb){sendData("/tracermetrics",tracingMetrics,atMostOnce("callback for sendTracingMetricsToAgent",err=>{cb(err)}))},exports.isConnected=function(){return isConnected};