Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

feat: add sync resource detector api and service and deployment detectors #129

Merged
merged 10 commits into from
Jun 16, 2021
44 changes: 44 additions & 0 deletions detectors/node/resource-detector-deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# OpenTelemetry Deployment Resource Detector for Node.js
[![NPM version](https://img.shields.io/npm/v/opentelemetry-resource-detector-deployment.svg)](https://www.npmjs.com/package/opentelemetry-resource-detector-deployment)

This module provides automatic resource detector for [Deployment](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/deployment_environment.md)

## Installation

```bash
npm install --save opentelemetry-resource-detector-deployment
```

## Usage

### Synchronous SDK Initialization
```js
import { detectSyncResources } from 'opentelemetry-resource-detector-sync-api';
import { deploymentSyncDetector } from 'opentelemetry-resource-detector-deployment';

const resource = detectSyncResources({
detectors: [deploymentSyncDetector, /* add other sync detectors here */],
});
const tracerProvider = new NodeTracerProvider({ resource });
```

### Asynchronous SDK Initialization
```js
import { detectResources } from '@opentelemetry/resources';
import { deploymentDetector } from 'opentelemetry-resource-detector-deployment';

( async () => {
const resource = await detectResources({
detectors: [deploymentDetector, /* add other async detectors here */],
});
const tracerProvider = new NodeTracerProvider({ resource });
// Initialize auto instrumentation plugins and register provider.
// Make sure you don't 'require' instrumented packages elsewhere
// before they are registered here
})();
```

## Attributes
| Attribute | Type | Source |
| --- | --- | --- |
| `deployment.environment` | string | `process.env.NODE_ENV` |
61 changes: 61 additions & 0 deletions detectors/node/resource-detector-deployment/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "opentelemetry-resource-detector-deployment",
"version": "0.4.0",
"description": "open telemetry resource detector for deployment",
"keywords": [
"opentelemetry"
],
"homepage": "https://github.com/aspecto-io/opentelemetry-ext-js",
"license": "Apache-2.0",
"main": "dist/src/index.js",
"files": [
"dist/src/**/*.js",
"dist/src/**/*.d.ts",
"LICENSE",
"README.md"
],
"repository": {
"type": "git",
"url": "https://github.com/aspecto-io/opentelemetry-ext-js.git"
},
"scripts": {
"build": "tsc",
"prepare": "yarn run build",
"test": "mocha",
"test:jaeger": "OTEL_EXPORTER_JAEGER_AGENT_HOST=localhost mocha",
"watch": "tsc -w",
"version:update": "node ../../../scripts/version-update.js",
"test:ci": "yarn test",
"version": "yarn run version:update"
},
"bugs": {
"url": "https://github.com/aspecto-io/opentelemetry-ext-js/issues"
},
"peerDependencies": {
"@opentelemetry/api": "^0.20.0"
},
"dependencies": {
"@opentelemetry/resources": "^0.20.0",
"@opentelemetry/semantic-conventions": "^0.20.0",
"opentelemetry-resource-detector-sync-api": "^0.4.0"
},
"devDependencies": {
"@opentelemetry/api": "^0.20.0",
"@types/mocha": "^8.2.2",
"expect": "^26.6.2",
"mocha": "^8.4.0",
"opentelemetry-instrumentation-mocha": "0.0.1-rc.1",
"ts-node": "^9.1.1",
"typescript": "^4.0.5"
},
"mocha": {
"extension": [
"ts"
],
"spec": "test/**/*.spec.ts",
"require": [
"ts-node/register",
"opentelemetry-instrumentation-mocha"
]
}
}
18 changes: 18 additions & 0 deletions detectors/node/resource-detector-deployment/src/deployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ResourceAttributes as ResourceAttributesKeys } from '@opentelemetry/semantic-conventions';
import { Resource } from '@opentelemetry/resources';
import { SyncDetector, syncDetectorToDetector } from 'opentelemetry-resource-detector-sync-api';

class DeploymentSyncDetector implements SyncDetector {
detect(): Resource {
if (process.env.NODE_ENV) {
return new Resource({
[ResourceAttributesKeys.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
});
} else {
return Resource.empty();
}
}
}

export const deploymentSyncDetector = new DeploymentSyncDetector();
export const deploymentDetector = syncDetectorToDetector(deploymentSyncDetector);
1 change: 1 addition & 0 deletions detectors/node/resource-detector-deployment/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './deployment';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'mocha';
import expect from 'expect';
import { deploymentDetector, deploymentSyncDetector } from '../src';
import { ResourceAttributes } from '@opentelemetry/semantic-conventions';

describe('deployment detector', () => {
it('read deployment environment from environment variable', () => {
process.env.NODE_ENV = 'env from testing';
const resource = deploymentSyncDetector.detect();
expect(resource.attributes[ResourceAttributes.DEPLOYMENT_ENVIRONMENT]).toMatch('env from testing');
});

it('no deployment environment in environment variable', () => {
delete process.env.NODE_ENV;
const resource = deploymentSyncDetector.detect();
expect(resource.attributes[ResourceAttributes.DEPLOYMENT_ENVIRONMENT]).toBeUndefined();
});

it('async version', async () => {
process.env.NODE_ENV = 'env from testing';
const resource = await deploymentDetector.detect();
expect(resource.attributes[ResourceAttributes.DEPLOYMENT_ENVIRONMENT]).toMatch('env from testing');
});
});
8 changes: 8 additions & 0 deletions detectors/node/resource-detector-deployment/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist",
"types": ["node"]
}
}
46 changes: 46 additions & 0 deletions detectors/node/resource-detector-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# OpenTelemetry Service Resource Detector for Node.js
[![NPM version](https://img.shields.io/npm/v/opentelemetry-resource-detector-service.svg)](https://www.npmjs.com/package/opentelemetry-resource-detector-service)

This module provides automatic resource detector for [Service](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#service)

## Installation

```bash
npm install --save opentelemetry-resource-detector-service
```

## Usage

### Synchronous SDK Initialization
```js
import { detectSyncResources } from 'opentelemetry-resource-detector-sync-api';
import { serviceSyncDetector } from 'opentelemetry-resource-detector-service';

const resource = detectSyncResources({
detectors: [serviceSyncDetector, /* add other sync detectors here */],
});
const tracerProvider = new NodeTracerProvider({ resource });
```

### Asynchronous SDK Initialization
```js
import { detectResources } from '@opentelemetry/resources';
import { serviceDetector } from 'opentelemetry-resource-detector-service';

( async () => {
const resource = await detectResources({
detectors: [serviceDetector, /* add other async detectors here */],
});
const tracerProvider = new NodeTracerProvider({ resource });
// Initialize auto instrumentation plugins and register provider.
// Make sure you don't 'require' instrumented packages elsewhere
// before they are registered here
})();
```

## Attributes
| Attribute | Type | Source |
| --- | --- | --- |
| `service.name` | string | `process.env.OTEL_SERVICE_NAME`. If not set, will try to read `name` attribute from package.json. If not set will fallback to `unknown_service: concatenated with process.executable.name` (according to specification) |
| `serivce.version` | string | `version` attribute from package.json |
| `service.instance.id` | [string (v4 UUID)](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)) | Automatically generated by the detector for each invocation of the service |
62 changes: 62 additions & 0 deletions detectors/node/resource-detector-service/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "opentelemetry-resource-detector-service",
"version": "0.4.0",
"description": "open telemetry resource detector for service",
"keywords": [
"opentelemetry"
],
"homepage": "https://github.com/aspecto-io/opentelemetry-ext-js",
"license": "Apache-2.0",
"main": "dist/src/index.js",
"files": [
"dist/src/**/*.js",
"dist/src/**/*.d.ts",
"LICENSE",
"README.md"
],
"repository": {
"type": "git",
"url": "https://github.com/aspecto-io/opentelemetry-ext-js.git"
},
"scripts": {
"build": "tsc",
"prepare": "yarn run build",
"test": "mocha",
"test:jaeger": "OTEL_EXPORTER_JAEGER_AGENT_HOST=localhost mocha",
"watch": "tsc -w",
"version:update": "node ../../../scripts/version-update.js",
"test:ci": "yarn test",
"version": "yarn run version:update"
},
"bugs": {
"url": "https://github.com/aspecto-io/opentelemetry-ext-js/issues"
},
"peerDependencies": {
"@opentelemetry/api": "^0.20.0"
},
"dependencies": {
"@opentelemetry/resources": "^0.20.0",
"@opentelemetry/semantic-conventions": "^0.20.0",
"opentelemetry-resource-detector-sync-api": "^0.4.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@opentelemetry/api": "^0.20.0",
"@types/mocha": "^8.2.2",
"expect": "^26.6.2",
"mocha": "^8.4.0",
"opentelemetry-instrumentation-mocha": "0.0.1-rc.1",
"ts-node": "^9.1.1",
"typescript": "^4.0.5"
},
"mocha": {
"extension": [
"ts"
],
"spec": "test/**/*.spec.ts",
"require": [
"ts-node/register",
"opentelemetry-instrumentation-mocha"
]
}
}
1 change: 1 addition & 0 deletions detectors/node/resource-detector-service/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './service';
45 changes: 45 additions & 0 deletions detectors/node/resource-detector-service/src/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ResourceAttributes as ResourceAttributesKeys } from '@opentelemetry/semantic-conventions';
import { Resource, defaultServiceName, ResourceAttributes } from '@opentelemetry/resources';
import { SyncDetector, syncDetectorToDetector } from 'opentelemetry-resource-detector-sync-api';
import { v4 as uuidv4 } from 'uuid';
import * as fs from 'fs';

// set as global to make sure it's the same on any invocation even for multiple
// instances of ServiceSyncDetector
const instanceId = uuidv4();

class ServiceSyncDetector implements SyncDetector {
detect(): Resource {
const packageJson = this.loadJsonFile('package.json');
const attributes: ResourceAttributes = {
[ResourceAttributesKeys.SERVICE_INSTANCE_ID]: instanceId,
[ResourceAttributesKeys.SERVICE_NAME]: this.getServiceName(packageJson),
};
const serviceVersion = packageJson?.version;
if (serviceVersion) {
attributes[ResourceAttributesKeys.SERVICE_VERSION] = serviceVersion;
}
return new Resource(attributes);
}

getServiceName(packageJson: any): string {
const fromEnv = process.env.OTEL_SERVICE_NAME;
if (fromEnv) return fromEnv;

const fromPackageJson = packageJson?.name;
if (fromPackageJson) return fromPackageJson;

return defaultServiceName();
}

loadJsonFile(path: string): any {
try {
return JSON.parse(fs.readFileSync(path).toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return require(path); does the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why, but it gives "Error: Cannot find module 'package.json'" when I try to require('package.json').
Probably the require base path is different somehow vs the fs.readFileSync alternative (which I believe can also bump into some edge cases where the process is not started from the service root)
If you have experience on how to write it so it works, that would be great :)

Copy link
Collaborator

@rauno56 rauno56 Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because it is looking for a package named package.json. ./package.json tells require to look for the file.

Both solutions are prone to errors from running the process from the outside of the project root folder.
I don't think there's a "canonical" way to get that without letting the user specify the root OR the path to the main package.json.

Best way I could come up with to more or less consistently find what you are looking for is this:

const path = require('path');

const findRootPackageJson = () => {
	const paths = require.main.paths.map((p) => {
		return path.resolve(p, '..', 'package.json');
	});
	for (const path of paths) {
		try {
			return {
				path,
				contents: require(path),
			};
		} catch (e) {}
	}

	return {};
}

console.log(require.main);
console.log(findRootPackageJson());

EDIT: Changed the catch syntax a tiny bit to support node 8.

} catch (err) {
return null;
}
}
}

export const serviceSyncDetector = new ServiceSyncDetector();
export const serviceDetector = syncDetectorToDetector(serviceSyncDetector);
37 changes: 37 additions & 0 deletions detectors/node/resource-detector-service/test/service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'mocha';
import expect from 'expect';
import { serviceDetector, serviceSyncDetector } from '../src';
import { ResourceAttributes } from '@opentelemetry/semantic-conventions';

describe('service detector', () => {
it('read service from package json', () => {
const resource = serviceSyncDetector.detect();
expect(resource.attributes[ResourceAttributes.SERVICE_NAME]).toMatch('opentelemetry-resource-detector-service');
expect(resource.attributes[ResourceAttributes.SERVICE_VERSION]).toMatch(/\d+.\d+.\d+/);
expect(resource.attributes[ResourceAttributes.SERVICE_INSTANCE_ID]).toMatch(
/[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}/
);
});

it('read service from env variable', () => {
process.env.OTEL_SERVICE_NAME = 'otel service name from env';
const resource = serviceSyncDetector.detect();
expect(resource.attributes[ResourceAttributes.SERVICE_NAME]).toMatch('otel service name from env');
delete process.env.OTEL_SERVICE_NAME;
});

it('calling detect twice return same resource', () => {
const resource1 = serviceSyncDetector.detect();
const resource2 = serviceSyncDetector.detect();
expect(resource1).toStrictEqual(resource2);
});

it('async version', async () => {
const resource = await serviceDetector.detect();
expect(resource.attributes[ResourceAttributes.SERVICE_NAME]).toMatch('opentelemetry-resource-detector-service');
expect(resource.attributes[ResourceAttributes.SERVICE_VERSION]).toMatch(/\d+.\d+.\d+/);
expect(resource.attributes[ResourceAttributes.SERVICE_INSTANCE_ID]).toMatch(
/[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}/
);
});
});
8 changes: 8 additions & 0 deletions detectors/node/resource-detector-service/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist",
"types": ["node"]
}
}
Loading