UNPKG

pinpoint-node-agent

Version:

Pinpoint APM agent for Node.js — distributed tracing, error analysis, and URI statistics

143 lines (118 loc) 4.7 kB
/** * Pinpoint Node.js Agent * Copyright 2020-present NAVER Corp. * Apache License v2.0 */ 'use strict' const { nextExceptionId } = require('./id-generator') const { namedGroupCallSite } = require('../../instrumentation/call-stack') const { LongAnnotation } = require('../../instrumentation/context/annotation/long-annotation') const AnnotationKey = require('../annotation-key') const exceptionChainIdAnnotationKey = new AnnotationKey(-52, 'ExceptionChainId') const spanMessages = require('../../data/v1/Span_pb') class Frame { constructor(className = '', fileName = 'unknown', lineNumber = 0, methodName = '') { this.className = className this.fileName = fileName this.lineNumber = lineNumber this.methodName = methodName } toProtocolBuffer() { const pFrame = new spanMessages.PStackTraceElement() pFrame.setClassname(this.className) pFrame.setFilename(this.fileName) pFrame.setLinenumber(this.lineNumber) pFrame.setMethodname(this.methodName) return pFrame } } class Exception { constructor(errorClassName, errorMessage, startTime, exceptionId, exceptionDepth) { this.errorClassName = errorClassName this.errorMessage = errorMessage this.startTime = startTime this.exceptionId = exceptionId ?? nextExceptionId() this.exceptionDepth = exceptionDepth ?? 0 this.frameStack = [] } exceptionIdAnnotation() { return new LongAnnotation(exceptionChainIdAnnotationKey.getCode(), this.exceptionId) } toProtocolBuffer() { const pException = new spanMessages.PException() pException.setExceptionclassname(this.errorClassName) pException.setExceptionmessage(this.errorMessage) pException.setStarttime(this.startTime) pException.setExceptionid(this.exceptionId) pException.setExceptiondepth(this.exceptionDepth) this.frameStack.forEach(frame => { pException.addStacktraceelement(frame.toProtocolBuffer()) }) return pException } } const nullErrorClassName = 'unknown' const nullErrorMessage = '' const defaultMaxDepth = 10 class ExceptionBuilder { constructor(error, maxDepth) { this.error = error this.maxDepth = maxDepth ?? defaultMaxDepth } build() { return this.buildChain() } buildChain() { const exceptions = [] const exceptionId = nextExceptionId() let current = this.error let depth = 0 while (current && depth < this.maxDepth) { const exception = this.buildSingleException(current, exceptionId, depth) exceptions.push(exception) current = current.cause depth++ } if (exceptions.length === 0) { exceptions.push(new Exception(nullErrorClassName, nullErrorMessage, Date.now(), exceptionId, 0)) } return exceptions } buildSingleException(error, exceptionId, depth) { if (!error || typeof error.stack !== 'string') { return new Exception(nullErrorClassName, nullErrorMessage, Date.now(), exceptionId, depth) } const stack = error.stack.split(/\r?\n/) if (stack.length < 1) { return new Exception(nullErrorClassName, nullErrorMessage, Date.now(), exceptionId, depth) } const className = this.extractErrorClassName(stack[0]) const message = error.message ?? nullErrorMessage const exception = new Exception(className, message, Date.now(), exceptionId, depth) exception.frameStack = stack.slice(1).map(callSite => { const { type, location, fileName, lineNumber, functionName, methodName } = namedGroupCallSite(callSite.trim()) let functionNameWithMethod if (functionName && methodName) { functionNameWithMethod = `${functionName} [as ${methodName}]` } else if (functionName) { functionNameWithMethod = functionName } else if (methodName) { functionNameWithMethod = methodName } return new Frame(type || fileName || nullErrorClassName, location, lineNumber, functionNameWithMethod || '<anonymous>') }) exception.stack = stack return exception } extractErrorClassName(firstLine) { if (!firstLine || typeof firstLine !== 'string') { return nullErrorClassName } const match = /^([^:\s]+):/.exec(firstLine) return match && match[1] ? match[1].trim() : nullErrorClassName } } module.exports = { Exception, ExceptionBuilder }