Skip to content

Commit e3a689f

Browse files
committed
fix resource detectors to never have their async attribute promises reject
Also tweak the auto-instrumentations-node/register.js test to actually use resource detectors, to help test if there are issues with them. Refs: open-telemetry/opentelemetry-js#5539
1 parent 9cb5f76 commit e3a689f

File tree

7 files changed

+218
-122
lines changed

7 files changed

+218
-122
lines changed

detectors/node/opentelemetry-resource-detector-alibaba-cloud/src/detectors/AlibabaCloudEcsDetector.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { context } from '@opentelemetry/api';
17+
import { context, diag } from '@opentelemetry/api';
1818
import { suppressTracing } from '@opentelemetry/core';
1919
import {
2020
ResourceDetector,
@@ -61,38 +61,58 @@ class AlibabaCloudEcsDetector implements ResourceDetector {
6161
* @param config (unused) The resource detection config
6262
*/
6363
detect(): DetectedResource {
64-
const attributes = context.with(suppressTracing(context.active()), () =>
65-
this._getAttributes()
64+
const dataPromise = context.with(suppressTracing(context.active()), () =>
65+
this._gatherData()
6666
);
67+
68+
const attrNames = [
69+
SEMRESATTRS_CLOUD_PROVIDER,
70+
SEMRESATTRS_CLOUD_PLATFORM,
71+
SEMRESATTRS_CLOUD_ACCOUNT_ID,
72+
SEMRESATTRS_CLOUD_REGION,
73+
SEMRESATTRS_CLOUD_AVAILABILITY_ZONE,
74+
SEMRESATTRS_HOST_ID,
75+
SEMRESATTRS_HOST_TYPE,
76+
SEMRESATTRS_HOST_NAME,
77+
];
78+
79+
const attributes = {} as DetectedResourceAttributes;
80+
attrNames.forEach(name => {
81+
// Each resource attribute is determined asynchronously in _gatherData().
82+
attributes[name] = dataPromise.then(data => data[name]);
83+
});
84+
6785
return { attributes };
6886
}
6987

7088
/** Gets identity and host info and returns them as attribs. Empty object if fails */
71-
_getAttributes(): DetectedResourceAttributes {
72-
const dataP = Promise.all([this._fetchIdentity(), this._fetchHost()]);
89+
async _gatherData(): Promise<DetectedResourceAttributes> {
90+
try {
91+
const {
92+
'owner-account-id': accountId,
93+
'instance-id': instanceId,
94+
'instance-type': instanceType,
95+
'region-id': region,
96+
'zone-id': availabilityZone,
97+
} = await this._fetchIdentity();
98+
const hostname = await this._fetchHost();
7399

74-
return {
75-
[SEMRESATTRS_CLOUD_PROVIDER]: dataP.then(
76-
() => CLOUDPROVIDERVALUES_ALIBABA_CLOUD
77-
),
78-
[SEMRESATTRS_CLOUD_PLATFORM]: dataP.then(
79-
() => CLOUDPLATFORMVALUES_ALIBABA_CLOUD_ECS
80-
),
81-
82-
// Data from _fetchIdentity()
83-
[SEMRESATTRS_CLOUD_ACCOUNT_ID]: dataP.then(
84-
data => data[0]['owner-account-id']
85-
),
86-
[SEMRESATTRS_CLOUD_REGION]: dataP.then(data => data[0]['region-id']),
87-
[SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: dataP.then(
88-
data => data[0]['zone-id']
89-
),
90-
[SEMRESATTRS_HOST_ID]: dataP.then(data => data[0]['instance-id']),
91-
[SEMRESATTRS_HOST_TYPE]: dataP.then(data => data[0]['instance-type']),
92-
93-
// Data from _fetchHost()
94-
[SEMRESATTRS_HOST_NAME]: dataP.then(data => data[1]),
95-
};
100+
return {
101+
[SEMRESATTRS_CLOUD_PROVIDER]: CLOUDPROVIDERVALUES_ALIBABA_CLOUD,
102+
[SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_ALIBABA_CLOUD_ECS,
103+
[SEMRESATTRS_CLOUD_ACCOUNT_ID]: accountId,
104+
[SEMRESATTRS_CLOUD_REGION]: region,
105+
[SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: availabilityZone,
106+
[SEMRESATTRS_HOST_ID]: instanceId,
107+
[SEMRESATTRS_HOST_TYPE]: instanceType,
108+
[SEMRESATTRS_HOST_NAME]: hostname,
109+
};
110+
} catch (err: any) {
111+
diag.debug(
112+
`${this.constructor.name}: did not detect resource: ${err?.message}`
113+
);
114+
return {};
115+
}
96116
}
97117

98118
/**

detectors/node/opentelemetry-resource-detector-alibaba-cloud/test/detectors/AlibabaCloudEcsDetector.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { setTimeout as setTimeoutP } from 'timers/promises';
1718
import * as nock from 'nock';
1819
import * as assert from 'assert';
1920
import { detectResources } from '@opentelemetry/resources';
@@ -138,4 +139,38 @@ describe('alibabaCloudEcsDetector', () => {
138139
scope.done();
139140
});
140141
});
142+
143+
describe('with delay in calling .waitForAsyncAttributes()', () => {
144+
// Note any `unhandledRejection` process events during the test run.
145+
let gotUnhandledRejections: Error[];
146+
const unhandleRejectionHandler = (err: any) => {
147+
gotUnhandledRejections.push(err);
148+
};
149+
beforeEach(() => {
150+
gotUnhandledRejections = [];
151+
process.on('unhandledRejection', unhandleRejectionHandler);
152+
});
153+
afterEach(() => {
154+
process.removeListener('unhandledRejection', unhandleRejectionHandler);
155+
});
156+
157+
it('should return empty resource when receiving error', async () => {
158+
const scope = nock(ALIYUN_HOST)
159+
.get(ALIYUN_IDENTITY_PATH)
160+
.replyWithError('NOT FOUND');
161+
162+
const resource = detectResources({
163+
detectors: [alibabaCloudEcsDetector],
164+
});
165+
// This pause simulates the delay between `detectResources` and
166+
// `waitForAsyncAttributes` typically called later in an exporter.
167+
await setTimeoutP(200); // Hope this is enough time to get error response.
168+
await resource.waitForAsyncAttributes?.();
169+
170+
assert.deepStrictEqual(resource.attributes, {});
171+
assert.deepStrictEqual(gotUnhandledRejections, []);
172+
173+
scope.done();
174+
});
175+
});
141176
});

detectors/node/opentelemetry-resource-detector-aws/src/detectors/AwsBeanstalkDetector.ts

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { context } from '@opentelemetry/api';
17+
import { context, diag } from '@opentelemetry/api';
1818
import { suppressTracing } from '@opentelemetry/core';
1919

2020
import {
@@ -65,45 +65,55 @@ export class AwsBeanstalkDetector implements ResourceDetector {
6565
}
6666

6767
detect(): DetectedResource {
68-
const attributes = context.with(suppressTracing(context.active()), () =>
69-
this._getAttributes()
68+
const dataPromise = context.with(suppressTracing(context.active()), () =>
69+
this._gatherData()
7070
);
71+
72+
const attrNames = [
73+
ATTR_CLOUD_PROVIDER,
74+
ATTR_CLOUD_PLATFORM,
75+
ATTR_SERVICE_NAME,
76+
ATTR_SERVICE_NAMESPACE,
77+
ATTR_SERVICE_VERSION,
78+
ATTR_SERVICE_INSTANCE_ID,
79+
];
80+
81+
const attributes = {} as DetectedResourceAttributes;
82+
attrNames.forEach(name => {
83+
// Each resource attribute is determined asynchronously in _gatherData().
84+
attributes[name] = dataPromise.then(data => data[name]);
85+
});
86+
7187
return { attributes };
7288
}
73-
7489
/**
7590
* Async resource attributes for AWS Beanstalk configuration read from file.
7691
*/
77-
_getAttributes(): DetectedResourceAttributes {
78-
const parsedDataP = AwsBeanstalkDetector.fileAccessAsync(
79-
this.BEANSTALK_CONF_PATH,
80-
fs.constants.R_OK
81-
)
82-
.then(() =>
83-
AwsBeanstalkDetector.readFileAsync(this.BEANSTALK_CONF_PATH, 'utf8')
84-
)
85-
.then(rawData => {
86-
return JSON.parse(rawData);
87-
});
92+
async _gatherData(): Promise<DetectedResourceAttributes> {
93+
try {
94+
await AwsBeanstalkDetector.fileAccessAsync(
95+
this.BEANSTALK_CONF_PATH,
96+
fs.constants.R_OK
97+
);
98+
99+
const rawData = await AwsBeanstalkDetector.readFileAsync(
100+
this.BEANSTALK_CONF_PATH,
101+
'utf8'
102+
);
103+
const parsedData = JSON.parse(rawData);
88104

89-
return {
90-
[ATTR_CLOUD_PROVIDER]: parsedDataP.then(() => CLOUD_PROVIDER_VALUE_AWS),
91-
[ATTR_CLOUD_PLATFORM]: parsedDataP.then(
92-
() => CLOUD_PLATFORM_VALUE_AWS_ELASTIC_BEANSTALK
93-
),
94-
[ATTR_SERVICE_NAME]: parsedDataP.then(
95-
() => CLOUD_PLATFORM_VALUE_AWS_ELASTIC_BEANSTALK
96-
),
97-
[ATTR_SERVICE_NAMESPACE]: parsedDataP.then(
98-
parsedData => parsedData.environment_name
99-
),
100-
[ATTR_SERVICE_VERSION]: parsedDataP.then(
101-
parsedData => parsedData.version_label
102-
),
103-
[ATTR_SERVICE_INSTANCE_ID]: parsedDataP.then(
104-
parsedData => parsedData.deployment_id
105-
),
106-
};
105+
return {
106+
[ATTR_CLOUD_PROVIDER]: CLOUD_PROVIDER_VALUE_AWS,
107+
[ATTR_CLOUD_PLATFORM]: CLOUD_PLATFORM_VALUE_AWS_ELASTIC_BEANSTALK,
108+
[ATTR_SERVICE_NAME]: CLOUD_PLATFORM_VALUE_AWS_ELASTIC_BEANSTALK,
109+
[ATTR_SERVICE_NAMESPACE]: parsedData.environment_name,
110+
[ATTR_SERVICE_VERSION]: parsedData.version_label,
111+
[ATTR_SERVICE_INSTANCE_ID]: parsedData.deployment_id,
112+
};
113+
} catch (e: any) {
114+
diag.debug(`AwsBeanstalkDetector: did not detect resource: ${e.message}`);
115+
return {};
116+
}
107117
}
108118
}
109119

detectors/node/opentelemetry-resource-detector-aws/src/detectors/AwsEc2Detector.ts

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,33 +55,49 @@ class AwsEc2Detector implements ResourceDetector {
5555
readonly MILLISECOND_TIME_OUT = 5000;
5656

5757
detect(): DetectedResource {
58-
const attributes = context.with(suppressTracing(context.active()), () =>
59-
this._getAttributes()
58+
const dataPromise = context.with(suppressTracing(context.active()), () =>
59+
this._gatherData()
6060
);
61+
62+
const attrNames = [
63+
ATTR_CLOUD_PROVIDER,
64+
ATTR_CLOUD_PLATFORM,
65+
ATTR_CLOUD_ACCOUNT_ID,
66+
ATTR_CLOUD_REGION,
67+
ATTR_CLOUD_AVAILABILITY_ZONE,
68+
ATTR_HOST_ID,
69+
ATTR_HOST_TYPE,
70+
ATTR_HOST_NAME,
71+
];
72+
73+
const attributes = {} as DetectedResourceAttributes;
74+
attrNames.forEach(name => {
75+
// Each resource attribute is determined asynchronously in _gatherData().
76+
attributes[name] = dataPromise.then(data => data[name]);
77+
});
78+
6179
return { attributes };
6280
}
6381

64-
_getAttributes(): DetectedResourceAttributes {
82+
/**
83+
* Attempts to connect and obtain an AWS instance Identity document.
84+
*/
85+
async _gatherData(): Promise<DetectedResourceAttributes> {
6586
try {
66-
const dataP = this._fetchToken().then(token =>
67-
Promise.all([this._fetchIdentity(token), this._fetchHost(token)])
68-
);
87+
const token = await this._fetchToken();
88+
const { accountId, instanceId, instanceType, region, availabilityZone } =
89+
await this._fetchIdentity(token);
90+
const hostname = await this._fetchHost(token);
6991

7092
return {
71-
[ATTR_CLOUD_PROVIDER]: dataP.then(() => CLOUD_PROVIDER_VALUE_AWS),
72-
[ATTR_CLOUD_PLATFORM]: dataP.then(() => CLOUD_PLATFORM_VALUE_AWS_EC2),
73-
74-
// Data from _fetchIdentity()
75-
[ATTR_CLOUD_ACCOUNT_ID]: dataP.then(data => data[0].accountId),
76-
[ATTR_CLOUD_REGION]: dataP.then(data => data[0].region),
77-
[ATTR_CLOUD_AVAILABILITY_ZONE]: dataP.then(
78-
data => data[0].availabilityZone
79-
),
80-
[ATTR_HOST_ID]: dataP.then(data => data[0].instanceId),
81-
[ATTR_HOST_TYPE]: dataP.then(data => data[0].instanceType),
82-
83-
// Data from _fetchHost()
84-
[ATTR_HOST_NAME]: dataP.then(data => data[1]),
93+
[ATTR_CLOUD_PROVIDER]: CLOUD_PROVIDER_VALUE_AWS,
94+
[ATTR_CLOUD_PLATFORM]: CLOUD_PLATFORM_VALUE_AWS_EC2,
95+
[ATTR_CLOUD_ACCOUNT_ID]: accountId,
96+
[ATTR_CLOUD_REGION]: region,
97+
[ATTR_CLOUD_AVAILABILITY_ZONE]: availabilityZone,
98+
[ATTR_HOST_ID]: instanceId,
99+
[ATTR_HOST_TYPE]: instanceType,
100+
[ATTR_HOST_NAME]: hostname,
85101
};
86102
} catch {
87103
return {};

detectors/node/opentelemetry-resource-detector-aws/src/detectors/AwsEksDetector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ export class AwsEksDetector implements ResourceDetector {
108108
[ATTR_K8S_CLUSTER_NAME]: clusterName || '',
109109
[ATTR_CONTAINER_ID]: containerId || '',
110110
};
111-
} catch (e) {
112-
diag.debug('AwsEksDetector: Process is not running on K8S', e);
111+
} catch (e: any) {
112+
diag.debug('AwsEksDetector: Process is not running on K8S:', e.message);
113113
return {};
114114
}
115115
}

0 commit comments

Comments
 (0)