diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml new file mode 100644 index 0000000..3d68a63 --- /dev/null +++ b/.github/workflows/pr-build.yml @@ -0,0 +1,60 @@ +name: JavaScript Instrumentation PR Build +on: + pull_request: + branches: + - main + - "release/v*" + +permissions: + id-token: write + contents: read + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false # ensures the entire test matrix is run, even if one permutation fails + matrix: + node: ["14", "16", "18", "20", "22"] + env: + NPM_CONFIG_UNSAFE_PERM: true + steps: + - name: Checkout Repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Update npm to a version that supports workspaces (v7 or later) + if: ${{ matrix.node < 16 }} + run: npm install -g npm@9 # npm@9 supports node >=14.17.0 + - name: NPM Clean Install + # https://docs.npmjs.com/cli/v10/commands/npm-ci + run: npm ci + - name: Compile all NPM projects + run: npm run compile + - name: Unit tests (Full) + if: matrix.code-coverage + run: npm run test + - name: Report Coverage + if: ${{ matrix.code-coverage && !cancelled()}} + uses: codecov/codecov-action@v4 + with: + verbose: true + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + - run: npm ci + - name: Lint + run: | + npm run lint + npm run lint:markdown + npm run lint:readme diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..3f6711f --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,18 @@ +// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/d52d4218235528dcecc706867425b86bac49b1f0/.markdownlint-cli2.jsonc +{ + "config": { + // https://github.com/DavidAnson/markdownlint/blob/main/README.md#rules--aliases + "MD013": false, + "MD024": false, + "MD033": false, + "MD041": false, + // MD004/ul-style. We prefer dash, but generated CHANGELOG.md files use + // asterisk. The default "consistent" is a good compromise. + "MD004": { "style": "consistent" }, + // This setting is for a future potential CHANGELOG.md file, which doesn't exist yet + "MD012": false // no-multiple-blanks; disabled because common in CHANGELOG.md files + }, + "gitignore": true, + "noBanner": true, + "noProgress": true +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b627cf..fd35679 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,5 @@ ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. + with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26d1be4..f620fff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ reported the issue. Please try to include as much information as you can. Detail ## Contributing via Pull Requests + Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the *main* branch. @@ -47,23 +48,34 @@ The following are useful commands that can be run within the `root` directory of Use `npm install` within the root directory to initialize all package directories before running any of the following commands. ### Build TypeScript into JavaScript -``` + +```shell npm run compile ``` ### Lint -``` + +```shell npm run lint ``` ### Lint automatic fixing -``` + +```shell npm run lint:fix ``` + +### Run unit tests + +```shell +npm run tests +``` + ### Test the local ADOT JS package with your own local NodeJS project In the `./aws-distro-opentelemetry-node-autoinstrumentation` directory, run: -``` + +```shell npm install npm run compile npm link @@ -71,28 +83,32 @@ npm link In the target local NodeJS project to be instrumented, run -``` +```shell npm install npm link @aws/aws-distro-opentelemetry-node-autoinstrumentation ``` Your NodeJS project can now be run with your local copy of the ADOT NodeJS code with: -``` + +```shell node --require '@aws/aws-distro-opentelemetry-node-autoinstrumentation/register' your-application.js.js ``` ## Finding contributions to work on + Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. ## Code of Conduct + This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. + with any additional questions or comments. ## Security issue notifications + If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/README.md b/aws-distro-opentelemetry-node-autoinstrumentation/README.md index 80863a4..14eb785 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/README.md +++ b/aws-distro-opentelemetry-node-autoinstrumentation/README.md @@ -1,18 +1,20 @@ # AWS Distro for OpenTelemetry (ADOT) NodeJS Auto-Instrumentation Install this package into your NodeJS project with: -``` + +```shell npm install --save @aws/aws-distro-opentelemetry-node-autoinstrumentation ``` Run your application with ADOT NodeJS with: -``` + +```shell node --require '@aws/aws-distro-opentelemetry-node-autoinstrumentation/register' your-application.js ``` ## Sample Environment Variables for Application Signals -``` +```shell export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ export OTEL_PROPAGATORS=xray,tracecontext,b3,b3multi \ export OTEL_TRACES_EXPORTER=console,otlp \ diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index e8bbee0..df8303d 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -45,7 +45,8 @@ "rimraf": "5.0.5", "sinon": "15.2.0", "ts-mocha": "10.0.0", - "typescript": "4.4.4" + "typescript": "4.4.4", + "expect": "29.2.0" }, "dependencies": { "@opentelemetry/api": "1.9.0", diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/always-record-sampler.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/always-record-sampler.ts new file mode 100644 index 0000000..1d3b2ff --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/always-record-sampler.ts @@ -0,0 +1,67 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Context, Link, SpanAttributes, SpanKind } from '@opentelemetry/api'; +import { Sampler, SamplingDecision, SamplingResult } from '@opentelemetry/sdk-trace-base'; + +/** + * This sampler will return the sampling result of the provided {@link #rootSampler}, unless the + * sampling result contains the sampling decision {@link SamplingDecision.NOT_RECORD}, in which case, a + * new sampling result will be returned that is functionally equivalent to the original, except that + * it contains the sampling decision {@link SamplingDecision.RECORD}. This ensures that all + * spans are recorded, with no change to sampling. + * + *

The intended use case of this sampler is to provide a means of sending all spans to a + * processor without having an impact on the sampling rate. This may be desirable if a user wishes + * to count or otherwise measure all spans produced in a service, without incurring the cost of 100% + * sampling. + */ +export class AlwaysRecordSampler implements Sampler { + private rootSampler: Sampler; + + public static create(rootSampler: Sampler): AlwaysRecordSampler { + return new AlwaysRecordSampler(rootSampler); + } + + private constructor(rootSampler: Sampler) { + if (rootSampler === null) { + throw new Error('rootSampler is null. It must be provided'); + } + this.rootSampler = rootSampler; + } + + shouldSample( + context: Context, + traceId: string, + spanName: string, + spanKind: SpanKind, + attributes: SpanAttributes, + links: Link[] + ): SamplingResult { + const rootSamplerSamplingResult: SamplingResult = this.rootSampler.shouldSample( + context, + traceId, + spanName, + spanKind, + attributes, + links + ); + if (rootSamplerSamplingResult.decision === SamplingDecision.NOT_RECORD) { + return this.wrapResultWithRecordOnlyResult(rootSamplerSamplingResult); + } + return rootSamplerSamplingResult; + } + + toString(): string { + return `AlwaysRecordSampler{${this.rootSampler.toString()}}`; + } + + wrapResultWithRecordOnlyResult(result: SamplingResult) { + const wrappedResult: SamplingResult = { + decision: SamplingDecision.RECORD, + attributes: result.attributes, + traceState: result.traceState, + }; + return wrappedResult; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts new file mode 100644 index 0000000..9b4b4b2 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Utility class holding attribute keys with special meaning to AWS components +export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = { + AWS_SPAN_KIND: 'aws.span.kind', + AWS_LOCAL_SERVICE: 'aws.local.service', + AWS_LOCAL_OPERATION: 'aws.local.operation', + AWS_REMOTE_SERVICE: 'aws.remote.service', + AWS_REMOTE_OPERATION: 'aws.remote.operation', + AWS_REMOTE_RESOURCE_TYPE: 'aws.remote.resource.type', + AWS_REMOTE_RESOURCE_IDENTIFIER: 'aws.remote.resource.identifier', + AWS_SDK_DESCENDANT: 'aws.sdk.descendant', + AWS_CONSUMER_PARENT_SPAN_KIND: 'aws.consumer.parent.span.kind', + + AWS_REMOTE_TARGET: 'aws.remote.target', + AWS_REMOTE_DB_USER: 'aws.remote.db.user', + + // Used for JavaScript workaround - attribute for pre-calculated value of isLocalRoot + AWS_IS_LOCAL_ROOT: 'aws.is.local.root', + + // Divergence from Java/Python + // TODO: Audit this: These will most definitely be different in JavaScript. + // For example: + // - `messaging.url` for AWS_QUEUE_URL + // - `aws.dynamodb.table_names` for AWS_TABLE_NAME + AWS_BUCKET_NAME: 'aws.bucket.name', + AWS_QUEUE_URL: 'aws.queue.url', + AWS_QUEUE_NAME: 'aws.queue.name', + AWS_STREAM_NAME: 'aws.stream.name', + AWS_TABLE_NAME: 'aws.table.name', +}; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index cd2c7a2..ee0e6d0 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -1,5 +1,5 @@ -//Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -//SPDX-License-Identifier: Apache-2.0 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 import { diag } from '@opentelemetry/api'; import { diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts new file mode 100644 index 0000000..dd8d451 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts @@ -0,0 +1,245 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AttributeValue, Context, SpanContext, SpanKind, diag, isSpanContextValid, trace } from '@opentelemetry/api'; +import { InstrumentationLibrary } from '@opentelemetry/core'; +import { ReadableSpan, Span } from '@opentelemetry/sdk-trace-base'; + +import { + MessagingOperationValues, + SEMATTRS_DB_OPERATION, + SEMATTRS_DB_STATEMENT, + SEMATTRS_DB_SYSTEM, + SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_TARGET, + SEMATTRS_MESSAGING_OPERATION, + SEMATTRS_RPC_SYSTEM, +} from '@opentelemetry/semantic-conventions'; +import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys'; +import * as SQL_DIALECT_KEYWORDS_JSON from './configuration/sql_dialect_keywords.json'; + +/** Utility class designed to support shared logic across AWS Span Processors. */ +export class AwsSpanProcessingUtil { + // Default attribute values if no valid span attribute value is identified + static UNKNOWN_SERVICE: string = 'UnknownService'; + static UNKNOWN_OPERATION: string = 'UnknownOperation'; + static UNKNOWN_REMOTE_SERVICE: string = 'UnknownRemoteService'; + static UNKNOWN_REMOTE_OPERATION: string = 'UnknownRemoteOperation'; + static INTERNAL_OPERATION: string = 'InternalOperation'; + static LOCAL_ROOT: string = 'LOCAL_ROOT'; + static SQS_RECEIVE_MESSAGE_SPAN_NAME: string = 'Sqs.ReceiveMessage'; + static AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX: string = '@opentelemetry/instrumentation-aws-sdk'; + + // Max keyword length supported by parsing into remote_operation from DB_STATEMENT. + // The current longest command word is DATETIME_INTERVAL_PRECISION at 27 characters. + // If we add a longer keyword to the sql dialect keyword list, need to update the constant below. + static MAX_KEYWORD_LENGTH: number = 27; + static SQL_DIALECT_PATTERN = '^(?:' + AwsSpanProcessingUtil.getDialectKeywords().join('|') + ')\\b'; + + static getDialectKeywords(): string[] { + return SQL_DIALECT_KEYWORDS_JSON.keywords; + } + + /** + * Ingress operation (i.e. operation for Server and Consumer spans) will be generated from + * "http.method + http.target/with the first API path parameter" if the default span name equals + * null, UnknownOperation or http.method value. + */ + static getIngressOperation(span: ReadableSpan): string { + let operation: string = span.name; + if (AwsSpanProcessingUtil.shouldUseInternalOperation(span)) { + operation = AwsSpanProcessingUtil.INTERNAL_OPERATION; + } else if (!AwsSpanProcessingUtil.isValidOperation(span, operation)) { + operation = AwsSpanProcessingUtil.generateIngressOperation(span); + } + return operation; + } + + static getEgressOperation(span: ReadableSpan): string | undefined { + if (AwsSpanProcessingUtil.shouldUseInternalOperation(span)) { + return AwsSpanProcessingUtil.INTERNAL_OPERATION; + } else { + const awsLocalOperation: AttributeValue | undefined = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION]; + return awsLocalOperation === undefined ? undefined : awsLocalOperation.toString(); + } + } + + /** + * Extract the first part from API http target if it exists + * + * @param httpTarget http request target string value. Eg, /payment/1234 + * @return the first part from the http target. Eg, /payment + */ + static extractAPIPathValue(httpTarget: string | undefined | null): string { + // In TypeScript, `httpTarget == null` checks both null and undefined + if (httpTarget == null || httpTarget === '') { + return '/'; + } + const paths: string[] = httpTarget.split('/'); + if (paths.length > 1) { + return '/' + paths[1]; + } + return '/'; + } + + static isKeyPresent(span: ReadableSpan, key: string): boolean { + return span.attributes[key] !== undefined; + } + + static isAwsSDKSpan(span: ReadableSpan): boolean { + const rpcSystem: AttributeValue | undefined = span.attributes[SEMATTRS_RPC_SYSTEM]; + + if (rpcSystem === undefined) { + return false; + } + + // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/aws-sdk/#common-attributes + return 'aws-api' === rpcSystem; + } + + static shouldGenerateServiceMetricAttributes(span: ReadableSpan): boolean { + return ( + (AwsSpanProcessingUtil.isLocalRoot(span) && !AwsSpanProcessingUtil.isSqsReceiveMessageConsumerSpan(span)) || + SpanKind.SERVER === span.kind + ); + } + + static shouldGenerateDependencyMetricAttributes(span: ReadableSpan): boolean { + // Divergence from Java/Python + // In OTel JS, the AWS SDK instrumentation creates two Client Spans, one for AWS SDK, + // and another for the underlying HTTP Client used by the SDK. The following code block + // ensures that dependency metrics are not generated for direct descendent of AWS SDK Spans. + const isAwsSdkDescendent: AttributeValue | undefined = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SDK_DESCENDANT]; + if (isAwsSdkDescendent !== undefined && isAwsSdkDescendent === 'true') { + return false; + } + + return ( + SpanKind.CLIENT === span.kind || + SpanKind.PRODUCER === span.kind || + (AwsSpanProcessingUtil.isDependencyConsumerSpan(span) && + !AwsSpanProcessingUtil.isSqsReceiveMessageConsumerSpan(span)) + ); + } + + static isConsumerProcessSpan(spanData: ReadableSpan): boolean { + const messagingOperation: AttributeValue | undefined = spanData.attributes[SEMATTRS_MESSAGING_OPERATION]; + if (messagingOperation === undefined) { + return false; + } + + return SpanKind.CONSUMER === spanData.kind && MessagingOperationValues.PROCESS === messagingOperation; + } + + // Any spans that are Local Roots and also not SERVER should have aws.local.operation renamed to + // InternalOperation. + static shouldUseInternalOperation(span: ReadableSpan): boolean { + return AwsSpanProcessingUtil.isLocalRoot(span) && SpanKind.SERVER !== span.kind; + } + + // A span is a local root if it has no parent or if the parent is remote. This function checks the + // parent context and returns true if it is a local root. + static isLocalRoot(spanData: ReadableSpan): boolean { + // Workaround implemented for this function as parent span context is not obtainable. + // This isLocalRoot value is precalculated in AttributePropagatingSpanProcessor, which + // is started before the other processors (e.g. AwsSpanMetricsProcessor) + // Thus this function is implemented differently than in Java/Python + const isLocalRoot: AttributeValue | undefined = spanData.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT]; + if (isLocalRoot === undefined) { + // isLocalRoot should be precalculated, this code block should not be entered + diag.debug('isLocalRoot for span has not been precalculated. Assuming span is Local Root Span.'); + return true; + } + return isLocalRoot as boolean; + } + + // To identify the SQS consumer spans produced by AWS SDK instrumentation + private static isSqsReceiveMessageConsumerSpan(spanData: ReadableSpan): boolean { + const spanName: string = spanData.name; + const spanKind: SpanKind = spanData.kind; + const messagingOperation: AttributeValue | undefined = spanData.attributes[SEMATTRS_MESSAGING_OPERATION]; + + const instrumentationLibrary: InstrumentationLibrary = spanData.instrumentationLibrary; + + return ( + AwsSpanProcessingUtil.SQS_RECEIVE_MESSAGE_SPAN_NAME.toLowerCase() === spanName.toLowerCase() && + SpanKind.CONSUMER === spanKind && + instrumentationLibrary != null && + instrumentationLibrary.name.startsWith(AwsSpanProcessingUtil.AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX) && + (messagingOperation === undefined || messagingOperation === MessagingOperationValues.PROCESS) + ); + } + + private static isDependencyConsumerSpan(span: ReadableSpan): boolean { + if (SpanKind.CONSUMER !== span.kind) { + return false; + } else if (AwsSpanProcessingUtil.isConsumerProcessSpan(span)) { + if (AwsSpanProcessingUtil.isLocalRoot(span)) { + return true; + } + const parentSpanKind: AttributeValue | undefined = + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_CONSUMER_PARENT_SPAN_KIND]; + + return SpanKind[SpanKind.CONSUMER] !== parentSpanKind; + } + return true; + } + + /** + * When Span name is null, UnknownOperation or HttpMethod value, it will be treated as invalid + * local operation value that needs to be further processed + */ + private static isValidOperation(span: ReadableSpan, operation: string): boolean { + if (operation == null || operation === AwsSpanProcessingUtil.UNKNOWN_OPERATION) { + return false; + } + if (AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_HTTP_METHOD)) { + const httpMethod: AttributeValue | undefined = span.attributes[SEMATTRS_HTTP_METHOD]; + return operation !== httpMethod; + } + return true; + } + + /** + * When span name is not meaningful(null, unknown or http_method value) as operation name for http + * use cases. Will try to extract the operation name from http target string + */ + private static generateIngressOperation(span: ReadableSpan): string { + let operation: string = AwsSpanProcessingUtil.UNKNOWN_OPERATION; + if (AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_HTTP_TARGET)) { + const httpTarget: AttributeValue | undefined = span.attributes[SEMATTRS_HTTP_TARGET]; + // get the first part from API path string as operation value + // the more levels/parts we get from API path the higher chance for getting high cardinality + // data + if (httpTarget != null) { + operation = AwsSpanProcessingUtil.extractAPIPathValue(httpTarget.toString()); + if (AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_HTTP_METHOD)) { + const httpMethod: AttributeValue | undefined = span.attributes[SEMATTRS_HTTP_METHOD]; + if (httpMethod != null) { + operation = httpMethod.toString() + ' ' + operation; + } + } + } + } + return operation; + } + + // Check if the current Span adheres to database semantic conventions + static isDBSpan(span: ReadableSpan): boolean { + return ( + AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_DB_SYSTEM) || + AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_DB_OPERATION) || + AwsSpanProcessingUtil.isKeyPresent(span, SEMATTRS_DB_STATEMENT) + ); + } + + // Divergence from Java/Python + static setIsLocalRootInformation(span: Span, parentContext: Context): void { + const parentSpanContext: SpanContext | undefined = trace.getSpanContext(parentContext); + const isParentSpanContextValid: boolean = parentSpanContext !== undefined && isSpanContextValid(parentSpanContext); + const isParentSpanRemote: boolean = parentSpanContext !== undefined && parentSpanContext.isRemote === true; + + const isLocalRoot: boolean = span.parentSpanId === undefined || !isParentSpanContextValid || isParentSpanRemote; + span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT, isLocalRoot); + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/configuration/sql_dialect_keywords.json b/aws-distro-opentelemetry-node-autoinstrumentation/src/configuration/sql_dialect_keywords.json new file mode 100644 index 0000000..86b190e --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/configuration/sql_dialect_keywords.json @@ -0,0 +1,818 @@ +{ + "top_comments": " The keywords are sorted based on descending order of the length of the keyword characters.This list was generated by combining keywords from various SQL systems: SQL Server, PostgreSQL, SQLite, Oracle.", + "keywords": [ + "DATETIME_INTERVAL_PRECISION", + "PARAMETER_SPECIFIC_CATALOG", + "PARAMETER_ORDINAL_POSITION", + "USER_DEFINED_TYPE_CATALOG", + "PARAMETER_SPECIFIC_SCHEMA", + "TRANSACTIONS_ROLLED_BACK", + "USER_DEFINED_TYPE_SCHEMA", + "PARAMETER_SPECIFIC_NAME", + "USER_DEFINED_TYPE_NAME", + "TRANSACTIONS_COMMITTED", + "DATETIME_INTERVAL_CODE", + "RETURNED_OCTET_LENGTH", + "COMMAND_FUNCTION_CODE", + "CHARACTER_SET_CATALOG", + "DYNAMIC_FUNCTION_CODE", + "MESSAGE_OCTET_LENGTH", + "CHARACTER_SET_SCHEMA", + "INSERT INTO SELECT", + "CHARACTER_SET_NAME", + "CONSTRAINT_CATALOG", + "CHARACTER VARYING", + "RETURNED_SQLSTATE", + "TRANSATION_ACTIVE", + "CURRENT_TIMESTAMP", + "CONSTRAINT_SCHEMA", + "COLLATION_CATALOG", + "DOUBLE PRECISION", + "DYNAMIC_FUNCTION", + "CONDITION_NUMBER", + "COMMAND_FUNCTION", + "CHARACTER_LENGTH", + "COLLATION_SCHEMA", + "FULL OUTER JOIN", + "DROP CONSTRAINT", + "BACKUP DATABASE", + "CREATE DATABASE", + "SELECT DISTINCT", + "TIMEZONE_MINUTE", + "ROUTINE_CATALOG", + "TRIGGER_CATALOG", + "SUBCLASS_ORIGIN", + "MIN_PART_STRING", + "CONNECTION_NAME", + "PARSE_URL_TUPLE", + "MAX_PART_STRING", + "RETURNED_LENGTH", + "CHARACTERISTICS", + "CONSTRAINT_NAME", + "TRUNCATE TABLE", + "PARAMETER_NAME", + "UNIX_TIMESTAMP", + "ARRAY_CONTAINS", + "MESSAGE_LENGTH", + "LOCALTIMESTAMP", + "TRIGGER_SCHEMA", + "ROUTINE_SCHEMA", + "IMPLEMENTATION", + "AUTO_INCREMENT", + "SIMPLE_INTEGER", + "COLLATION_NAME", + "PARAMETER_MODE", + "REGEXP_REPLACE", + "DROP DATABASE", + "STRAIGHT_JOIN", + "SYS_REFCURSOR", + "UTC_TIMESTAMP", + "MIN_PART_DATE", + "DETERMINISTIC", + "TBLPROPERTIES", + "PART_COUNT_BY", + "TIMEZONE_HOUR", + "SIMPLE_DOUBLE", + "MAX_PART_DATE", + "FROM_UNIXTIME", + "SPECIFIC_NAME", + "MAXLOGMEMBERS", + "ON_ERROR_STOP", + "TXID_SNAPSHOT", + "MAXLOGHISTORY", + "CORRESPONDING", + "AUTHORIZATION", + "TIMESTAMP_ISO", + "CREATE INDEX", + "DROP DEFAULT", + "CREATE TABLE", + "ALTER COLUMN", + "CURRENT_ROLE", + "MAXINSTANCES", + "CONCURRENTLY", + "ROUTINE_NAME", + "MIN_PART_INT", + "SESSION_USER", + "MESSAGE_TEXT", + "INSTANTIABLE", + "MAX_PART_INT", + "STATEMENT_ID", + "OCTET_LENGTH", + "CURRENT_DATE", + "NOARCHIVELOG", + "TO_TIMESTAMP", + "COLLECT_LIST", + "CATALOG_NAME", + "TRIGGER_NAME", + "SIMPLE_FLOAT", + "NOCREATEUSER", + "CURRENT_PATH", + "CLASS_ORIGIN", + "MAXDATAFILES", + "CURRENT_TIME", + "SQLEXCEPTION", + "SERIALIZABLE", + "SPECIFICTYPE", + "CURRENT_USER", + "IS NOT NULL", + "SELECT INTO", + "DROP COLUMN", + "PRIMARY KEY", + "FOREIGN KEY", + "ALTER TABLE", + "BIT VARYING", + "INSERT INTO", + "CREATE VIEW", + "CONTROLFILE", + "SERVER_NAME", + "REFERENCING", + "TRANSLATION", + "DBMS_OUTPUT", + "CARDINALITY", + "UNCOMMITTED", + "CHAR_LENGTH", + "PLS_INTEGER", + "SYSTEM_USER", + "SMALLSERIAL", + "UNENCRYPTED", + "MAXLOGFILES", + "DISTINCTROW", + "CONSTRAINTS", + "PCTINCREASE", + "CURSOR_NAME", + "LANCOMPILER", + "NORESETLOGS", + "COLUMN_NAME", + "INSENSITIVE", + "DIAGNOSTICS", + "SCHEMA_NAME", + "CONSTRUCTOR", + "TRANSACTION", + "OUTER JOIN", + "INNER JOIN", + "DROP TABLE", + "RIGHT JOIN", + "SELECT TOP", + "DROP INDEX", + "POSEXPLODE", + "CONNECTION", + "NOCOMPRESS", + "MINEXTENTS", + "ARCHIVELOG", + "SAVE_POINT", + "MAXEXTENTS", + "SQLWARNING", + "COMPLETION", + "ORDINALITY", + "CHECKPOINT", + "PRIVILEGES", + "ASSIGNMENT", + "ASYMMETRIC", + "TRANSFORMS", + "DELIMITERS", + "PROCEDURAL", + "EXTERNALLY", + "CREATEUSER", + "REFERENCES", + "DESCRIPTOR", + "CONSTRAINT", + "SUCCESSFUL", + "MAXELEMENT", + "CONVERSION", + "TABLE_NAME", + "UNIQUEJOIN", + "DEFERRABLE", + "PARAMETERS", + "NOCREATEDB", + "IDENTIFIED", + "EXCEPTIONS", + "OVERRIDING", + "DEALLOCATE", + "NOMAXVALUE", + "PART_COUNT", + "KEY_MEMBER", + "NOMINVALUE", + "STATISTICS", + "BIT_LENGTH", + "RESTRICTED", + "DICTIONARY", + "INITIALIZE", + "REPEATABLE", + "DISTRIBUTE", + "ASENSITIVE", + "TABLESPACE", + "TRANSATION", + "DESTRUCTOR", + "DISCONNECT", + "MINELEMENT", + "DROP VIEW", + "LEFT JOIN", + "UNION ALL", + "UNBOUNDED", + "PRECISION", + "FILE_TYPE", + "TIMESTAMP", + "CONDITION", + "OPERATION", + "SUBSTRING", + "CHARACTER", + "INCLUDING", + "TRANSFORM", + "SAVEPOINT", + "FOLLOWING", + "UNLIMITED", + "EXCLUDING", + "STRUCTURE", + "PROCEDURE", + "POSITIVEN", + "SYMMETRIC", + "OVERWRITE", + "ISOLATION", + "DELIMITER", + "EXCLUSIVE", + "RETURNING", + "COLLATION", + "FREELISTS", + "VALIDATOR", + "DIRECTORY", + "AGGREGATE", + "LOCALTIME", + "HIERARCHY", + "TEMPORARY", + "EXCEPTION", + "ENCRYPTED", + "ASSERTION", + "COMMITTED", + "STATEMENT", + "INITIALLY", + "PARTITION", + "TERMINATE", + "BIGSERIAL", + "IMMEDIATE", + "RESETLOGS", + "PARAMETER", + "INDICATOR", + "GENERATED", + "RECURSIVE", + "SENSITIVE", + "TRANSLATE", + "INCREMENT", + "IMMUTABLE", + "INTERSECT", + "ROW_COUNT", + "ORDER BY", + "NOT NULL", + "GROUP BY", + "LOCATION", + "TRUNCATE", + "PREORDER", + "TRAILING", + "BACKWARD", + "NVARCHAR", + "OVERLAPS", + "SQLSTATE", + "CREATEDB", + "EXISTING", + "ABSOLUTE", + "GROUPING", + "MAXTRANS", + "FREELIST", + "POSITION", + "DATE_SUB", + "INTERVAL", + "UNSIGNED", + "MAXINDEX", + "DATAFILE", + "DEFERRED", + "TSVECTOR", + "ROWLABEL", + "MININDEX", + "CONFLICT", + "ROLLBACK", + "DISPATCH", + "OPERATOR", + "PARALLEL", + "PUT_LINE", + "VARIABLE", + "CASCADED", + "ENCODING", + "FUNCTION", + "SECURITY", + "EXCHANGE", + "NOTFOUND", + "TEMPLATE", + "CONTAINS", + "IDENTITY", + "NATURALN", + "TRIGGERS", + "SPECIFIC", + "RESIGNAL", + "INITRANS", + "EXTENDED", + "SQLERROR", + "PART_LOC", + "VARCHAR2", + "SIGNTYPE", + "SMALLINT", + "COMPRESS", + "DATABASE", + "ALLOCATE", + "PRESERVE", + "NULLABLE", + "CONTINUE", + "INSTANCE", + "WHENEVER", + "RESTRICT", + "CONTENTS", + "POSITIVE", + "SEQUENCE", + "MAXVALUE", + "ARRAYLEN", + "MODIFIES", + "UNLISTEN", + "RELATIVE", + "LANGUAGE", + "NATIONAL", + "VOLATILE", + "VALIDATE", + "DISMOUNT", + "IMPLICIT", + "DISTINCT", + "COALESCE", + "UTL_FILE", + "ELEMENTS", + "RESOURCE", + "KEY_TYPE", + "EXTERNAL", + "DESCRIBE", + "MINVALUE", + "END-EXEC", + "DATE_ADD", + "INHERITS", + "DEFAULTS", + "SNAPSHOT", + "IS NULL", + "PERFORM", + "LOGFILE", + "DEFAULT", + "FORWARD", + "INTEGER", + "DESTROY", + "FOREACH", + "POSTFIX", + "SYSDATE", + "RECOVER", + "SECTION", + "INDEXES", + "TO_CHAR", + "INITIAL", + "ITERATE", + "OPTIONS", + "PRIVATE", + "ARCHIVE", + "OPTIMAL", + "ROUTINE", + "CHECKED", + "SYNONYM", + "TSQUERY", + "INCLUDE", + "NOORDER", + "INHERIT", + "STORAGE", + "SESSION", + "BREADTH", + "PREPARE", + "PRIMARY", + "GENERAL", + "CLUSTER", + "CONNECT", + "PCTUSED", + "PLACING", + "CURRENT", + "REINDEX", + "EXPLODE", + "TINYINT", + "CATALOG", + "SUBLIST", + "OVERLAY", + "EXECUTE", + "DECIMAL", + "LATERAL", + "UNNAMED", + "PACKAGE", + "DECLARE", + "COMPILE", + "CHARSET", + "PLPGSQL", + "NOTNULL", + "COMMENT", + "POLYGON", + "BETWEEN", + "FOREIGN", + "RESTART", + "TRIGGER", + "MACADDR", + "ANALYSE", + "VERBOSE", + "COLLATE", + "EXPLAIN", + "SIMILAR", + "GRANTED", + "VARCHAR", + "PENDANT", + "TRACING", + "INVOKER", + "COLLECT", + "CONVERT", + "NATURAL", + "TRUSTED", + "INSTEAD", + "NOCACHE", + "NOCYCLE", + "NOTHING", + "FORTRAN", + "SERIAL8", + "SEGMENT", + "PARTIAL", + "UNKNOWN", + "RETURNS", + "PCTFREE", + "LEADING", + "QUARTER", + "CASCADE", + "REPLACE", + "VARYING", + "SUMMARY", + "ANALYZE", + "RECHECK", + "NUMERIC", + "LOCATOR", + "VERSION", + "OFFLINE", + "EXTRACT", + "PROFILE", + "BOOLEAN", + "SQLCODE", + "WITHOUT", + "DEFINER", + "DISABLE", + "TO_DATE", + "NOAUDIT", + "DEFINED", + "DYNAMIC", + "SIGNED", + "NOTICE", + "SOURCE", + "VACUUM", + "STABLE", + "ENGINE", + "STRICT", + "REDUCE", + "SECOND", + "ISNULL", + "SIMPLE", + "MANUAL", + "BEFORE", + "METHOD", + "PG_LSN", + "EXISTS", + "CIRCLE", + "EQUALS", + "NULLIF", + "UNIQUE", + "DOUBLE", + "CANCEL", + "STRING", + "OBJECT", + "FREEZE", + "EVENTS", + "SUBSTR", + "THREAD", + "ATOMIC", + "VALUES", + "MODIFY", + "PREFIX", + "ROWNUM", + "BECOME", + "UPDATE", + "UNLOCK", + "UNNEST", + "SEARCH", + "RENAME", + "PASCAL", + "BACKUP", + "BITVAR", + "DECODE", + "SWITCH", + "EXTENT", + "OUTPUT", + "TABLES", + "RESULT", + "PUBLIC", + "SQLBUF", + "ONLINE", + "ENABLE", + "NOTIFY", + "SELECT", + "BINARY", + "GLOBAL", + "CREATE", + "STATIC", + "INLINE", + "NOWAIT", + "STDOUT", + "REVOKE", + "ESCAPE", + "MINUTE", + "SCROLL", + "MODULE", + "NOSORT", + "UROWID", + "SCHEMA", + "DOMAIN", + "WINDOW", + "ROLLUP", + "OPTION", + "CURSOR", + "OFFSET", + "CONCAT", + "COMMIT", + "UPSERT", + "NUMBER", + "INSERT", + "CALLED", + "LENGTH", + "BIGINT", + "SIGNAL", + "LISTEN", + "RETURN", + "CHANGE", + "NORMAL", + "SERIAL", + "COLUMN", + "SHARED", + "IGNORE", + "MANAGE", + "SYSTEM", + "STRUCT", + "ACCESS", + "HAVING", + "EXCEPT", + "LOCATE", + "DELETE", + "FOUND", + "LEAVE", + "OWNER", + "RTRIM", + "INDEX", + "VIEWS", + "CHAIN", + "ELSIF", + "WHILE", + "RIGHT", + "UNION", + "CHECK", + "ALIAS", + "WHERE", + "ROLES", + "OUTER", + "RAISE", + "SCOPE", + "INFIX", + "FIRST", + "COBOL", + "NCHAR", + "DEREF", + "LOCAL", + "FLOOR", + "GRANT", + "TOAST", + "INNER", + "STACK", + "RANGE", + "SPLIT", + "TREAT", + "PRIOR", + "USAGE", + "BYTEA", + "SPACE", + "QUOTA", + "ORDER", + "NCLOB", + "ARRAY", + "LIMIT", + "ALTER", + "TABLE", + "JSONB", + "PRINT", + "UPPER", + "COUNT", + "STYPE", + "ABORT", + "POINT", + "LISTS", + "USING", + "INPUT", + "MOUNT", + "FORCE", + "INOUT", + "MATCH", + "CLASS", + "LOWER", + "FINAL", + "MERGE", + "BLOCK", + "READS", + "EVERY", + "VALID", + "RESET", + "FALSE", + "INSTR", + "NAMES", + "WRITE", + "START", + "TRUNC", + "ROWID", + "GROUP", + "MINUS", + "UNDER", + "BEGIN", + "CYCLE", + "MONTH", + "SHARE", + "CLOSE", + "FETCH", + "SETOF", + "REUSE", + "STDIN", + "FLUSH", + "ILIKE", + "SCALE", + "UNTIL", + "AUDIT", + "LARGE", + "ADMIN", + "LEVEL", + "SYSID", + "LAYER", + "MONEY", + "BREAK", + "CROSS", + "MUMPS", + "AFTER", + "CACHE", + "FLOAT", + "DUMP", + "INT8", + "SETS", + "MODE", + "LESS", + "WEEK", + "CUBE", + "NONE", + "NULL", + "DATE", + "THEN", + "NEXT", + "YEAR", + "REAL", + "ONLY", + "LINK", + "WHEN", + "BLOB", + "SQRT", + "ELSE", + "DROP", + "PATH", + "CASE", + "PLAN", + "SOME", + "LIKE", + "DESC", + "SIZE", + "LEFT", + "FROM", + "HOST", + "ZONE", + "CIDR", + "RULE", + "HOLD", + "THAN", + "CHAR", + "EXEC", + "TEXT", + "HOUR", + "LSEG", + "ROLE", + "JOIN", + "STOP", + "READ", + "LAST", + "LOAD", + "FILE", + "DATA", + "EACH", + "BOTH", + "USER", + "OIDS", + "SHOW", + "GOTO", + "LONG", + "JSON", + "ROWS", + "LOCK", + "SORT", + "INTO", + "LOOP", + "BODY", + "TIME", + "CAST", + "TRUE", + "NVL2", + "OPEN", + "TRIM", + "INET", + "UUID", + "EXIT", + "FULL", + "CALL", + "FREE", + "MOVE", + "WITH", + "TEMP", + "OVER", + "SIGN", + "SELF", + "VIEW", + "CLOB", + "MORE", + "COPY", + "TYPE", + "WORK", + "LINE", + "UID", + "END", + "TOP", + "FOR", + "GET", + "MAP", + "REF", + "NOT", + "MOD", + "SQL", + "ROW", + "PLI", + "SET", + "LEN", + "OFF", + "DBA", + "MAX", + "ALL", + "DAY", + "RAW", + "STR", + "ADD", + "USE", + "MIN", + "OUT", + "DIV", + "FTP", + "ADA", + "SIN", + "XML", + "CMP", + "ASC", + "NVL", + "ANY", + "PAD", + "ABS", + "OLD", + "SUM", + "BOX", + "AND", + "INT", + "OWN", + "ARE", + "KEY", + "BIT", + "AVG", + "NEW", + "SCN", + "DEC", + "NOW", + "ON", + "OF", + "OR", + "AS", + "NO", + "IN", + "IF", + "TO", + "BY", + "AT", + "GO", + "IS", + "DO" + ], + "end_comments": " The keywords are sorted based on descending order of the length of the keyword characters.This list was generated by combining keywords from various SQL systems: SQL Server, PostgreSQL, SQLite, Oracle." +} \ No newline at end of file diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts index 69c45a6..f587fff 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts @@ -1,5 +1,5 @@ -//Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -//SPDX-License-Identifier: Apache-2.0 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 import { DiagConsoleLogger, diag } from '@opentelemetry/api'; import * as opentelemetry from '@opentelemetry/sdk-node'; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/sqs-url-parser.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/sqs-url-parser.ts new file mode 100644 index 0000000..ae44666 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/sqs-url-parser.ts @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +const HTTP_SCHEMA: string = 'http://'; +const HTTPS_SCHEMA: string = 'https://'; + +// Cannot define type for regex variables +// eslint-disable-next-line @typescript-eslint/typedef +const ALPHABET_REGEX = /^[a-zA-Z]+$/; + +export class SqsUrlParser { + /** + * Best-effort logic to extract queue name from an HTTP url. This method should only be used with + * a string that is, with reasonably high confidence, an SQS queue URL. Handles new/legacy/some + * custom URLs. Essentially, we require that the URL should have exactly three parts, delimited by + * /'s (excluding schema), the second part should be a 12-digit account id, and the third part + * should be a valid queue name, per SQS naming conventions. + */ + public static getQueueName(url: string | undefined): string | undefined { + if (url === undefined) { + return undefined; + } + url = url.replace(HTTP_SCHEMA, '').replace(HTTPS_SCHEMA, ''); + const splitUrl: string[] = url.split('/'); + if (splitUrl.length === 3 && this.isAccountId(splitUrl[1]) && this.isValidQueueName(splitUrl[2])) { + return splitUrl[2]; + } + return undefined; + } + + private static isAccountId(input: string): boolean { + if (input == null || input.length !== 12) { + return false; + } + + if (!this._checkDigits(input)) { + return false; + } + + return true; + } + + private static _checkDigits(str: string): boolean { + return /^\d+$/.test(str); + } + + private static isValidQueueName(input: string): boolean { + if (input === null || input.length === 0 || input.length > 80) { + return false; + } + + for (let i: number = 0; i < input.length; i++) { + const c: string = input.charAt(i); + if (c !== '_' && c !== '-' && !ALPHABET_REGEX.test(c) && !(c >= '0' && c <= '9')) { + return false; + } + } + + return true; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/always-record-sampler.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/always-record-sampler.test.ts new file mode 100644 index 0000000..9287f4e --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/always-record-sampler.test.ts @@ -0,0 +1,79 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Attributes, SpanKind, TraceState, context, createTraceState } from '@opentelemetry/api'; +import { + AlwaysOffSampler, + RandomIdGenerator, + Sampler, + SamplingDecision, + SamplingResult, +} from '@opentelemetry/sdk-trace-base'; +import { expect } from 'expect'; +import { AlwaysRecordSampler } from '../src/always-record-sampler'; + +let mockedSampler: Sampler; +let sampler: AlwaysRecordSampler; + +describe('AlwaysRecordSamplerTest', () => { + beforeEach(() => { + mockedSampler = new AlwaysOffSampler(); + sampler = AlwaysRecordSampler.create(mockedSampler); + }); + + it('testGetDescription', () => { + mockedSampler.toString = () => 'mockDescription'; + expect(sampler.toString()).toEqual('AlwaysRecordSampler{mockDescription}'); + }); + + it('testRecordAndSampleSamplingDecision', () => { + validateShouldSample(SamplingDecision.RECORD_AND_SAMPLED, SamplingDecision.RECORD_AND_SAMPLED); + }); + + it('testRecordOnlySamplingDecision', () => { + validateShouldSample(SamplingDecision.RECORD, SamplingDecision.RECORD); + }); + + it('testDropSamplingDecision', () => { + validateShouldSample(SamplingDecision.NOT_RECORD, SamplingDecision.RECORD); + }); +}); + +function validateShouldSample(rootDecision: SamplingDecision, expectedDecision: SamplingDecision): void { + const rootResult: SamplingResult = buildRootSamplingResult(rootDecision); + mockedSampler.shouldSample = () => { + return rootResult; + }; + + const actualResult: SamplingResult = sampler.shouldSample( + context.active(), + new RandomIdGenerator().generateTraceId(), + 'spanName', + SpanKind.CLIENT, + {}, + [] + ); + + if (rootDecision === expectedDecision) { + expect(actualResult).toBe(rootResult); + expect(actualResult.decision).toBe(rootDecision); + } else { + expect(actualResult).not.toBe(rootResult); + expect(actualResult.decision).toBe(expectedDecision); + } + + expect(actualResult.attributes).toEqual(rootResult.attributes); + expect(actualResult.traceState).toEqual(rootResult.traceState); +} + +function buildRootSamplingResult(samplingDecision: SamplingDecision): SamplingResult { + const samplingAttr: Attributes = { key: SamplingDecision[samplingDecision] }; + const samplingTraceState: TraceState = createTraceState(); + samplingTraceState.set('key', SamplingDecision[samplingDecision]); + const samplingResult: SamplingResult = { + decision: samplingDecision, + attributes: samplingAttr, + traceState: samplingTraceState, + }; + return samplingResult; +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-span-processing-util.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-span-processing-util.test.ts new file mode 100644 index 0000000..aaeba2d --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-span-processing-util.test.ts @@ -0,0 +1,361 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Attributes, SpanContext, SpanKind } from '@opentelemetry/api'; +import { InstrumentationLibrary } from '@opentelemetry/core'; +import { Resource } from '@opentelemetry/resources'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { + MESSAGINGOPERATIONVALUES_PROCESS, + MESSAGINGOPERATIONVALUES_RECEIVE, + SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_TARGET, + SEMATTRS_MESSAGING_OPERATION, + SEMATTRS_RPC_SYSTEM, +} from '@opentelemetry/semantic-conventions'; +import { expect } from 'expect'; +import { AWS_ATTRIBUTE_KEYS } from '../src/aws-attribute-keys'; +import { AwsSpanProcessingUtil } from '../src/aws-span-processing-util'; + +const DEFAULT_PATH_VALUE: string = '/'; +const UNKNOWN_OPERATION: string = 'UnknownOperation'; +const INTERNAL_OPERATION: string = 'InternalOperation'; + +let attributesMock: Attributes; +let spanDataMock: ReadableSpan; + +describe('AwsSpanProcessingUtilTest', () => { + beforeEach(() => { + attributesMock = {}; + spanDataMock = { + name: 'spanName', + kind: SpanKind.SERVER, + spanContext: () => { + const spanContext: SpanContext = { + traceId: 'traceId', + spanId: 'spanId', + traceFlags: 0, + }; + return spanContext; + }, + startTime: [0, 0], + endTime: [0, 1], + status: { code: 0 }, + attributes: {}, + links: [], + events: [], + duration: [0, 1], + ended: true, + resource: new Resource({}), + instrumentationLibrary: { name: 'mockedLibrary' }, + droppedAttributesCount: 0, + droppedEventsCount: 0, + droppedLinksCount: 0, + }; + (spanDataMock as any).attributes = attributesMock; + }); + + it('testGetIngressOperationValidName', () => { + const validName: string = 'ValidName'; + (spanDataMock as any).name = validName; + (spanDataMock as any).kind = SpanKind.SERVER; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(validName); + }); + + it('testGetIngressOperationWithNotServer', () => { + const validName: string = 'ValidName'; + (spanDataMock as any).name = validName; + (spanDataMock as any).kind = SpanKind.CLIENT; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(INTERNAL_OPERATION); + }); + + it('testGetIngressOperationHttpMethodNameAndNoFallback', () => { + const invalidName: string = 'GET'; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.SERVER; + attributesMock[SEMATTRS_HTTP_METHOD] = invalidName; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(UNKNOWN_OPERATION); + }); + + it('testGetIngressOperationNullNameAndNoFallback', () => { + const invalidName: string | null = null; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.SERVER; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(UNKNOWN_OPERATION); + }); + + it('testGetIngressOperationUnknownNameAndNoFallback', () => { + const invalidName: string = UNKNOWN_OPERATION; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.SERVER; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(UNKNOWN_OPERATION); + }); + + it('testGetIngressOperationInvalidNameAndValidTarget', () => { + const invalidName: string | null = null; + const validTarget: string = '/'; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.SERVER; + attributesMock[SEMATTRS_HTTP_TARGET] = validTarget; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(validTarget); + }); + + it('testGetIngressOperationInvalidNameAndValidTargetAndMethod', () => { + const invalidName: string | null = null; + const validTarget: string = '/'; + const validMethod: string = 'GET'; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.SERVER; + attributesMock[SEMATTRS_HTTP_TARGET] = validTarget; + attributesMock[SEMATTRS_HTTP_METHOD] = validMethod; + const actualOperation: string = AwsSpanProcessingUtil.getIngressOperation(spanDataMock); + expect(actualOperation).toEqual(validMethod + ' ' + validTarget); + }); + + it('testGetEgressOperationUseInternalOperation', () => { + const invalidName: string | null = null; + (spanDataMock as any).name = invalidName; + (spanDataMock as any).kind = SpanKind.CONSUMER; + const actualOperation: string | undefined = AwsSpanProcessingUtil.getEgressOperation(spanDataMock); + expect(actualOperation).toEqual(INTERNAL_OPERATION); + }); + + it('testGetEgressOperationGetLocalOperation', () => { + const operation: string = 'TestOperation'; + attributesMock[AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION] = operation; + (spanDataMock as any).attributes = attributesMock; + (spanDataMock as any).kind = SpanKind.SERVER; + const actualOperation: string | undefined = AwsSpanProcessingUtil.getEgressOperation(spanDataMock); + expect(actualOperation).toEqual(operation); + }); + + it('testExtractAPIPathValueEmptyTarget', () => { + const invalidTarget: string = ''; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(invalidTarget); + expect(pathValue).toEqual(DEFAULT_PATH_VALUE); + }); + + it('testExtractAPIPathValueNullTarget', () => { + const invalidTarget: string | undefined = undefined; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(invalidTarget); + expect(pathValue).toEqual(DEFAULT_PATH_VALUE); + }); + + it('testExtractAPIPathValueNoSlash', () => { + const invalidTarget: string = 'users'; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(invalidTarget); + expect(pathValue).toEqual(DEFAULT_PATH_VALUE); + }); + + it('testExtractAPIPathValueOnlySlash', () => { + const invalidTarget: string = '/'; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(invalidTarget); + expect(pathValue).toEqual(DEFAULT_PATH_VALUE); + }); + + it('testExtractAPIPathValueOnlySlashAtEnd', () => { + const invalidTarget: string = 'users/'; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(invalidTarget); + expect(pathValue).toEqual(DEFAULT_PATH_VALUE); + }); + + it('testExtractAPIPathValidPath', () => { + const validTarget: string = '/users/1/pet?query#fragment'; + const pathValue: string = AwsSpanProcessingUtil.extractAPIPathValue(validTarget); + expect(pathValue).toEqual('/users'); + }); + + it('testIsKeyPresentKeyPresent', () => { + attributesMock[SEMATTRS_HTTP_TARGET] = 'target'; + expect(AwsSpanProcessingUtil.isKeyPresent(spanDataMock, SEMATTRS_HTTP_TARGET)).toBeTruthy(); + }); + + it('testIsKeyPresentKeyAbsent', () => { + expect(AwsSpanProcessingUtil.isKeyPresent(spanDataMock, SEMATTRS_HTTP_TARGET)).toBeFalsy(); + }); + + it('testIsAwsSpanTrue', () => { + attributesMock[SEMATTRS_RPC_SYSTEM] = 'aws-api'; + expect(AwsSpanProcessingUtil.isAwsSDKSpan(spanDataMock)).toBeTruthy(); + }); + + it('testIsAwsSpanFalse', () => { + expect(AwsSpanProcessingUtil.isAwsSDKSpan(spanDataMock)).toBeFalsy(); + }); + + it('testShouldUseInternalOperationFalse', () => { + (spanDataMock as any).kind = SpanKind.SERVER; + expect(AwsSpanProcessingUtil.shouldUseInternalOperation(spanDataMock)).toBeFalsy(); + + const parentSpanContext: SpanContext = createMockSpanContext(); + (parentSpanContext as any).isRemote = false; + + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT as false because parentSpanContext is valid and not remote + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = false; + (spanDataMock as any).kind = SpanKind.CONSUMER; + + expect(AwsSpanProcessingUtil.shouldUseInternalOperation(spanDataMock)).toBeFalsy(); + }); + + it('testShouldGenerateServiceMetricAttributes', () => { + const parentSpanContext: SpanContext = { + traceId: 'traceId', + spanId: 'spanId', + traceFlags: 0, + }; + (parentSpanContext as any).isRemote = false; + + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT as false because parentSpanContext is valid and not remote + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = false; + + (spanDataMock as any).kind = SpanKind.SERVER; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeTruthy(); + + (spanDataMock as any).kind = SpanKind.CONSUMER; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + + (spanDataMock as any).kind = SpanKind.INTERNAL; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + + (spanDataMock as any).kind = SpanKind.PRODUCER; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + + (spanDataMock as any).kind = SpanKind.CLIENT; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT as true because parentSpanContext is remote + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = true; + (spanDataMock as any).kind = SpanKind.PRODUCER; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeTruthy(); + }); + + it('testShouldGenerateDependencyMetricAttributes', () => { + (spanDataMock as any).kind = SpanKind.SERVER; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeFalsy(); + + (spanDataMock as any).kind = SpanKind.INTERNAL; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeFalsy(); + + (spanDataMock as any).kind = SpanKind.CONSUMER; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + + (spanDataMock as any).kind = SpanKind.PRODUCER; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + + (spanDataMock as any).kind = SpanKind.CLIENT; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + + const parentSpanContextMock: SpanContext = createMockSpanContext(); + (parentSpanContextMock as any).isRemote = false; + (spanDataMock as any).kind = SpanKind.CONSUMER; + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT as false because isRemote is false + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = false; + + attributesMock[SEMATTRS_MESSAGING_OPERATION] = MESSAGINGOPERATIONVALUES_PROCESS; + attributesMock[AWS_ATTRIBUTE_KEYS.AWS_CONSUMER_PARENT_SPAN_KIND] = SpanKind[SpanKind.CONSUMER]; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeFalsy(); + + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT as true because parentSpanContextMock is not valid + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = true; + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + }); + + // Divergence from Java/Python - set AWS_IS_LOCAL_ROOT to test isLocalRoot + it('testIsLocalRoot', () => { + // AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT is undefined + expect(AwsSpanProcessingUtil.isLocalRoot(spanDataMock)).toBeTruthy(); + + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = true; + expect(AwsSpanProcessingUtil.isLocalRoot(spanDataMock)).toBeTruthy(); + + spanDataMock.attributes[AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT] = false; + expect(AwsSpanProcessingUtil.isLocalRoot(spanDataMock)).toBeFalsy(); + }); + + it('testIsConsumerProcessSpanFalse', () => { + expect(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).toBeFalsy(); + }); + + it('testIsConsumerProcessSpanTrue', () => { + attributesMock[SEMATTRS_MESSAGING_OPERATION] = MESSAGINGOPERATIONVALUES_PROCESS; + (spanDataMock as any).kind = SpanKind.CONSUMER; + expect(AwsSpanProcessingUtil.isConsumerProcessSpan(spanDataMock)).toBeTruthy(); + }); + + // check that AWS SDK SQS ReceiveMessage consumer spans metrics are suppressed + it('testNoMetricAttributesForSqsConsumerSpanAwsSdk', () => { + const instrumentationLibrary: InstrumentationLibrary = { + name: '@opentelemetry/instrumentation-aws-sdk', + }; + (spanDataMock as any).instrumentationLibrary = instrumentationLibrary; + (spanDataMock as any).kind = SpanKind.CONSUMER; + (spanDataMock as any).name = 'SQS.ReceiveMessage'; + + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeFalsy(); + }); + + // check that SQS ReceiveMessage consumer spans metrics are still generated for other + // instrumentation + it('testMetricAttributesGeneratedForOtherInstrumentationSqsConsumerSpan', () => { + const instrumentationLibrary: InstrumentationLibrary = { + name: 'my-instrumentationy', + }; + (spanDataMock as any).instrumentationLibrary = instrumentationLibrary; + (spanDataMock as any).kind = SpanKind.CONSUMER; + (spanDataMock as any).name = 'Sqs.ReceiveMessage'; + + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeTruthy(); + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + }); + + // check that SQS ReceiveMessage consumer span metrics are suppressed if messaging operation is + // process and not receive + it('testNoMetricAttributesForAwsSdkSqsConsumerProcessSpan', () => { + const instrumentationLibrary: InstrumentationLibrary = { + name: '@opentelemetry/instrumentation-aws-sdk', + }; + (spanDataMock as any).instrumentationLibrary = instrumentationLibrary; + (spanDataMock as any).kind = SpanKind.CONSUMER; + (spanDataMock as any).name = 'Sqs.ReceiveMessage'; + attributesMock[SEMATTRS_MESSAGING_OPERATION] = MESSAGINGOPERATIONVALUES_PROCESS; + + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeFalsy(); + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeFalsy(); + + attributesMock[SEMATTRS_MESSAGING_OPERATION] = MESSAGINGOPERATIONVALUES_RECEIVE; + expect(AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)).toBeTruthy(); + expect(AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)).toBeTruthy(); + }); + + it('testSqlDialectKeywordsOrder', () => { + const keywords: string[] = AwsSpanProcessingUtil.getDialectKeywords(); + let prevKeywordLength: number = Number.MAX_VALUE; + keywords.forEach((keyword: string) => { + const currKeywordLength: number = keyword.length; + expect(prevKeywordLength >= currKeywordLength); + prevKeywordLength = currKeywordLength; + }); + }); + + it('testSqlDialectKeywordsMaxLength', () => { + const keywords: string[] = AwsSpanProcessingUtil.getDialectKeywords(); + keywords.forEach((keyword: string) => { + expect(AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH).toBeGreaterThanOrEqual(keyword.length); + }); + }); +}); + +function createMockSpanContext(): SpanContext { + return { + traceId: 'traceId', + spanId: 'spanId', + traceFlags: 0, + }; +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/sqs-url-parser.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/sqs-url-parser.test.ts new file mode 100644 index 0000000..53f6e29 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/sqs-url-parser.test.ts @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { expect } from 'expect'; +import { SqsUrlParser } from '../src/sqs-url-parser'; + +describe('SqsUrlParserTest', () => { + it('testSqsClientSpanBasicUrls', async () => { + validate('https://sqs.us-east-1.amazonaws.com/123412341234/Q_Name-5', 'Q_Name-5'); + validate('https://sqs.af-south-1.amazonaws.com/999999999999/-_ThisIsValid', '-_ThisIsValid'); + validate('http://sqs.eu-west-3.amazonaws.com/000000000000/FirstQueue', 'FirstQueue'); + validate('sqs.sa-east-1.amazonaws.com/123456781234/SecondQueue', 'SecondQueue'); + }); + + it('testSqsClientSpanLegacyFormatUrls', () => { + validate('https://ap-northeast-2.queue.amazonaws.com/123456789012/MyQueue', 'MyQueue'); + validate('http://cn-northwest-1.queue.amazonaws.com/123456789012/MyQueue', 'MyQueue'); + validate('http://cn-north-1.queue.amazonaws.com/123456789012/MyQueue', 'MyQueue'); + validate('ap-south-1.queue.amazonaws.com/123412341234/MyLongerQueueNameHere', 'MyLongerQueueNameHere'); + validate('https://queue.amazonaws.com/123456789012/MyQueue', 'MyQueue'); + }); + + it('testSqsClientSpanCustomUrls', () => { + validate('http://127.0.0.1:1212/123456789012/MyQueue', 'MyQueue'); + validate('https://127.0.0.1:1212/123412341234/RRR', 'RRR'); + validate('127.0.0.1:1212/123412341234/QQ', 'QQ'); + validate('https://amazon.com/123412341234/BB', 'BB'); + }); + + it('testSqsClientSpanLongUrls', () => { + const queueName: string = 'a'.repeat(80); + validate('http://127.0.0.1:1212/123456789012/' + queueName, queueName); + + const queueNameTooLong: string = 'a'.repeat(81); + validate('http://127.0.0.1:1212/123456789012/' + queueNameTooLong, undefined); + }); + + it('testClientSpanSqsInvalidOrEmptyUrls', () => { + validate(undefined, undefined); + validate('', undefined); + validate(' ', undefined); + validate('/', undefined); + validate('//', undefined); + validate('///', undefined); + validate('//asdf', undefined); + validate('/123412341234/as&df', undefined); + validate('invalidUrl', undefined); + validate('https://www.amazon.com', undefined); + validate('https://sqs.us-east-1.amazonaws.com/123412341234/.', undefined); + validate('https://sqs.us-east-1.amazonaws.com/12/Queue', undefined); + validate('https://sqs.us-east-1.amazonaws.com/A/A', undefined); + validate('https://sqs.us-east-1.amazonaws.com/123412341234/A/ThisShouldNotBeHere', undefined); + }); +}); + +function validate(url: string | undefined, expectedName: string | undefined): void { + expect(SqsUrlParser.getQueueName(url)).toEqual(expectedName); +} diff --git a/eslint.config.js b/eslint.config.js index 629079d..c54e092 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,8 +20,8 @@ module.exports = { "no-shadow": "off", "node/no-deprecated-api": ["warn"], "header/header": ["error", "line", [ - "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.", - "SPDX-License-Identifier: Apache-2.0" + " Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.", + " SPDX-License-Identifier: Apache-2.0" ]] }, overrides: [ diff --git a/package-lock.json b/package-lock.json index 17400e2..350898d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@types/mocha": "7.0.2", "@types/node": "18.6.5", "@types/sinon": "10.0.18", + "expect": "29.2.0", "mocha": "7.2.0", "nyc": "15.1.0", "rimraf": "5.0.5", @@ -802,6 +803,19 @@ "node": ">=8" } }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -815,6 +829,24 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -4110,6 +4142,33 @@ "@types/node": "*" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4226,6 +4285,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -4235,6 +4301,23 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz", @@ -6331,6 +6414,16 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7347,6 +7440,23 @@ "dev": true, "license": "ISC" }, + "node_modules/expect": { + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.2.0.tgz", + "integrity": "sha512-03ClF3GWwUqd9Grgkr9ZSdaCJGMRA69PQ8jT7o+Bx100VlGiAFf9/8oIm9Qve7ZVJhuJxFftqFhviZJRxxNfvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.2.0", + "jest-get-type": "^29.2.0", + "jest-matcher-utils": "^29.2.0", + "jest-message-util": "^29.2.0", + "jest-util": "^29.2.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", @@ -9811,6 +9921,187 @@ "node": "*" } }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16655,6 +16946,29 @@ "node": ">=8" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", diff --git a/package.json b/package.json index a0720bc..2786cb9 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "markdownlint-cli2": "0.13.0", "minimatch": "^9.0.3", "prettier": "2.8.8", - "semver": "^7.6.0", "process": "0.11.10", + "semver": "^7.6.0", "util": "0.12.5" }, "changelog": { diff --git a/sample-applications/simple-express-server/README.md b/sample-applications/simple-express-server/README.md index b3297a0..b3e46e8 100644 --- a/sample-applications/simple-express-server/README.md +++ b/sample-applications/simple-express-server/README.md @@ -1,22 +1,28 @@ ## Sample Applications ### Sample App Setup + The Sample App is an ExpressJS Server that listens on `http://localhost:8080` by default You may change the port number from `8080` using: -``` + +```shell export SAMPLE_APP_PORT=8082 ``` #### Without Instrumentation + To startup the Express Sample App without instrumentation -``` + +```shell npm install node sample-app-express-server.js ``` #### With OTel Instrumentation + To startup the Express Sample App with OTel auto-instrumentation -``` + +```shell npm install npm install --save @opentelemetry/api npm install --save @opentelemetry/auto-instrumentations-node @@ -24,17 +30,22 @@ node --require '@opentelemetry/auto-instrumentations-node/register' sample-app-e ``` #### With ADOT Instrumentation + To startup the Express Sample App with local AWS Distro OTel auto-instrumentation, go to the `root` directory and run the following command to install the sample app with ADOT JS instrumentation: -``` + +```shell ./scripts/install_and_link_simple_express_app_with_instrumentation.sh ``` + Then start the app in this directory with: -``` + +```shell node --require '@aws/aws-distro-opentelemetry-node-autoinstrumentation/register' sample-app-express-server.js ``` ### Ping Sample App -``` + +```shell curl http://localhost:8080/rolldice curl http://localhost:8080/http curl http://localhost:8080/aws-sdk diff --git a/scripts/README.md b/scripts/README.md index 4c6b2b4..ae3fb23 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,4 +1,5 @@ # Scripts ## Useful Links -- [Setting Up Node.js on an Amazon EC2 Instance](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html) \ No newline at end of file + +- [Setting Up Node.js on an Amazon EC2 Instance](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html)