Skip to content

Commit

Permalink
feat: resource auto-detection (open-telemetry#899)
Browse files Browse the repository at this point in the history
* 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 <mayurkale@google.com>
  • Loading branch information
mwear and mayurkale22 committed Apr 15, 2020
1 parent 8e56923 commit 182e9ee
Show file tree
Hide file tree
Showing 19 changed files with 997 additions and 5 deletions.
10 changes: 9 additions & 1 deletion packages/opentelemetry-resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
Expand All @@ -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"
}
}
3 changes: 2 additions & 1 deletion packages/opentelemetry-resources/src/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
) {}

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/opentelemetry-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
* limitations under the License.
*/

export { Resource } from './Resource';
export * from './Resource';
export * from './platform';
export * from './constants';
export * from './types';
Original file line number Diff line number Diff line change
@@ -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<Resource> => {
return Resource.createTelemetrySDKResource();
};
17 changes: 17 additions & 0 deletions packages/opentelemetry-resources/src/platform/browser/index.ts
Original file line number Diff line number Diff line change
@@ -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';
20 changes: 20 additions & 0 deletions packages/opentelemetry-resources/src/platform/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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<Detector> = [envDetector, awsEc2Detector, gcpDetector];

/**
* Runs all resource detectors and returns the results merged into a single
* Resource.
*/
export const detectResources = async (): Promise<Resource> => {
const resources: Array<Resource> = await Promise.all(
DETECTORS.map(d => {
try {
return d.detect();
} catch {
return Resource.empty();
}
})
);
return resources.reduce(
(acc, resource) => acc.merge(resource),
Resource.createTelemetrySDKResource()
);
};
Original file line number Diff line number Diff line change
@@ -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<Resource> {
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<T>(): Promise<T> {
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();
Loading

0 comments on commit 182e9ee

Please sign in to comment.