diff --git a/src/Constants.ts b/src/Constants.ts index 9e8ff708..a69678b4 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -77,6 +77,7 @@ export function getDefaultCollectorEndpoint() { export const MONITORING_DATA_PATH = '/monitoring-data'; export const COMPOSITE_MONITORING_DATA_PATH = '/composite-monitoring-data'; export const LOCAL_COLLECTOR_ENDPOINT = 'localhost:3333'; +export const MAX_MONITOR_DATA_BATCH_SIZE = 100; export const PROC_STAT_PATH: string = '/proc/self/stat'; export const PROC_IO_PATH: string = '/proc/self/io'; @@ -572,6 +573,13 @@ export const GoogleBigQueryTags = { RESPONSE: 'google.bigquery.response', }; +export const CatchpointTags = { + REGION_NAME: 'catchpoint.region.name', + COUNTRY_NAME: 'catchpoint.country.name', + CITY_NAME: 'catchpoint.city.name', + TEST_ID: 'catchpoint.test.id', +}; + export const ErrorTags = { ERROR: 'error', ERROR_KIND: 'error.kind', @@ -590,6 +598,33 @@ export const TriggerHeaderTags = { RESOURCE_NAME: 'x-catchpoint-resource-name', }; +export const HTTPHeaders = { + USER_AGENT: 'User-Agent', + CONTENT_TYPE: 'Content-Type', + AUTHORIZATION: 'Authorization', +}; + +export const CatchpointHeaders = { + REGION_NAME: 'x-catchpoint-region-name', + COUNTRY_NAME: 'x-catchpoint-country-name', + CITY_NAME: 'x-catchpoint-city-name', + TEST_ID: 'x-catchpoint-test-id', + TIME: 'x-catchpoint-time', +}; + +export const CatchpointProperties = { + DEFAULT_APP_NAME: 'Catchpoint', + APP_DOMAIN_NAME: 'Monitoring', + APP_CLASS_NAME: 'Catchpoint', + APP_REGION_PLACEHOLDER: '${cp-app-region}', + APP_NAME_PLACEHOLDER: '${cp-app-name}', + APP_ID_TEMPLATE: ':catchpoint:${cp-app-region}:${cp-app-name}', + HTTP_REQUEST_DOMAIN_NAME: 'API', + HTTP_REQUEST_CLASS_NAME: 'HTTP', + TIME_MAX_DIFF: 30000, + AGENT_VERSION: 'Catchpoint', +}; + export const AlreadyTracedHeader = 'x-catchpoint-already-traced'; export const SpanTypes = { diff --git a/src/Reporter.ts b/src/Reporter.ts index ad2f4d35..8fa7a6dc 100644 --- a/src/Reporter.ts +++ b/src/Reporter.ts @@ -3,23 +3,21 @@ import * as http from 'http'; import * as https from 'https'; import * as url from 'url'; import { - COMPOSITE_MONITORING_DATA_PATH, LOCAL_COLLECTOR_ENDPOINT, SPAN_TAGS_TO_TRIM_1, SPAN_TAGS_TO_TRIM_2, SPAN_TAGS_TO_TRIM_3, REPORTER_HTTP_TIMEOUT, REPORTER_DATA_SIZE_LIMIT, + MONITORING_DATA_PATH, + MAX_MONITOR_DATA_BATCH_SIZE, getDefaultCollectorEndpoint, } from './Constants'; import Utils from './utils/Utils'; import ThundraLogger from './ThundraLogger'; -import BaseMonitoringData from './plugins/data/base/BaseMonitoringData'; import MonitoringDataType from './plugins/data/base/MonitoringDataType'; import ConfigNames from './config/ConfigNames'; import ConfigProvider from './config/ConfigProvider'; -import CompositeMonitoringData from './plugins/data/composite/CompositeMonitoringData'; -import MonitorDataType from './plugins/data/base/MonitoringDataType'; const httpAgent = new http.Agent({ keepAlive: true, @@ -117,16 +115,60 @@ class Reporter { */ sendReports(reports: any[], disableTrim: boolean = false): Promise { ThundraLogger.debug(' Sending reports ...'); - let compositeReport: any; - try { - compositeReport = this.getCompositeReport(reports); - } catch (err) { - ThundraLogger.error(' Cannot create batch request will send no report:', err); + + // Split reports into batches + const batchedReports: any[] = this.getBatchedReports(reports); + + const reportPromises: Array> = []; + // Report each batch asynchronously in parallel + batchedReports.forEach((batch: any) => { + reportPromises.push(this.doSendReports(batch, disableTrim)); + }); + + return new Promise((resolve, reject) => { + try { + const allPromises: Promise = Promise.all(reportPromises); + allPromises + .then(() => { + ThundraLogger.debug(' Sent all reports successfully'); + resolve(); + }) + .catch((err) => { + ThundraLogger.debug(' Failed to send all reports:', err); + reject(err); + }); + } catch (err) { + ThundraLogger.debug(' Unable to send all reports:', err); + reject(err); + } + }); + } + + private getBatchedReports(reports: any[]): any[] { + const batchedReports: any[] = []; + const batchCount: number = Math.ceil(reports.length / MAX_MONITOR_DATA_BATCH_SIZE); + + for (let i = 0; i < batchCount; i++) { + const batch: any[] = []; + for (let j = 0; j < MAX_MONITOR_DATA_BATCH_SIZE; j++) { + const report = reports[i * MAX_MONITOR_DATA_BATCH_SIZE + j]; + if (!report) { + continue; + } + batch.push(report); + } + batchedReports.push(batch); } + return batchedReports; + } + + private doSendReports(monitoringDataList: any[], disableTrim: boolean = false): Promise { + ThundraLogger.debug(' Sending batch ...'); + return new Promise((resolve, reject) => { try { - const reportJson: string = this.serializeReport(compositeReport, disableTrim); + const reportJson: string = this.serializeReport(monitoringDataList, disableTrim); this.doReport(reportJson) .then((res: any) => { ThundraLogger.debug(' Sent reports successfully'); @@ -157,7 +199,7 @@ class Reporter { } private createRequestOptions(u?: url.URL): http.RequestOptions { - const path = COMPOSITE_MONITORING_DATA_PATH; + const path = MONITORING_DATA_PATH; return { timeout: REPORTER_HTTP_TIMEOUT, @@ -191,6 +233,7 @@ class Reporter { }; } + /* private getCompositeReport(reports: any[]): any { ThundraLogger.debug(' Generating composite report ...'); @@ -230,6 +273,7 @@ class Reporter { return monitoringData; } + */ private doReport(reportJson: string) { ThundraLogger.debug(' Reporting ...'); @@ -332,44 +376,44 @@ class Reporter { }); } - private serializeReport(batch: any, disableTrim: boolean): string { + private serializeReport(monitoringDataList: any[], disableTrim: boolean): string { // If trimming is disabled, trim if and only if data size is bigger than maximum allowed limit const maxReportDataSize: number = disableTrim ? REPORTER_DATA_SIZE_LIMIT : this.maxReportSize; - let json: string = this.serializeMasked(batch, this.maskedKeys, this.hide); + let json: string = this.serializeMasked(monitoringDataList, this.maskedKeys, this.hide); if (json.length < maxReportDataSize) { return json; } for (const trimmer of this.trimmers) { - const trimResult: TrimResult = trimmer.trim(batch.data.allMonitoringData); - batch.data.allMonitoringData = trimResult.monitoringDataList; + const trimResult: TrimResult = trimmer.trim(monitoringDataList); + monitoringDataList = trimResult.monitoringDataList; if (!trimResult.mutated) { continue; } - json = this.serializeMasked(batch, this.maskedKeys, this.hide); + json = this.serializeMasked(monitoringDataList, this.maskedKeys, this.hide); if (json.length < maxReportDataSize) { return json; } } - return this.serializeMasked(batch, this.maskedKeys, this.hide); + return this.serializeMasked(monitoringDataList, this.maskedKeys, this.hide); } - private serializeMasked(batch: any, maskedKeys: MaskedKey[], hide?: boolean): string { + private serializeMasked(monitoringDataList: any[], maskedKeys: MaskedKey[], hide?: boolean): string { if (maskedKeys && maskedKeys.length) { try { ThundraLogger.debug(` Serializing masked ...`); const maskCheckSet: WeakSet = new WeakSet(); - for (const monitoringData of batch.data.allMonitoringData) { + for (const monitoringData of monitoringDataList) { if (monitoringData.tags) { maskCheckSet.add(monitoringData.tags); } } const result: string = - JSON.stringify(batch, this.createMaskingReplacer(maskCheckSet, maskedKeys, hide)); + JSON.stringify(monitoringDataList, this.createMaskingReplacer(maskCheckSet, maskedKeys, hide)); ThundraLogger.debug(` Serialized masked`); @@ -378,7 +422,7 @@ class Reporter { ThundraLogger.debug(` Error occurred while serializing masked`, err); } } - return Utils.serializeJSON(batch); + return Utils.serializeJSON(monitoringDataList); } private isMasked(key: string, maskedKeys: MaskedKey[]): boolean { diff --git a/src/application/ApplicationInfo.ts b/src/application/ApplicationInfo.ts index 012199ee..f2d582d8 100644 --- a/src/application/ApplicationInfo.ts +++ b/src/application/ApplicationInfo.ts @@ -9,6 +9,8 @@ export interface ApplicationInfo { applicationDomainName: string; applicationRegion: string; applicationVersion: string; + applicationRuntime: string; + applicationRuntimeVersion: string; applicationStage: string; applicationTags: any; } diff --git a/src/application/GlobalApplicationInfoProvider.ts b/src/application/GlobalApplicationInfoProvider.ts index 14444342..c2def333 100644 --- a/src/application/GlobalApplicationInfoProvider.ts +++ b/src/application/GlobalApplicationInfoProvider.ts @@ -38,6 +38,8 @@ export class GlobalApplicationInfoProvider implements ApplicationInfoProvider { applicationDomainName: ConfigProvider.get(ConfigNames.THUNDRA_APPLICATION_DOMAIN_NAME), applicationRegion: ConfigProvider.get(ConfigNames.THUNDRA_APPLICATION_REGION), applicationVersion: ConfigProvider.get(ConfigNames.THUNDRA_APPLICATION_VERSION), + applicationRuntime: 'node', + applicationRuntimeVersion: process.version, applicationStage: ConfigProvider.get(ConfigNames.THUNDRA_APPLICATION_STAGE), applicationTags: Utils.getApplicationTags(), }; diff --git a/src/plugins/data/invocation/Resource.ts b/src/plugins/data/invocation/Resource.ts index ca2aa332..1525169a 100644 --- a/src/plugins/data/invocation/Resource.ts +++ b/src/plugins/data/invocation/Resource.ts @@ -21,7 +21,7 @@ class Resource { constructor(opt: any = {}) { this.resourceType = opt.resourceType; - this.resourceName = opt.resourceOperation; + this.resourceName = opt.resourceName; this.resourceOperation = opt.resourceOperation; this.resourceCount = opt.resourceCount; this.resourceErrorCount = opt.resourceErrorCount; diff --git a/src/utils/TestRunnerUtils.ts b/src/utils/TestRunnerUtils.ts index 005b3ac3..972c7544 100644 --- a/src/utils/TestRunnerUtils.ts +++ b/src/utils/TestRunnerUtils.ts @@ -26,7 +26,7 @@ class TestRunnerUtils { const testRunIdSeed = environment + '_' + repoURL + '_' + commitHash + '_' + testRunKey; - return Utils.generareIdFrom(testRunIdSeed); + return Utils.generateIdFrom(testRunIdSeed); } /** diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index cc30b677..322d3f3e 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -55,7 +55,7 @@ class Utils { * Generates id in UUID format with given value * @param value value */ - static generareIdFrom(value: string): string { + static generateIdFrom(value: string): string { return uuidv5(value, AGENT_UUID_CONST); } @@ -384,24 +384,33 @@ class Utils { * Injects common application properties into given {@link BaseMonitoringData} monitoring data * @param {BaseMonitoringData} monitoringData the @link BaseMonitoringData} monitoring data * in which common application properties will be injected to + * @param {ApplicationInfo} applicationInfo the the {@link ApplicationInfo} to be injected into */ - static injectCommonApplicationProperties(monitoringData: BaseMonitoringData) { - const applicationInfo = ApplicationManager.getApplicationInfo(); + static injectCommonApplicationProperties(monitoringData: BaseMonitoringData, + applicationInfo?: ApplicationInfo) { + const appInfo = applicationInfo || ApplicationManager.getApplicationInfo(); monitoringData.agentVersion = AGENT_VERSION; monitoringData.dataModelVersion = DATA_MODEL_VERSION; + monitoringData.applicationRuntime = 'node'; monitoringData.applicationRuntimeVersion = process.version; - if (applicationInfo) { - monitoringData.applicationInstanceId = applicationInfo.applicationInstanceId; - monitoringData.applicationId = applicationInfo.applicationId; - monitoringData.applicationName = applicationInfo.applicationName; - monitoringData.applicationClassName = applicationInfo.applicationClassName; - monitoringData.applicationDomainName = applicationInfo.applicationDomainName; - monitoringData.applicationStage = applicationInfo.applicationStage; - monitoringData.applicationVersion = applicationInfo.applicationVersion; + if (appInfo) { + if (appInfo.applicationRuntime) { + monitoringData.applicationRuntime = appInfo.applicationRuntime; + } + if (appInfo.applicationRuntimeVersion) { + monitoringData.applicationRuntimeVersion = appInfo.applicationRuntimeVersion; + } + monitoringData.applicationInstanceId = appInfo.applicationInstanceId; + monitoringData.applicationId = appInfo.applicationId; + monitoringData.applicationName = appInfo.applicationName; + monitoringData.applicationClassName = appInfo.applicationClassName; + monitoringData.applicationDomainName = appInfo.applicationDomainName; + monitoringData.applicationStage = appInfo.applicationStage; + monitoringData.applicationVersion = appInfo.applicationVersion; monitoringData.applicationTags = { ...monitoringData.applicationTags, - ...applicationInfo.applicationTags, + ...appInfo.applicationTags, }; } } @@ -570,6 +579,8 @@ class Utils { ...(applicationRegion ? { applicationRegion } : undefined), ...(applicationVersion ? { applicationVersion } : undefined), ...(applicationStage ? { applicationStage } : undefined), + applicationRuntime: 'node', + applicationRuntimeVersion: process.version, applicationTags: Utils.getApplicationTags(), }; } diff --git a/src/wrappers/WebWrapperUtils.ts b/src/wrappers/WebWrapperUtils.ts index 7f8c0a6c..dce6c8f0 100644 --- a/src/wrappers/WebWrapperUtils.ts +++ b/src/wrappers/WebWrapperUtils.ts @@ -17,11 +17,24 @@ import MonitoringDataType from '../plugins/data/base/MonitoringDataType'; import InvocationData from '../plugins/data/invocation/InvocationData'; import InvocationSupport from '../plugins/support/InvocationSupport'; import InvocationTraceSupport from '../plugins/support/InvocationTraceSupport'; -import { HttpTags, SpanTags, TriggerHeaderTags, DEFAULT_APPLICATION_NAME } from '../Constants'; +import { + DEFAULT_APPLICATION_NAME, + HttpTags, + SpanTags, + TriggerHeaderTags, + HTTPHeaders, + TraceHeaderTags, + CatchpointProperties, + CatchpointHeaders, + CatchpointTags, +} from '../Constants'; import ThundraSpanContext from '../opentracing/SpanContext'; import { ApplicationInfo } from '../application/ApplicationInfo'; import HttpError from '../error/HttpError'; import WrapperContext from './WrapperContext'; +import ThundraSpan from '../opentracing/Span'; +import Resource from '../plugins/data/invocation/Resource'; +import SpanData from '../plugins/data/trace/SpanData'; const get = require('lodash.get'); @@ -81,7 +94,8 @@ export default class WebWrapperUtils { context.reports = []; try { // Run plugins and let them to generate reports - for (const plugin of plugins) { + for (let i = plugins.length - 1; i >= 0; i--) { + const plugin = plugins[i]; await plugin.afterInvocation(context); } reports = context.reports; @@ -112,13 +126,13 @@ export default class WebWrapperUtils { plugins.push(tracePlugin); } + const invocationPlugin = new InvocationPlugin(config.invocationConfig); + plugins.push(invocationPlugin); + if (!ConfigProvider.get(ConfigNames.THUNDRA_LOG_DISABLE) && config.logConfig.enabled) { plugins.push(new LogPlugin(config.logConfig)); } - const invocationPlugin = new InvocationPlugin(config.invocationConfig); - plugins.push(invocationPlugin); - // Set plugin context for plugins plugins.forEach((plugin: any) => { plugin.setPluginContext(pluginContext); }); @@ -281,6 +295,10 @@ export default class WebWrapperUtils { // If root span is already finished, it won't have any effect rootSpan.finish(finishTimestamp); + + WebWrapperUtils.onFinish( + pluginContext, execContext, + execContext.request, execContext.response, rootSpan); } static mergePathAndRoute(path: string, route: string) { @@ -305,4 +323,202 @@ export default class WebWrapperUtils { return normalizedPath; } + + private static onFinish(pluginContext: PluginContext, execContext: ExecutionContext, + request: any, response: any, span: ThundraSpan): void { + if (WebWrapperUtils.isTriggeredFromCatchpoint(request, response)) { + WebWrapperUtils.onCatchpointRequestFinish(pluginContext, execContext, request, response, span); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private static isTriggeredFromCatchpoint(request: any, response: any): boolean { + const userAgent: string = request.headers && request.headers[HTTPHeaders.USER_AGENT.toLowerCase()]; + return userAgent && userAgent.includes('Catchpoint'); + } + + private static getCatchpointApplicationInfo(request: any, appName: string, appRegion: string): ApplicationInfo { + const appId: string = CatchpointProperties.APP_ID_TEMPLATE. + replace(CatchpointProperties.APP_NAME_PLACEHOLDER, appName). + replace(CatchpointProperties.APP_REGION_PLACEHOLDER, appRegion); + return { + applicationId: appId, + applicationInstanceId: Utils.generateIdFrom(appId), + applicationName: appName, + applicationClassName: CatchpointProperties.APP_CLASS_NAME, + applicationDomainName: CatchpointProperties.APP_DOMAIN_NAME, + applicationRegion: appRegion, + applicationVersion: '', + applicationRuntime: undefined, + applicationRuntimeVersion: undefined, + applicationStage: '', + applicationTags: {}, + }; + } + + private static getCatchpointRequestResource(execContext: ExecutionContext, + request: any, span: ThundraSpan, duration: number, error: any): Resource { + return new Resource({ + resourceType: CatchpointProperties.HTTP_REQUEST_CLASS_NAME, + resourceName: execContext.triggerOperationName || span.operationName, + resourceOperation: request.method, + resourceCount: 1, + resourceErrorCount: error ? 1 : 0, + resourceErrors: error ? [error.errorType] : undefined, + resourceDuration: duration, + resourceMaxDuration: duration, + resourceAvgDuration: duration, + }); + } + + private static generateCatchpointAppName(regionName: string, countryName: string, cityName: string): string { + return cityName || countryName || regionName || DEFAULT_APPLICATION_NAME; + } + + private static createCatchpointRequestInvocation(execContext: ExecutionContext, applicationInfo: ApplicationInfo, + regionName: string, countryName: string, cityName: string, testId: string, + traceId: string, transactionId: string, spanId: string, + startTimestamp: number, finishTimestamp: number, + resource: Resource, error: any): InvocationData { + const invocationData: InvocationData = new InvocationData(); + + Utils.injectCommonApplicationProperties(invocationData, applicationInfo); + + invocationData.id = Utils.generateId(); + invocationData.agentVersion = CatchpointProperties.AGENT_VERSION; + invocationData.traceId = traceId; + invocationData.transactionId = transactionId; + invocationData.spanId = spanId; + invocationData.applicationRegion = regionName || ''; + invocationData.tags = { + [CatchpointTags.REGION_NAME]: regionName, + [CatchpointTags.COUNTRY_NAME]: countryName, + [CatchpointTags.CITY_NAME]: cityName, + [CatchpointTags.TEST_ID]: testId, + }; + invocationData.resources = [ + resource, + ]; + invocationData.userTags = {}; + invocationData.startTimestamp = startTimestamp; + invocationData.finishTimestamp = finishTimestamp; + invocationData.duration = finishTimestamp - startTimestamp; + invocationData.applicationPlatform = ''; + invocationData.erroneous = error ? true : false; + invocationData.errorType = error ? (error.errorType || '') : ''; + invocationData.errorMessage = error ? (error.errorMessage || '') : ''; + invocationData.coldStart = false; + invocationData.timeout = false; + invocationData.incomingTraceLinks = []; + invocationData.outgoingTraceLinks = []; + + return invocationData; + } + + private static createCatchpointRequestSpan(execContext: ExecutionContext, + applicationInfo: ApplicationInfo, rootSpan: ThundraSpan, resource: Resource, + regionName: string, countryName: string, cityName: string, testId: string, + traceId: string, transactionId: string, spanId: string, + startTimestamp: number, finishTimestamp: number): SpanData { + const spanData: SpanData = new SpanData(); + + Utils.injectCommonApplicationProperties(spanData, applicationInfo); + + spanData.id = spanId; + spanData.domainName = CatchpointProperties.HTTP_REQUEST_DOMAIN_NAME; + spanData.className = CatchpointProperties.HTTP_REQUEST_CLASS_NAME; + spanData.serviceName = applicationInfo.applicationName; + spanData.transactionId = transactionId; + spanData.traceId = traceId; + spanData.spanOrder = 0; + spanData.operationName = resource.resourceName; + spanData.startTimestamp = startTimestamp; + spanData.finishTimestamp = finishTimestamp; + spanData.duration = finishTimestamp - startTimestamp; + spanData.tags = { + [HttpTags.HTTP_URL]: rootSpan.tags[HttpTags.HTTP_URL], + [HttpTags.HTTP_HOST]: rootSpan.tags[HttpTags.HTTP_HOST], + [HttpTags.HTTP_PATH]: rootSpan.tags[HttpTags.HTTP_PATH], + [HttpTags.HTTP_METHOD]: rootSpan.tags[HttpTags.HTTP_METHOD], + [HttpTags.QUERY_PARAMS]: rootSpan.tags[HttpTags.QUERY_PARAMS], + [HttpTags.HTTP_STATUS]: rootSpan.tags[HttpTags.HTTP_STATUS], + [CatchpointTags.REGION_NAME]: regionName, + [CatchpointTags.COUNTRY_NAME]: countryName, + [CatchpointTags.CITY_NAME]: cityName, + [CatchpointTags.TEST_ID]: testId, + }; + + return spanData; + } + + private static onCatchpointRequestFinish(pluginContext: PluginContext, execContext: ExecutionContext, + request: any, response: any, span: ThundraSpan): void { + const spanContext: ThundraSpanContext = span.context() as ThundraSpanContext; + + const headers: any = request.headers || {}; + + const regionName: string = headers[CatchpointHeaders.REGION_NAME]; + const countryName: string = headers[CatchpointHeaders.COUNTRY_NAME]; + const cityName: string = headers[CatchpointHeaders.CITY_NAME]; + const testId: string = headers[CatchpointHeaders.TEST_ID]; + const time: string = headers[CatchpointHeaders.TIME]; + + const appName: string = WebWrapperUtils.generateCatchpointAppName(regionName, countryName, cityName); + const appRegion = regionName || ''; + const startTime: number = span.startTime; + // TODO Handle time header to calculate start time more accurately + /* + if (time) { + try { + // Parse time in "yyyyMMddHHmmssSSS" format + startTime = parseTime(time); + if (startTime > span.finishTime + || (startTime < span.startTime - CatchpointProperties.TIME_MAX_DIFF)) { + startTime = span.startTime; + ThundraLogger.debug(` Invalid Catchpoint time: ${time}`); + } + } catch (error) { + ThundraLogger.debug(` Unable to parse Catchpoint time: ${time}`, err); + } + } + */ + const traceId: string = spanContext.traceId; + const transactionId: string = Utils.generateId(); + const spanId: string = Utils.generateId(); + const startTimestamp: number = startTime; + const finishTimestamp: number = span.finishTime; + const duration: number = finishTimestamp - startTimestamp; + const applicationInfo: ApplicationInfo = WebWrapperUtils.getCatchpointApplicationInfo(request, appName, appRegion); + const error = execContext.error ? Utils.parseError(execContext.error) : undefined; + + const resource: Resource = + WebWrapperUtils.getCatchpointRequestResource( + execContext, request, span, duration, error); + const catchpointInvocationData: InvocationData = + WebWrapperUtils.createCatchpointRequestInvocation( + execContext, applicationInfo, + regionName, countryName, cityName, testId, + traceId, transactionId, spanId, + startTimestamp, finishTimestamp, + resource, error); + + const catchpointInvocation = + Utils.generateReport(catchpointInvocationData, pluginContext.apiKey); + execContext.report(catchpointInvocation); + + const catchpointSpanData: SpanData = + WebWrapperUtils.createCatchpointRequestSpan( + execContext, applicationInfo, span, resource, + regionName, countryName, cityName, testId, + traceId, transactionId, spanId, + startTimestamp, finishTimestamp); + const catchpointSpan = + Utils.generateReport(catchpointSpanData, pluginContext.apiKey); + execContext.report(catchpointSpan); + + response.headers = response.headers || {}; + response.headers[TraceHeaderTags.TRACE_ID] = spanContext.traceId; + } + } diff --git a/src/wrappers/express/ExpressExecutor.ts b/src/wrappers/express/ExpressExecutor.ts index 3973eff9..bfe7b052 100644 --- a/src/wrappers/express/ExpressExecutor.ts +++ b/src/wrappers/express/ExpressExecutor.ts @@ -59,8 +59,6 @@ export function startTrace(pluginContext: PluginContext, execContext: ExecutionC } export function finishTrace(pluginContext: PluginContext, execContext: ExecutionContext) { - WrapperUtils.finishTrace(pluginContext, execContext); - const { rootSpan, response, @@ -70,6 +68,8 @@ export function finishTrace(pluginContext: PluginContext, execContext: Execution InvocationSupport.setAgentTag(HttpTags.HTTP_STATUS, response.statusCode); Utils.copyProperties(response, ['statusCode'], rootSpan.tags, [HttpTags.HTTP_STATUS]); Utils.copyProperties(request.route, ['path'], rootSpan.tags, [HttpTags.HTTP_ROUTE_PATH]); + + WrapperUtils.finishTrace(pluginContext, execContext); } function handleRoutePath(context: ExecutionContext, resourceName: string) { diff --git a/src/wrappers/hapi/HapiExecutor.ts b/src/wrappers/hapi/HapiExecutor.ts index 25e445c3..5942b6da 100644 --- a/src/wrappers/hapi/HapiExecutor.ts +++ b/src/wrappers/hapi/HapiExecutor.ts @@ -32,7 +32,6 @@ export function finishInvocation(pluginContext: PluginContext, execContext: Exec * @param {ExecutionContext} execContext */ export function startTrace(pluginContext: PluginContext, execContext: ExecutionContext) { - WrapperUtils.startTrace(pluginContext, execContext); const { @@ -83,8 +82,6 @@ export function startTrace(pluginContext: PluginContext, execContext: ExecutionC * @param {ExecutionContext} execContext */ export function finishTrace(pluginContext: PluginContext, execContext: ExecutionContext) { - WrapperUtils.finishTrace(pluginContext, execContext); - const { rootSpan, response, @@ -107,7 +104,6 @@ export function finishTrace(pluginContext: PluginContext, execContext: Execution ...{ [TriggerHeaderTags.RESOURCE_NAME]: triggerOperationName }, }; } - statusCode = response.output.statusCode; } } @@ -116,4 +112,6 @@ export function finishTrace(pluginContext: PluginContext, execContext: Execution InvocationSupport.setAgentTag(HttpTags.HTTP_STATUS, statusCode); Utils.copyProperties(request.route, ['path'], rootSpan.tags, [HttpTags.HTTP_ROUTE_PATH]); rootSpan.tags[HttpTags.HTTP_STATUS] = statusCode; + + WrapperUtils.finishTrace(pluginContext, execContext); } diff --git a/src/wrappers/hapi/HapiWrapper.ts b/src/wrappers/hapi/HapiWrapper.ts index f59b88b4..95da649e 100644 --- a/src/wrappers/hapi/HapiWrapper.ts +++ b/src/wrappers/hapi/HapiWrapper.ts @@ -50,9 +50,7 @@ const initWrapperContext = () => { * @param {Function} wrappedFunction */ function hapiServerWrapper(wrappedFunction: Function) { - return function internalHapiServerWrapper() { - ThundraLogger.debug(' Hapi server wrapped.'); const server = wrappedFunction.apply(this, arguments); @@ -65,25 +63,23 @@ function hapiServerWrapper(wrappedFunction: Function) { return WrapperUtils.createExecContext(ApplicationClassName, ApplicationDomainName); }, async function () { + ThundraLogger.debug(' Running with execution context'); - ThundraLogger.debug(' Running with execution context'); + const context: ExecutionContext = this; - const context: ExecutionContext = this; + request.hostname = request.info.hostname; + request.thundra = { + executionContext: context, + }; - request.hostname = request.info.hostname; - request.thundra = { - executionContext: context, - }; - - ThundraLogger.debug(' Before handling request'); - await WrapperUtils.beforeRequest(request, request.response, _PLUGINS); - }); + ThundraLogger.debug(' Before handling request'); + await WrapperUtils.beforeRequest(request, request.response, _PLUGINS); + }); /** * Handler method for outgoing response & finish instrumentation process */ const finishInstrument = async (request: any, response: any) => { - ThundraLogger.debug(' Finish Instrumentation'); if (request.thundra && request.thundra.executionContext) { @@ -105,11 +101,10 @@ function hapiServerWrapper(wrappedFunction: Function) { * Attach onPreHandler event of Hapi server * it will start instrumentation process */ - server.ext('onPreHandler', (request: any, h: any) => { - + server.ext('onPreHandler', async (request: any, h: any) => { ThundraLogger.debug(` Instrumentation started for request: ${request}`); - startInstrument(request); + await startInstrument(request); return h.continue; }); @@ -119,7 +114,6 @@ function hapiServerWrapper(wrappedFunction: Function) { * it will finish instrumentation process */ server.ext('onPreResponse', async (request: any, h: any) => { - ThundraLogger.debug(` Instrumentation finished for request: ${request}`); const response = request.response; @@ -130,7 +124,7 @@ function hapiServerWrapper(wrappedFunction: Function) { * if statusCode equels to 404 onPreHandler will not fired * so instrumentation process must be started in here */ - startInstrument(request); + await startInstrument(request); } } @@ -147,11 +141,10 @@ function hapiServerWrapper(wrappedFunction: Function) { * Instrument Hapi wrapper & wrap Hapi server process */ export const init = () => { - - const isHapiTracingDisabled = ConfigProvider.get(ConfigNames.THUNDRA_TRACE_INTEGRATIONS_HAPI_DISABLE); + const isHapiTracingDisabled = + ConfigProvider.get(ConfigNames.THUNDRA_TRACE_INTEGRATIONS_HAPI_DISABLE); if (isHapiTracingDisabled) { - ThundraLogger.debug(' Hapi wrapper disabled ...'); return false; @@ -163,7 +156,6 @@ export const init = () => { ModuleUtils.instrument( [moduleName], undefined, (lib: any, cfg: any) => { - initWrapperContext(); let moduleWillBeInitilized = false; diff --git a/src/wrappers/koa/KoaExecutor.ts b/src/wrappers/koa/KoaExecutor.ts index ec698328..ffe8a2dc 100644 --- a/src/wrappers/koa/KoaExecutor.ts +++ b/src/wrappers/koa/KoaExecutor.ts @@ -58,8 +58,6 @@ export function startTrace(pluginContext: PluginContext, execContext: ExecutionC } export function finishTrace(pluginContext: PluginContext, execContext: ExecutionContext) { - WrapperUtils.finishTrace(pluginContext, execContext); - const {rootSpan, response, request} = execContext; if (request._matchedRoute) { @@ -76,7 +74,8 @@ export function finishTrace(pluginContext: PluginContext, execContext: Execution Utils.copyProperties(request, ['path'], rootSpan.tags, [HttpTags.HTTP_ROUTE_PATH]); if (execContext.triggerOperationName) { - response.set(TriggerHeaderTags.RESOURCE_NAME, execContext.triggerOperationName); } + + WrapperUtils.finishTrace(pluginContext, execContext); } diff --git a/src/wrappers/koa/KoaWrapper.ts b/src/wrappers/koa/KoaWrapper.ts index 2dc421e5..041e21b0 100644 --- a/src/wrappers/koa/KoaWrapper.ts +++ b/src/wrappers/koa/KoaWrapper.ts @@ -37,7 +37,6 @@ const initWrapperContext = () => { }; export function koaMiddleWare(opts: any = {}) { - ThundraLogger.debug(' Creating Thundra middleware ...'); return async (ctx: any, next: any) => ExecutionContextManager.runWithContext( @@ -109,7 +108,6 @@ export function init() { ModuleUtils.instrument( ['koa/lib/application.js'], undefined, (lib: any, cfg: any) => { - initWrapperContext(); ModuleUtils.patchModule( diff --git a/src/wrappers/lambda/LambdaApplicationInfoProvider.ts b/src/wrappers/lambda/LambdaApplicationInfoProvider.ts index bd32ebd9..72d3ec32 100644 --- a/src/wrappers/lambda/LambdaApplicationInfoProvider.ts +++ b/src/wrappers/lambda/LambdaApplicationInfoProvider.ts @@ -40,6 +40,8 @@ export class LambdaApplicationInfoProvider implements ApplicationInfoProvider { applicationRegion: region ? region : '', applicationStage: '', applicationVersion: functionVersion ? functionVersion : '', + applicationRuntime: 'node', + applicationRuntimeVersion: process.version, applicationTags: Utils.getApplicationTags(), }; } diff --git a/test/wrappers/hapi/hapi-wrapper.test.js b/test/wrappers/hapi/hapi-wrapper.test.js index 67c4b21a..b7dcb71d 100644 --- a/test/wrappers/hapi/hapi-wrapper.test.js +++ b/test/wrappers/hapi/hapi-wrapper.test.js @@ -23,7 +23,6 @@ describe('Hapijs Wrapper Tests', () => { let server; beforeAll(async () => { - ConfigProvider.init({ apiKey: 'foo' }); HapiWrapper.__PRIVATE__.getReporter = jest.fn(() => createMockReporterInstance()); @@ -41,7 +40,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('should create root span', async () => { - const { headers } = await request(server.listener).get('/'); expect(headers[TriggerHeaderTags.RESOURCE_NAME]).toBeTruthy(); @@ -64,7 +62,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('should trace error', async () => { - await request(server.listener).get('/error'); const execContext = ExecutionContextManager.get(); @@ -89,7 +86,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('should trace 404 not found', async () => { - await request(server.listener).get('/404'); const execContext = ExecutionContextManager.get(); @@ -112,7 +108,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('should pass trace context', async () => { - await request(server.listener) .get('/') .set('x-catchpoint-transaction-id', 'incomingTransactionId') @@ -131,7 +126,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('should fill execution context', async () => { - await request(server.listener).get('/', () => { const execContext = ExecutionContextManager.get(); @@ -169,7 +163,6 @@ describe('Hapijs Wrapper Tests', () => { }); test('Hapijs Wrapper Custom Span Tests', async () => { - let execContext; const wait = (ms) => new Promise(r => setTimeout(r, ms)); @@ -238,4 +231,4 @@ describe('Hapijs Wrapper Tests', () => { expect(customSpan2.spanContext.parentId).toBe(rootSpanId); expect(customSpan2.spanContext.traceId).toBe(traceId); }); -}); \ No newline at end of file +});