Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(resources)!: unify resource interfaces #5205

Draft
wants to merge 2 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions packages/opentelemetry-resources/src/IResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,9 @@ import { Attributes } from '@opentelemetry/api';
*/
export interface IResource {
/**
* Check if async attributes have resolved. This is useful to avoid awaiting
* waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary.
*
* @returns true if the resource "attributes" property is not yet settled to its final value
*/
asyncAttributesPending?: boolean;

/**
* @returns the Resource's attributes.
*/
readonly attributes: Attributes;

/**
* Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to
* this Resource's attributes. This is useful in exporters to block until resource detection
* has finished.
* @returns the Resource's attributes wrapped in a promise.
*/
waitForAsyncAttributes?(): Promise<void>;
readonly attributes: Promise<Attributes>;

/**
* Returns a new, merged {@link Resource} by merging the current Resource
Expand Down
120 changes: 35 additions & 85 deletions packages/opentelemetry-resources/src/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,20 @@

import { Attributes, diag } from '@opentelemetry/api';
import {
SEMRESATTRS_SERVICE_NAME,
SEMRESATTRS_TELEMETRY_SDK_LANGUAGE,
SEMRESATTRS_TELEMETRY_SDK_NAME,
SEMRESATTRS_TELEMETRY_SDK_VERSION,
ATTR_SERVICE_NAME, ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_NAME, ATTR_TELEMETRY_SDK_VERSION,
} from '@opentelemetry/semantic-conventions';
import { SDK_INFO } from '@opentelemetry/core';
import { defaultServiceName } from './platform';
import { IResource } from './IResource';
import { isPromiseLike } from './utils';

/**
* A Resource describes the entity for which a signals (metrics or trace) are
* collected.
*/
export class Resource implements IResource {
static readonly EMPTY = new Resource({});
private _syncAttributes?: Attributes;
private _asyncAttributesPromise?: Promise<Attributes>;
private _attributes?: Attributes;

/**
* Check if async attributes have resolved. This is useful to avoid awaiting
* waitForAsyncAttributes (which will introduce asynchronous behavior) when not necessary.
*
* @returns true if the resource "attributes" property is not yet settled to its final value
*/
public asyncAttributesPending?: boolean;
static readonly EMPTY = new Resource(Promise.resolve({}));
private _attributes: Promise<Attributes>;

/**
* Returns an empty Resource
Expand All @@ -54,15 +42,15 @@ export class Resource implements IResource {
* Returns a Resource that identifies the SDK in use.
*/
static default(): IResource {
return new Resource({
[SEMRESATTRS_SERVICE_NAME]: defaultServiceName(),
[SEMRESATTRS_TELEMETRY_SDK_LANGUAGE]:
SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_LANGUAGE],
[SEMRESATTRS_TELEMETRY_SDK_NAME]:
SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_NAME],
[SEMRESATTRS_TELEMETRY_SDK_VERSION]:
SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_VERSION],
});
return new Resource(Promise.resolve({
[ATTR_SERVICE_NAME]: defaultServiceName(),
[ATTR_TELEMETRY_SDK_LANGUAGE]:
SDK_INFO[ATTR_TELEMETRY_SDK_LANGUAGE],
[ATTR_TELEMETRY_SDK_NAME]:
SDK_INFO[ATTR_TELEMETRY_SDK_NAME],
[ATTR_TELEMETRY_SDK_VERSION]:
SDK_INFO[ATTR_TELEMETRY_SDK_VERSION],
}));
}

constructor(
Expand All @@ -71,45 +59,20 @@ export class Resource implements IResource {
* information about the entity as numbers, strings or booleans
* TODO: Consider to add check/validation on attributes.
*/
attributes: Attributes,
asyncAttributesPromise?: Promise<Attributes>
attributes: Attributes | Promise<Attributes>
) {
this._attributes = attributes;
this.asyncAttributesPending = asyncAttributesPromise != null;
this._syncAttributes = this._attributes ?? {};
this._asyncAttributesPromise = asyncAttributesPromise?.then(
asyncAttributes => {
this._attributes = Object.assign({}, this._attributes, asyncAttributes);
this.asyncAttributesPending = false;
return asyncAttributes;
},
err => {
if (isPromiseLike(attributes)) {
this._attributes = attributes.catch(err => {
diag.debug("a resource's async attributes promise rejected: %s", err);
this.asyncAttributesPending = false;
return {};
}
);
}

get attributes(): Attributes {
if (this.asyncAttributesPending) {
diag.error(
'Accessing resource attributes before async attributes settled'
);
});
} else {
this._attributes = Promise.resolve(attributes);
}

return this._attributes ?? {};
}

/**
* Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to
* this Resource's attributes. This is useful in exporters to block until resource detection
* has finished.
*/
async waitForAsyncAttributes?(): Promise<void> {
if (this.asyncAttributesPending) {
await this._asyncAttributesPromise;
}
get attributes(): Promise<Attributes> {
return this._attributes;
}

/**
Expand All @@ -123,33 +86,20 @@ export class Resource implements IResource {
merge(other: IResource | null): IResource {
if (!other) return this;

// Attributes from other resource overwrite attributes from this resource.
const mergedSyncAttributes = {
...this._syncAttributes,
//Support for old resource implementation where _syncAttributes is not defined
...((other as Resource)._syncAttributes ?? other.attributes),
};
return new Resource(Promise.allSettled([
this._attributes,
other.attributes,
]).then((attribPromises) => {
let result = {};

if (
!this._asyncAttributesPromise &&
!(other as Resource)._asyncAttributesPromise
) {
return new Resource(mergedSyncAttributes);
}

const mergedAttributesPromise = Promise.all([
this._asyncAttributesPromise,
(other as Resource)._asyncAttributesPromise,
]).then(([thisAsyncAttributes, otherAsyncAttributes]) => {
return {
...this._syncAttributes,
...thisAsyncAttributes,
//Support for old resource implementation where _syncAttributes is not defined
...((other as Resource)._syncAttributes ?? other.attributes),
...otherAsyncAttributes,
};
});

return new Resource(mergedSyncAttributes, mergedAttributesPromise);
for (const prom of attribPromises) {
if (prom.status === 'rejected') {
diag.debug("a resource's async attributes promise rejected: %s", prom.reason);
} else {
result = { ...result, ...prom.value};
}
}
return result;
}));
}
}
4 changes: 2 additions & 2 deletions packages/opentelemetry-resources/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* limitations under the License.
*/

import type { Detector, DetectorSync } from './types';
import type { DetectorSync } from './types';

/**
* ResourceDetectionConfig provides an interface for configuring resource auto-detection.
*/
export interface ResourceDetectionConfig {
detectors?: Array<Detector | DetectorSync>;
detectors?: Array<DetectorSync>;
}
104 changes: 9 additions & 95 deletions packages/opentelemetry-resources/src/detect-resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,8 @@
import { Resource } from './Resource';
import { ResourceDetectionConfig } from './config';
import { diag } from '@opentelemetry/api';
import { isPromiseLike } from './utils';
import { Detector, DetectorSync } from './types';
import { IResource } from './IResource';

/**
* Runs all resource detectors and returns the results merged into a single Resource. Promise
* does not resolve until all the underlying detectors have resolved, unlike
* detectResourcesSync.
*
* @deprecated use detectResourcesSync() instead.
* @param config Configuration for resource detection
*/
export const detectResources = async (
config: ResourceDetectionConfig = {}
): Promise<IResource> => {
const resources: IResource[] = await Promise.all(
(config.detectors || []).map(async d => {
try {
const resource = await d.detect(config);
diag.debug(`${d.constructor.name} found resource.`, resource);
return resource;
} catch (e) {
diag.debug(`${d.constructor.name} failed: ${e.message}`);
return Resource.empty();
}
})
);

// Future check if verbose logging is enabled issue #1903
logResources(resources);

return resources.reduce(
(acc, resource) => acc.merge(resource),
Resource.empty()
);
};

/**
* Runs all resource detectors synchronously, merging their results. In case of attribute collision later resources will take precedence.
*
Expand All @@ -62,66 +27,15 @@ export const detectResources = async (
export const detectResourcesSync = (
config: ResourceDetectionConfig = {}
): IResource => {
const resources: IResource[] = (config.detectors ?? []).map(
(d: Detector | DetectorSync) => {
try {
const resourceOrPromise = d.detect(config);
let resource: IResource;
if (isPromiseLike<Resource>(resourceOrPromise)) {
const createPromise = async () => {
const resolvedResource = await resourceOrPromise;
await resolvedResource.waitForAsyncAttributes?.();
return resolvedResource.attributes;
};
resource = new Resource({}, createPromise());
} else {
resource = resourceOrPromise as IResource;
}

if (resource.waitForAsyncAttributes) {
void resource
.waitForAsyncAttributes()
.then(() =>
diag.debug(`${d.constructor.name} found resource.`, resource)
);
} else {
diag.debug(`${d.constructor.name} found resource.`, resource);
}

return resource;
} catch (e) {
diag.error(`${d.constructor.name} failed: ${e.message}`);
return Resource.empty();
const detectors = config.detectors ?? [];
const resources = detectors.map(d => d.detect());

Promise.all(resources.map(r => r.attributes)).then((attribsArr) => {
for (const attribs of attribsArr) {
if (Object.keys(attribs).length > 0) {
diag.verbose(JSON.stringify(attribs, null, 4));
}
}
);

const mergedResources = resources.reduce(
(acc, resource) => acc.merge(resource),
Resource.empty()
);

if (mergedResources.waitForAsyncAttributes) {
void mergedResources.waitForAsyncAttributes().then(() => {
// Future check if verbose logging is enabled issue #1903
logResources(resources);
});
}

return mergedResources;
};

/**
* Writes debug information about the detected resources to the logger defined in the resource detection config, if one is provided.
*
* @param resources The array of {@link Resource} that should be logged. Empty entries will be ignored.
*/
const logResources = (resources: Array<IResource>) => {
resources.forEach(resource => {
// Print only populated resources
if (Object.keys(resource.attributes).length > 0) {
const resourceDebugString = JSON.stringify(resource.attributes, null, 4);
diag.verbose(resourceDebugString);
}
});
};
return resources.reduce((acc, res) => acc.merge(res), Resource.empty());
}

This file was deleted.

39 changes: 0 additions & 39 deletions packages/opentelemetry-resources/src/detectors/EnvDetector.ts

This file was deleted.

Loading
Loading