From 182e9ee8fd5bc75d1214fed6dcf2974f52b34eb2 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 15 Apr 2020 09:49:15 -0700 Subject: [PATCH 1/4] feat: resource auto-detection (#899) * feat: add resource detection from environment variable * feat: add AWS EC2 resource detection * feat: add resource detection for GCP * refactor: create Labels interface * feat: add Resource.detect() method * refactor: improve GcpDetector * feat: make detection platform aware; extract to detectResources() fn * chore: cleanup * chore: prefix private methods with _ * fix: import URL for Node 8 * chore: prefix private constants with _ * chore: do not export resources * chore: abort request on timeout * refactor: export instances of detectors instead of static classes * refactor: formalize Detector interface * refactor: rename Labels -> ResourceLabels * feat: gracefully handle detectors that throw * fix: do not resume in an end event * docs: document interfaces and idenitity document url * fix: nock flakiness in resource tests Co-authored-by: Mayur Kale --- packages/opentelemetry-resources/package.json | 10 +- .../opentelemetry-resources/src/Resource.ts | 3 +- packages/opentelemetry-resources/src/index.ts | 4 +- .../src/platform/browser/detect-resources.ts | 26 +++ .../src/platform/browser/index.ts | 17 ++ .../src/platform/index.ts | 20 +++ .../src/platform/node/detect-resources.ts | 41 +++++ .../platform/node/detectors/AwsEc2Detector.ts | 101 +++++++++++ .../platform/node/detectors/EnvDetector.ts | 138 +++++++++++++++ .../platform/node/detectors/GcpDetector.ts | 106 ++++++++++++ .../src/platform/node/detectors/index.ts | 19 ++ .../src/platform/node/index.ts | 17 ++ packages/opentelemetry-resources/src/types.ts | 30 ++++ .../test/Resource.test.ts | 4 +- .../test/detect-resources.test.ts | 161 +++++++++++++++++ .../test/detectors/AwsEc2Detector.test.ts | 82 +++++++++ .../test/detectors/EnvDetector.test.ts | 52 ++++++ .../test/detectors/GcpDetector.test.ts | 162 ++++++++++++++++++ .../test/util/resource-assertions.ts | 9 + 19 files changed, 997 insertions(+), 5 deletions(-) create mode 100644 packages/opentelemetry-resources/src/platform/browser/detect-resources.ts create mode 100644 packages/opentelemetry-resources/src/platform/browser/index.ts create mode 100644 packages/opentelemetry-resources/src/platform/index.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/detect-resources.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/detectors/AwsEc2Detector.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/detectors/GcpDetector.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/detectors/index.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/index.ts create mode 100644 packages/opentelemetry-resources/src/types.ts create mode 100644 packages/opentelemetry-resources/test/detect-resources.test.ts create mode 100644 packages/opentelemetry-resources/test/detectors/AwsEc2Detector.test.ts create mode 100644 packages/opentelemetry-resources/test/detectors/EnvDetector.test.ts create mode 100644 packages/opentelemetry-resources/test/detectors/GcpDetector.test.ts diff --git a/packages/opentelemetry-resources/package.json b/packages/opentelemetry-resources/package.json index 8e9d8a852c..532fd10150 100644 --- a/packages/opentelemetry-resources/package.json +++ b/packages/opentelemetry-resources/package.json @@ -3,6 +3,10 @@ "version": "0.6.1", "description": "OpenTelemetry SDK resources", "main": "build/src/index.js", + "browser": { + "./src/platform/index.ts": "./src/platform/browser/index.ts", + "./build/src/platform/index.js": "./build/src/platform/browser/index.js" + }, "types": "build/src/index.d.ts", "repository": "open-telemetry/opentelemetry-js", "scripts": { @@ -42,11 +46,14 @@ "devDependencies": { "@types/mocha": "^7.0.0", "@types/node": "^12.6.9", + "@types/sinon": "^7.0.13", "codecov": "^3.6.1", "gts": "^1.1.0", "mocha": "^6.2.0", + "nock": "^12.0.2", "nyc": "^15.0.0", "rimraf": "^3.0.0", + "sinon": "^7.5.0", "ts-mocha": "^6.0.0", "ts-node": "^8.6.2", "tslint-consistent-codestyle": "^1.16.0", @@ -55,6 +62,7 @@ }, "dependencies": { "@opentelemetry/api": "^0.6.1", - "@opentelemetry/base": "^0.6.1" + "@opentelemetry/base": "^0.6.1", + "gcp-metadata": "^3.5.0" } } diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index 471e7a943a..0a76dad968 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -16,6 +16,7 @@ import { SDK_INFO } from '@opentelemetry/base'; import { TELEMETRY_SDK_RESOURCE } from './constants'; +import { ResourceLabels } from './types'; /** * A Resource describes the entity for which a signals (metrics or trace) are @@ -48,7 +49,7 @@ export class Resource { * about the entity as numbers, strings or booleans * TODO: Consider to add check/validation on labels. */ - readonly labels: { [key: string]: number | string | boolean } + readonly labels: ResourceLabels ) {} /** diff --git a/packages/opentelemetry-resources/src/index.ts b/packages/opentelemetry-resources/src/index.ts index c75f0f7daf..13673409aa 100644 --- a/packages/opentelemetry-resources/src/index.ts +++ b/packages/opentelemetry-resources/src/index.ts @@ -14,5 +14,7 @@ * limitations under the License. */ -export { Resource } from './Resource'; +export * from './Resource'; +export * from './platform'; export * from './constants'; +export * from './types'; diff --git a/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts new file mode 100644 index 0000000000..43db76639a --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/browser/detect-resources.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Resource } from '../../Resource'; + +/** + * Detects resources for the browser platform, which is currently only the + * telemetry SDK resource. More could be added in the future. This method + * is async to match the signature of corresponding method for node. + */ +export const detectResources = async (): Promise => { + return Resource.createTelemetrySDKResource(); +}; diff --git a/packages/opentelemetry-resources/src/platform/browser/index.ts b/packages/opentelemetry-resources/src/platform/browser/index.ts new file mode 100644 index 0000000000..560cdcf821 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/browser/index.ts @@ -0,0 +1,17 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './detect-resources'; diff --git a/packages/opentelemetry-resources/src/platform/index.ts b/packages/opentelemetry-resources/src/platform/index.ts new file mode 100644 index 0000000000..6c6d039c91 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/index.ts @@ -0,0 +1,20 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Use the node platform by default. The "browser" field of package.json is used +// to override this file to use `./browser/index.ts` when packaged with +// webpack, Rollup, etc. +export * from './node'; diff --git a/packages/opentelemetry-resources/src/platform/node/detect-resources.ts b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts new file mode 100644 index 0000000000..2351250582 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/detect-resources.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Resource } from '../../Resource'; +import { envDetector, awsEc2Detector, gcpDetector } from './detectors'; +import { Detector } from '../../types'; + +const DETECTORS: Array = [envDetector, awsEc2Detector, gcpDetector]; + +/** + * Runs all resource detectors and returns the results merged into a single + * Resource. + */ +export const detectResources = async (): Promise => { + const resources: Array = await Promise.all( + DETECTORS.map(d => { + try { + return d.detect(); + } catch { + return Resource.empty(); + } + }) + ); + return resources.reduce( + (acc, resource) => acc.merge(resource), + Resource.createTelemetrySDKResource() + ); +}; diff --git a/packages/opentelemetry-resources/src/platform/node/detectors/AwsEc2Detector.ts b/packages/opentelemetry-resources/src/platform/node/detectors/AwsEc2Detector.ts new file mode 100644 index 0000000000..d3317b9c09 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/detectors/AwsEc2Detector.ts @@ -0,0 +1,101 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as http from 'http'; +import { Resource } from '../../../Resource'; +import { CLOUD_RESOURCE, HOST_RESOURCE } from '../../../constants'; +import { Detector } from '../../../types'; + +/** + * The AwsEc2Detector can be used to detect if a process is running in AWS EC2 + * and return a {@link Resource} populated with metadata about the EC2 + * instance. Returns an empty Resource if detection fails. + */ +class AwsEc2Detector implements Detector { + /** + * See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html + * for documentation about the AWS instance identity document endpoint. + */ + readonly AWS_INSTANCE_IDENTITY_DOCUMENT_URI = + 'http://169.254.169.254/latest/dynamic/instance-identity/document'; + + /** + * Attempts to connect and obtain an AWS instance Identity document. If the + * connection is succesful it returns a promise containing a {@link Resource} + * populated with instance metadata as labels. Returns a promise containing an + * empty {@link Resource} if the connection or parsing of the identity + * document fails. + */ + async detect(): Promise { + try { + const { + accountId, + instanceId, + region, + } = await this._awsMetadataAccessor(); + return new Resource({ + [CLOUD_RESOURCE.PROVIDER]: 'aws', + [CLOUD_RESOURCE.ACCOUNT_ID]: accountId, + [CLOUD_RESOURCE.REGION]: region, + [HOST_RESOURCE.ID]: instanceId, + }); + } catch { + return Resource.empty(); + } + } + + /** + * Establishes an HTTP connection to AWS instance identity document url. + * If the application is running on an EC2 instance, we should be able + * to get back a valid JSON document. Parses that document and stores + * the identity properties in a local map. + */ + private async _awsMetadataAccessor(): Promise { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + req.abort(); + reject(new Error('EC2 metadata api request timed out.')); + }, 1000); + + const req = http.get(this.AWS_INSTANCE_IDENTITY_DOCUMENT_URI, res => { + clearTimeout(timeoutId); + const { statusCode } = res; + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', chunk => (rawData += chunk)); + res.on('end', () => { + if (statusCode && statusCode >= 200 && statusCode < 300) { + try { + resolve(JSON.parse(rawData)); + } catch (e) { + reject(e); + } + } else { + reject( + new Error('Failed to load page, status code: ' + statusCode) + ); + } + }); + }); + req.on('error', err => { + clearTimeout(timeoutId); + reject(err); + }); + }); + } +} + +export const awsEc2Detector = new AwsEc2Detector(); diff --git a/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts new file mode 100644 index 0000000000..52dd160778 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts @@ -0,0 +1,138 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Resource } from '../../../Resource'; +import { Detector, ResourceLabels } from '../../../types'; + +/** + * EnvDetector can be used to detect the presence of and create a Resource + * from the OTEL_RESOURCE_LABELS environment variable. + */ +class EnvDetector implements Detector { + // Type, label keys, and label values should not exceed 256 characters. + private readonly _MAX_LENGTH = 255; + + // OTEL_RESOURCE_LABELS is a comma-separated list of labels. + private readonly _COMMA_SEPARATOR = ','; + + // OTEL_RESOURCE_LABELS contains key value pair separated by '='. + private readonly _LABEL_KEY_VALUE_SPLITTER = '='; + + private readonly _ERROR_MESSAGE_INVALID_CHARS = + 'should be a ASCII string with a length greater than 0 and not exceed ' + + this._MAX_LENGTH + + ' characters.'; + + private readonly _ERROR_MESSAGE_INVALID_VALUE = + 'should be a ASCII string with a length not exceed ' + + this._MAX_LENGTH + + ' characters.'; + + /** + * Returns a {@link Resource} populated with labels from the + * OTEL_RESOURCE_LABELS environment variable. Note this is an async function + * to conform to the Detector interface. + */ + async detect(): Promise { + try { + const labelString = process.env.OTEL_RESOURCE_LABELS; + if (!labelString) return Resource.empty(); + const labels = this._parseResourceLabels( + process.env.OTEL_RESOURCE_LABELS + ); + return new Resource(labels); + } catch { + return Resource.empty(); + } + } + + /** + * Creates a label map from the OTEL_RESOURCE_LABELS environment variable. + * + * OTEL_RESOURCE_LABELS: A comma-separated list of labels describing the + * source in more detail, e.g. “key1=val1,key2=val2”. Domain names and paths + * are accepted as label keys. Values may be quoted or unquoted in general. If + * a value contains whitespaces, =, or " characters, it must always be quoted. + * + * @param rawEnvLabels The resource labels as a comma-seperated list + * of key/value pairs. + * @returns The sanitized resource labels. + */ + private _parseResourceLabels(rawEnvLabels?: string): ResourceLabels { + if (!rawEnvLabels) return {}; + + const labels: ResourceLabels = {}; + const rawLabels: string[] = rawEnvLabels.split(this._COMMA_SEPARATOR, -1); + for (const rawLabel of rawLabels) { + const keyValuePair: string[] = rawLabel.split( + this._LABEL_KEY_VALUE_SPLITTER, + -1 + ); + if (keyValuePair.length !== 2) { + continue; + } + let [key, value] = keyValuePair; + // Leading and trailing whitespaces are trimmed. + key = key.trim(); + value = value + .trim() + .split('^"|"$') + .join(''); + if (!this._isValidAndNotEmpty(key)) { + throw new Error(`Label key ${this._ERROR_MESSAGE_INVALID_CHARS}`); + } + if (!this._isValid(value)) { + throw new Error(`Label value ${this._ERROR_MESSAGE_INVALID_VALUE}`); + } + labels[key] = value; + } + return labels; + } + + /** + * Determines whether the given String is a valid printable ASCII string with + * a length not exceed _MAX_LENGTH characters. + * + * @param str The String to be validated. + * @returns Whether the String is valid. + */ + private _isValid(name: string): boolean { + return name.length <= this._MAX_LENGTH && this._isPrintableString(name); + } + + private _isPrintableString(str: string): boolean { + for (let i = 0; i < str.length; i++) { + const ch: string = str.charAt(i); + if (ch <= ' ' || ch >= '~') { + return false; + } + } + return true; + } + + /** + * Determines whether the given String is a valid printable ASCII string with + * a length greater than 0 and not exceed _MAX_LENGTH characters. + * + * @param str The String to be validated. + * @returns Whether the String is valid and not empty. + */ + private _isValidAndNotEmpty(str: string): boolean { + return str.length > 0 && this._isValid(str); + } +} + +export const envDetector = new EnvDetector(); diff --git a/packages/opentelemetry-resources/src/platform/node/detectors/GcpDetector.ts b/packages/opentelemetry-resources/src/platform/node/detectors/GcpDetector.ts new file mode 100644 index 0000000000..ab1136fe8b --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/detectors/GcpDetector.ts @@ -0,0 +1,106 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as os from 'os'; +import * as gcpMetadata from 'gcp-metadata'; +import { Resource } from '../../../Resource'; +import { Detector, ResourceLabels } from '../../../types'; +import { + CLOUD_RESOURCE, + HOST_RESOURCE, + K8S_RESOURCE, + CONTAINER_RESOURCE, +} from '../../../constants'; + +/** + * The GcpDetector can be used to detect if a process is running in the Google + * Cloud Platofrm and return a {@link Resource} populated with metadata about + * the instance. Returns an empty Resource if detection fails. + */ +class GcpDetector implements Detector { + async detect(): Promise { + if (!(await gcpMetadata.isAvailable())) return Resource.empty(); + + const [projectId, instanceId, zoneId, clusterName] = await Promise.all([ + this._getProjectId(), + this._getInstanceId(), + this._getZone(), + this._getClusterName(), + ]); + + const labels: ResourceLabels = {}; + labels[CLOUD_RESOURCE.ACCOUNT_ID] = projectId; + labels[HOST_RESOURCE.ID] = instanceId; + labels[CLOUD_RESOURCE.ZONE] = zoneId; + labels[CLOUD_RESOURCE.PROVIDER] = 'gcp'; + + if (process.env.KUBERNETES_SERVICE_HOST) + this._addK8sLabels(labels, clusterName); + + return new Resource(labels); + } + + /** Add resource labels for K8s */ + private _addK8sLabels(labels: ResourceLabels, clusterName: string): void { + labels[K8S_RESOURCE.CLUSTER_NAME] = clusterName; + labels[K8S_RESOURCE.NAMESPACE_NAME] = process.env.NAMESPACE || ''; + labels[K8S_RESOURCE.POD_NAME] = process.env.HOSTNAME || os.hostname(); + labels[CONTAINER_RESOURCE.NAME] = process.env.CONTAINER_NAME || ''; + } + + /** Gets project id from GCP project metadata. */ + private async _getProjectId(): Promise { + try { + return await gcpMetadata.project('project-id'); + } catch { + return ''; + } + } + + /** Gets instance id from GCP instance metadata. */ + private async _getInstanceId(): Promise { + try { + const id = await gcpMetadata.instance('id'); + return id.toString(); + } catch { + return ''; + } + } + + /** Gets zone from GCP instance metadata. */ + private async _getZone(): Promise { + try { + const zoneId = await gcpMetadata.instance('zone'); + if (zoneId) { + return zoneId.split('/').pop(); + } + return ''; + } catch { + return ''; + } + } + + /** Gets cluster name from GCP instance metadata. */ + private async _getClusterName(): Promise { + try { + return await gcpMetadata.instance('attributes/cluster-name'); + } catch { + return ''; + } + } +} + +export const gcpDetector = new GcpDetector(); diff --git a/packages/opentelemetry-resources/src/platform/node/detectors/index.ts b/packages/opentelemetry-resources/src/platform/node/detectors/index.ts new file mode 100644 index 0000000000..79597f3f1c --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/detectors/index.ts @@ -0,0 +1,19 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { awsEc2Detector } from './AwsEc2Detector'; +export { envDetector } from './EnvDetector'; +export { gcpDetector } from './GcpDetector'; diff --git a/packages/opentelemetry-resources/src/platform/node/index.ts b/packages/opentelemetry-resources/src/platform/node/index.ts new file mode 100644 index 0000000000..560cdcf821 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/index.ts @@ -0,0 +1,17 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './detect-resources'; diff --git a/packages/opentelemetry-resources/src/types.ts b/packages/opentelemetry-resources/src/types.ts new file mode 100644 index 0000000000..a9705350a5 --- /dev/null +++ b/packages/opentelemetry-resources/src/types.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Resource } from './Resource'; + +/** Interface for Resource labels */ +export interface ResourceLabels { + [key: string]: number | string | boolean; +} + +/** + * Interface for a Resource Detector. In order to detect resources in parallel + * a detector returns a Promise containing a Resource. + */ +export interface Detector { + detect(): Promise; +} diff --git a/packages/opentelemetry-resources/test/Resource.test.ts b/packages/opentelemetry-resources/test/Resource.test.ts index 4dc63a9995..2623094817 100644 --- a/packages/opentelemetry-resources/test/Resource.test.ts +++ b/packages/opentelemetry-resources/test/Resource.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { SDK_INFO } from '@opentelemetry/base'; import * as assert from 'assert'; -import { Resource } from '../src/Resource'; +import { SDK_INFO } from '@opentelemetry/base'; +import { Resource } from '../src'; import { assertTelemetrySDKResource } from './util/resource-assertions'; describe('Resource', () => { diff --git a/packages/opentelemetry-resources/test/detect-resources.test.ts b/packages/opentelemetry-resources/test/detect-resources.test.ts new file mode 100644 index 0000000000..24efdf7638 --- /dev/null +++ b/packages/opentelemetry-resources/test/detect-resources.test.ts @@ -0,0 +1,161 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as nock from 'nock'; +import * as sinon from 'sinon'; +import { URL } from 'url'; +import { Resource, detectResources } from '../src'; +import { awsEc2Detector } from '../src/platform/node/detectors'; +import { + assertServiceResource, + assertCloudResource, + assertHostResource, +} from './util/resource-assertions'; +import { + BASE_PATH, + HEADER_NAME, + HEADER_VALUE, + HOST_ADDRESS, + SECONDARY_HOST_ADDRESS, + resetIsAvailableCache, +} from 'gcp-metadata'; + +const HEADERS = { + [HEADER_NAME.toLowerCase()]: HEADER_VALUE, +}; +const INSTANCE_PATH = BASE_PATH + '/instance'; +const INSTANCE_ID_PATH = BASE_PATH + '/instance/id'; +const PROJECT_ID_PATH = BASE_PATH + '/project/project-id'; +const ZONE_PATH = BASE_PATH + '/instance/zone'; +const CLUSTER_NAME_PATH = BASE_PATH + '/instance/attributes/cluster-name'; + +const { origin: AWS_HOST, pathname: AWS_PATH } = new URL( + awsEc2Detector.AWS_INSTANCE_IDENTITY_DOCUMENT_URI +); + +const mockedAwsResponse = { + instanceId: 'my-instance-id', + accountId: 'my-account-id', + region: 'my-region', +}; + +describe('detectResources', async () => { + beforeEach(() => { + nock.disableNetConnect(); + process.env.OTEL_RESOURCE_LABELS = + 'service.instance.id=627cc493,service.name=my-service,service.namespace=default,service.version=0.0.1'; + }); + + afterEach(() => { + nock.cleanAll(); + nock.enableNetConnect(); + delete process.env.OTEL_RESOURCE_LABELS; + }); + + describe('in GCP environment', () => { + after(() => { + resetIsAvailableCache(); + }); + + it('returns a merged resource', async () => { + const gcpScope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 452003179927758, HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(200, () => 'project/zone/my-zone', HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(404); + const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const awsScope = nock(AWS_HOST) + .get(AWS_PATH) + .replyWithError({ code: 'ENOTFOUND' }); + const resource: Resource = await detectResources(); + awsScope.done(); + gcpSecondaryScope.done(); + gcpScope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertHostResource(resource, { id: '452003179927758' }); + assertServiceResource(resource, { + instanceId: '627cc493', + name: 'my-service', + namespace: 'default', + version: '0.0.1', + }); + }); + }); + + describe('in AWS environment', () => { + it('returns a merged resource', async () => { + const gcpScope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .replyWithError({ + code: 'ENOTFOUND', + }); + const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .replyWithError({ + code: 'ENOTFOUND', + }); + const awsScope = nock(AWS_HOST) + .get(AWS_PATH) + .reply(200, () => mockedAwsResponse); + const resource: Resource = await detectResources(); + gcpSecondaryScope.done(); + gcpScope.done(); + awsScope.done(); + + assertCloudResource(resource, { + provider: 'aws', + accountId: 'my-account-id', + region: 'my-region', + }); + assertHostResource(resource, { id: 'my-instance-id' }); + assertServiceResource(resource, { + instanceId: '627cc493', + name: 'my-service', + namespace: 'default', + version: '0.0.1', + }); + }); + }); + + describe('with a buggy detector', () => { + it('returns a merged resource', async () => { + const stub = sinon.stub(awsEc2Detector, 'detect').throws(); + const resource: Resource = await detectResources(); + + assertServiceResource(resource, { + instanceId: '627cc493', + name: 'my-service', + namespace: 'default', + version: '0.0.1', + }); + + stub.restore(); + }); + }); +}); diff --git a/packages/opentelemetry-resources/test/detectors/AwsEc2Detector.test.ts b/packages/opentelemetry-resources/test/detectors/AwsEc2Detector.test.ts new file mode 100644 index 0000000000..6777d472fe --- /dev/null +++ b/packages/opentelemetry-resources/test/detectors/AwsEc2Detector.test.ts @@ -0,0 +1,82 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as nock from 'nock'; +import * as assert from 'assert'; +import { URL } from 'url'; +import { Resource } from '../../src'; +import { awsEc2Detector } from '../../src/platform/node/detectors/AwsEc2Detector'; +import { + assertCloudResource, + assertHostResource, + assertEmptyResource, +} from '../util/resource-assertions'; + +const { origin: AWS_HOST, pathname: AWS_PATH } = new URL( + awsEc2Detector.AWS_INSTANCE_IDENTITY_DOCUMENT_URI +); + +const mockedAwsResponse = { + instanceId: 'my-instance-id', + accountId: 'my-account-id', + region: 'my-region', +}; + +describe('awsEc2Detector', () => { + before(() => { + nock.disableNetConnect(); + nock.cleanAll(); + }); + + after(() => { + nock.enableNetConnect(); + }); + + describe('with successful request', () => { + it('should return aws_ec2_instance resource', async () => { + const scope = nock(AWS_HOST) + .get(AWS_PATH) + .reply(200, () => mockedAwsResponse); + const resource: Resource = await awsEc2Detector.detect(); + scope.done(); + + assert.ok(resource); + assertCloudResource(resource, { + provider: 'aws', + accountId: 'my-account-id', + region: 'my-region', + }); + assertHostResource(resource, { + id: 'my-instance-id', + }); + }); + }); + + describe('with failing request', () => { + it('should return empty resource', async () => { + const scope = nock(AWS_HOST) + .get(AWS_PATH) + .replyWithError({ + code: 'ENOTFOUND', + }); + const resource: Resource = await awsEc2Detector.detect(); + scope.done(); + + assert.ok(resource); + assertEmptyResource(resource); + }); + }); +}); diff --git a/packages/opentelemetry-resources/test/detectors/EnvDetector.test.ts b/packages/opentelemetry-resources/test/detectors/EnvDetector.test.ts new file mode 100644 index 0000000000..b8dfe508eb --- /dev/null +++ b/packages/opentelemetry-resources/test/detectors/EnvDetector.test.ts @@ -0,0 +1,52 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Resource } from '../../src/Resource'; +import { envDetector } from '../../src/platform/node/detectors/EnvDetector'; +import { + assertK8sResource, + assertEmptyResource, +} from '../util/resource-assertions'; +import { K8S_RESOURCE } from '../../src'; + +describe('envDetector()', () => { + describe('with valid env', () => { + before(() => { + process.env.OTEL_RESOURCE_LABELS = + 'k8s.pod.name="pod-xyz-123",k8s.cluster.name="c1",k8s.namespace.name="default"'; + }); + + after(() => { + delete process.env.OTEL_RESOURCE_LABELS; + }); + + it('should return resource information from environment variable', async () => { + const resource: Resource = await envDetector.detect(); + assertK8sResource(resource, { + [K8S_RESOURCE.POD_NAME]: 'pod-xyz-123', + [K8S_RESOURCE.CLUSTER_NAME]: 'c1', + [K8S_RESOURCE.NAMESPACE_NAME]: 'default', + }); + }); + }); + + describe('with empty env', () => { + it('should return empty resource', async () => { + const resource: Resource = await envDetector.detect(); + assertEmptyResource(resource); + }); + }); +}); diff --git a/packages/opentelemetry-resources/test/detectors/GcpDetector.test.ts b/packages/opentelemetry-resources/test/detectors/GcpDetector.test.ts new file mode 100644 index 0000000000..271f8f9c89 --- /dev/null +++ b/packages/opentelemetry-resources/test/detectors/GcpDetector.test.ts @@ -0,0 +1,162 @@ +/*! + * Copyright 2020, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BASE_PATH, + HEADER_NAME, + HEADER_VALUE, + HOST_ADDRESS, + SECONDARY_HOST_ADDRESS, + resetIsAvailableCache, +} from 'gcp-metadata'; +import * as nock from 'nock'; +import { Resource } from '../../src'; +import { gcpDetector } from '../../src/platform/node/detectors'; +import { + assertCloudResource, + assertHostResource, + assertK8sResource, + assertContainerResource, + assertEmptyResource, +} from '../util/resource-assertions'; + +const HEADERS = { + [HEADER_NAME.toLowerCase()]: HEADER_VALUE, +}; +const INSTANCE_PATH = BASE_PATH + '/instance'; +const INSTANCE_ID_PATH = BASE_PATH + '/instance/id'; +const PROJECT_ID_PATH = BASE_PATH + '/project/project-id'; +const ZONE_PATH = BASE_PATH + '/instance/zone'; +const CLUSTER_NAME_PATH = BASE_PATH + '/instance/attributes/cluster-name'; + +describe('gcpDetector', () => { + describe('.detect', () => { + before(() => { + nock.disableNetConnect(); + }); + + after(() => { + nock.enableNetConnect(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; + }); + + beforeEach(() => { + resetIsAvailableCache(); + nock.cleanAll(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; + }); + + it('should return resource with GCP metadata', async () => { + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 4520031799277581759, HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(200, () => 'project/zone/my-zone', HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(404); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource: Resource = await gcpDetector.detect(); + secondaryScope.done(); + scope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertHostResource(resource, { id: '4520031799277582000' }); + }); + + it('should populate K8s labels resource when KUBERNETES_SERVICE_HOST is set', async () => { + process.env.KUBERNETES_SERVICE_HOST = 'my-host'; + process.env.NAMESPACE = 'my-namespace'; + process.env.HOSTNAME = 'my-hostname'; + process.env.CONTAINER_NAME = 'my-container-name'; + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 4520031799277581759, HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(200, () => 'my-cluster', HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(200, () => 'project/zone/my-zone', HEADERS); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect(); + secondaryScope.done(); + scope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertK8sResource(resource, { + clusterName: 'my-cluster', + podName: 'my-hostname', + namespaceName: 'my-namespace', + }); + assertContainerResource(resource, { name: 'my-container-name' }); + }); + + it('should return resource and empty data for non-available metadata attributes', async () => { + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(413) + .get(INSTANCE_ID_PATH) + .reply(400, undefined, HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(413); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect(); + secondaryScope.done(); + scope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: '', + }); + }); + + it('returns empty resource if not detected', async () => { + const resource = await gcpDetector.detect(); + assertEmptyResource(resource); + }); + }); +}); diff --git a/packages/opentelemetry-resources/test/util/resource-assertions.ts b/packages/opentelemetry-resources/test/util/resource-assertions.ts index 0401a8dd4f..02332191a1 100644 --- a/packages/opentelemetry-resources/test/util/resource-assertions.ts +++ b/packages/opentelemetry-resources/test/util/resource-assertions.ts @@ -251,6 +251,15 @@ export const assertServiceResource = ( ); }; +/** + * Test utility method to validate an empty resource + * + * @param resource the Resource to validate + */ +export const assertEmptyResource = (resource: Resource) => { + assert.strictEqual(Object.keys(resource.labels).length, 0); +}; + const assertHasOneLabel = ( constants: { [key: string]: string }, resource: Resource From 7412d9b5181a22b308ab42355f4c78e6dd4bf481 Mon Sep 17 00:00:00 2001 From: Valentin Marchaud Date: Wed, 15 Apr 2020 19:00:51 +0200 Subject: [PATCH 2/4] fix(plugin-express): fix double span end #908 (#910) Co-authored-by: Daniel Dyla --- .../opentelemetry-plugin-express/README.md | 4 ++ .../src/express.ts | 45 +++++++++++-------- .../opentelemetry-plugin-express/src/utils.ts | 27 +---------- .../src/version.ts | 2 +- .../test/express.test.ts | 2 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/packages/opentelemetry-plugin-express/README.md b/packages/opentelemetry-plugin-express/README.md index fd693a45c3..0d82feab40 100644 --- a/packages/opentelemetry-plugin-express/README.md +++ b/packages/opentelemetry-plugin-express/README.md @@ -45,6 +45,10 @@ const provider = new NodeTracerProvider(); See [examples/express](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/express) for a short example. +### Caveats + +Because of the way express works, it's hard to correctly compute the time taken by asynchronous middlewares and request handlers. For this reason, the time you'll see reported for asynchronous middlewares and request handlers will only represent the synchronous execution time, and **not** any asynchronous work. + ### Express Plugin Options Express plugin has few options available to choose from. You can set the following: diff --git a/packages/opentelemetry-plugin-express/src/express.ts b/packages/opentelemetry-plugin-express/src/express.ts index b519ad0cbc..8ccee32645 100644 --- a/packages/opentelemetry-plugin-express/src/express.ts +++ b/packages/opentelemetry-plugin-express/src/express.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BasePlugin } from '@opentelemetry/core'; +import { BasePlugin, hrTime } from '@opentelemetry/core'; import { Attributes } from '@opentelemetry/api'; import * as express from 'express'; import * as core from 'express-serve-static-core'; @@ -30,12 +30,7 @@ import { ExpressPluginConfig, ExpressLayerType, } from './types'; -import { - getLayerMetadata, - storeLayerPath, - patchEnd, - isLayerIgnored, -} from './utils'; +import { getLayerMetadata, storeLayerPath, isLayerIgnored } from './utils'; import { VERSION } from './version'; /** @@ -190,26 +185,40 @@ export class ExpressPlugin extends BasePlugin { const span = plugin._tracer.startSpan(metadata.name, { attributes: Object.assign(attributes, metadata.attributes), }); + const startTime = hrTime(); + let spanHasEnded: boolean = false; + // If we found anything that isnt a middleware, there no point of measuring + // stheir time ince they dont have callback. + if ( + metadata.attributes[AttributeNames.EXPRESS_TYPE] !== + ExpressLayerType.MIDDLEWARE + ) { + span.end(startTime); + spanHasEnded = true; + } // verify we have a callback - let callbackIdx = Array.from(arguments).findIndex( - arg => typeof arg === 'function' - ); - let callbackHasBeenCalled = false; + const args = Array.from(arguments); + const callbackIdx = args.findIndex(arg => typeof arg === 'function'); if (callbackIdx >= 0) { arguments[callbackIdx] = function() { - callbackHasBeenCalled = true; + if (spanHasEnded === false) { + span.end(); + spanHasEnded = true; + } if (!(req.route && arguments[0] instanceof Error)) { (req[_LAYERS_STORE_PROPERTY] as string[]).pop(); } - return patchEnd(span, plugin._tracer.bind(next))(); + const callback = args[callbackIdx] as Function; + return plugin._tracer.bind(callback).apply(this, arguments); }; } const result = original.apply(this, arguments); - // if the layer return a response, the callback will never - // be called, so we need to manually close the span - if (callbackHasBeenCalled === false) { - span.end(); - } + // If the callback is never called, we need to close the span. + setImmediate(() => { + if (spanHasEnded === false) { + span.end(startTime); + } + }).unref(); return result; }; }); diff --git a/packages/opentelemetry-plugin-express/src/utils.ts b/packages/opentelemetry-plugin-express/src/utils.ts index 1ec8e348f9..084c30b694 100644 --- a/packages/opentelemetry-plugin-express/src/utils.ts +++ b/packages/opentelemetry-plugin-express/src/utils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CanonicalCode, Span, Attributes } from '@opentelemetry/api'; +import { Attributes } from '@opentelemetry/api'; import { ExpressLayer, AttributeNames, @@ -67,7 +67,7 @@ export const getLayerMetadata = ( [AttributeNames.EXPRESS_NAME]: layerPath ?? 'request handler', [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.REQUEST_HANDLER, }, - name: 'request handler', + name: `request handler${layer.path ? ` - ${layerPath}` : ''}`, }; } else { return { @@ -80,29 +80,6 @@ export const getLayerMetadata = ( } }; -/** - * Ends a created span. - * @param span The created span to end. - * @param resultHandler A callback function. - */ -export const patchEnd = (span: Span, resultHandler: Function): Function => { - return function patchedEnd(this: {}, ...args: unknown[]) { - const error = args[0]; - if (error instanceof Error) { - span.setStatus({ - code: CanonicalCode.INTERNAL, - message: error.message, - }); - } else { - span.setStatus({ - code: CanonicalCode.OK, - }); - } - span.end(); - return resultHandler.apply(this, args); - }; -}; - /** * Check whether the given obj match pattern * @param constant e.g URL of request diff --git a/packages/opentelemetry-plugin-express/src/version.ts b/packages/opentelemetry-plugin-express/src/version.ts index 74e9e92337..5beb5f65f6 100644 --- a/packages/opentelemetry-plugin-express/src/version.ts +++ b/packages/opentelemetry-plugin-express/src/version.ts @@ -15,4 +15,4 @@ */ // this is autogenerated file, see scripts/version-update.js -export const VERSION = '0.6.1'; +export const VERSION = '0.6.3'; diff --git a/packages/opentelemetry-plugin-express/test/express.test.ts b/packages/opentelemetry-plugin-express/test/express.test.ts index 195381d643..0dd3a808f9 100644 --- a/packages/opentelemetry-plugin-express/test/express.test.ts +++ b/packages/opentelemetry-plugin-express/test/express.test.ts @@ -81,7 +81,7 @@ describe('Express Plugin', () => { const app = express(); app.use(express.json()); app.use(function customMiddleware(req, res, next) { - for (let i = 0; i < 1000; i++) { + for (let i = 0; i < 1000000; i++) { continue; } return next(); From 1959930f7715046a80deba6c744a9870e740d579 Mon Sep 17 00:00:00 2001 From: Naseem Date: Thu, 16 Apr 2020 14:14:50 -0400 Subject: [PATCH 3/4] [http] fix: use url.URL (#960) url.URL from the standard library should be used instead of the global URL which comes from the dom lib. Signed-off-by: Naseem --- packages/opentelemetry-plugin-http/src/http.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/http.ts b/packages/opentelemetry-plugin-http/src/http.ts index 53bbebe5bb..51263cc89e 100644 --- a/packages/opentelemetry-plugin-http/src/http.ts +++ b/packages/opentelemetry-plugin-http/src/http.ts @@ -145,7 +145,7 @@ export class HttpPlugin extends BasePlugin { protected _getPatchOutgoingGetFunction( clientRequest: ( - options: RequestOptions | string | URL, + options: RequestOptions | string | url.URL, ...args: HttpRequestArgs ) => ClientRequest ) { @@ -161,7 +161,7 @@ export class HttpPlugin extends BasePlugin { // https://nodejs.org/dist/latest/docs/api/http.html#http_http_get_options_callback // https://github.com/googleapis/cloud-trace-nodejs/blob/master/src/plugins/plugin-http.ts#L198 return function outgoingGetRequest< - T extends RequestOptions | string | URL + T extends RequestOptions | string | url.URL >(options: T, ...args: HttpRequestArgs): ClientRequest { const req = clientRequest(options, ...args); req.end(); From d71954eb18b3851a6fe8aa2b3b0e13543f898176 Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Thu, 16 Apr 2020 12:15:28 -0700 Subject: [PATCH 4/4] Adding images of all the contributors in this project (#944) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 87eef2a56a..9afe784bd5 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,11 @@ Maintainers ([@open-telemetry/js-maintainers](https://github.com/orgs/open-telem *Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* +### Thanks to all the people who already contributed! + + + + ## Packages ### API @@ -192,7 +197,6 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [node-gitter-url]: https://gitter.im/open-telemetry/opentelemetry-node?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge [license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/master/LICENSE - [up-for-grabs-issues]: https://github.com/open-telemetry/OpenTelemetry-js/issues?q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs [good-first-issues]: https://github.com/open-telemetry/OpenTelemetry-js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22