Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into prepare-for-api
Browse files Browse the repository at this point in the history
  • Loading branch information
dyladan committed Jan 31, 2022
2 parents 56b936f + ce5f7f7 commit d20010d
Show file tree
Hide file tree
Showing 21 changed files with 312 additions and 55 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/w3c-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: |
npm install --ignore-scripts
npx lerna bootstrap --no-ci --scope=propagation-validation-server --include-dependencies
npx lerna bootstrap --no-ci --hoist --scope=propagation-validation-server --include-dependencies
- name: Install and Bootstrap (cache hit) 🔧
if: steps.cache.outputs.cache-hit == 'true'
run: |
npm ci --ignore-scripts
npx lerna bootstrap --scope=propagation-validation-server --include-dependencies
npx lerna bootstrap --hoist --scope=propagation-validation-server --include-dependencies
- name: Generate version.ts files
run: lerna run version
Expand Down
30 changes: 30 additions & 0 deletions doc/development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,33 @@ npm run docs
```

The document will be available under `packages/opentelemetry-api/docs/out` path.

## Platform conditional exports

Universal packages are packages that can be used in both web browsers and
Node.js environment. These packages may be implemented on top of different
platform APIs to achieve the same goal. Like accessing the _global_ reference,
we have different preferred ways to do it:

- In Node.js, we access the _global_ reference with `globalThis` or `global`:

```js
/// packages/opentelemetry-core/src/platform/node/globalThis.ts
export const _globalThis = typeof globalThis === 'object' ? globalThis : global;
```

- In web browser, we access the _global_ reference with the following definition:

```js
/// packages/opentelemetry-core/src/platform/browser/globalThis.ts
export const _globalThis: typeof globalThis =
typeof globalThis === 'object' ? globalThis :
typeof self === 'object' ? self :
typeof window === 'object' ? window :
typeof global === 'object' ? global :
{} as typeof globalThis;
```

Even though the implementation may differ, the exported names must be aligned.
It can be confusing if exported names present in one environment but not in the
others.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"compile": "tsc --build"
},
"dependencies": {
"@opentelemetry/api": "^1.1.0",
"@opentelemetry/api": "~1.1.0",
"@opentelemetry/context-async-hooks": "1.0.1",
"@opentelemetry/core": "1.0.1",
"@opentelemetry/sdk-trace-base": "1.0.1",
Expand Down
46 changes: 26 additions & 20 deletions packages/opentelemetry-context-async-hooks/README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
# OpenTelemetry AsyncHooks-based Context Manager
# OpenTelemetry async_hooks-based Context Managers

[![NPM Published Version][npm-img]][npm-url]
[![Apache License][license-image]][license-image]

This package provides [async-hooks][async-hooks-doc] based context manager which is used internally by OpenTelemetry plugins to propagate specific context between function calls and async operations. It only targets NodeJS since async-hooks is only available there.
This package provides two [`ContextManager`](https://open-telemetry.github.io/opentelemetry-js-api/interfaces/contextmanager.html) implementations built on APIs from Node.js's [`async_hooks`][async-hooks-doc] module. If you're looking for a `ContextManager` to use in browser environments, consider [opentelemetry-context-zone](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-context-zone) or [opentelemetry-context-zone-peer-dep](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-context-zone-peer-dep).

## What is a ContextManager
The definition of the `ContextManager` interface and the problem it solves can be found [here](def-context-manager).

The definition and why they exist is available on [the document for context manager][def-context-manager].
## API

### Implementation in NodeJS
Two `ContextManager` implementations are exported:

NodeJS has a specific API to track async context: [async-hooks][async-hooks-doc], it allows to track creation of new async operation and their respective parent.
This package only handle storing a specific object for a given async hooks context.
* `AsyncLocalStorageContextManager`, based on [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage)
* `AsyncHooksContextManager`, based on [`AsyncHook`](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynchook)

### Limitations
The former should be preferred over the latter as its implementation is substantially simpler than the latter's, and according to [Node.js docs](https://github.com/nodejs/node/blame/v17.1.0/doc/api/async_context.md#L42-L45),

Even if the API is native to NodeJS, it doesn't cover all possible cases of context propagation but there is a big effort from the NodeJS team to fix those. That's why we generally advise to be on the latest LTS to benefit from performance and bug fixes.
> While you can create your own implementation [of `AsyncLocalStorage`] on top of [`AsyncHook`], `AsyncLocalStorage` should be preferred as it is a performant and memory safe implementation that involves significant optimizations that are non-obvious to implement.
There are known modules that break context propagation ([some of them are listed there][pkgs-that-break-ah]), so it's possible that the context manager doesn't work with them.
`AsyncLocalStorage` is available in node `^12.17.0 || >= 13.10.0`, however `AsyncLocalStorageContextManager` is not enabled by default for node `<14.8.0` because of some important bugfixes which were introduced in `v14.8.0` (e.g., [nodejs/node#34573](https://github.com/nodejs/node/pull/34573)).

### Prior arts
## Limitations

Context propagation is a big subject when talking about tracing in NodeJS, if you want more information about that here are some resources:
It's possible that this package won't track context perfectly when used with certain packages. In particular, it inherits any bugs present in async_hooks. See [here][pkgs-that-break-ah] for known issues.

- <https://www.npmjs.com/package/continuation-local-storage> (which was the old way of doing context propagation)
- Datadog's own implementation for their Javascript tracer: [here][dd-js-tracer-scope]
- OpenTracing implementation: [here][opentracing-scope]
- Discussion about context propagation by the NodeJS diagnostics working group: [here][diag-team-scope-discussion]
async_hooks is still seeing significant correctness and performance fixes, it's recommended to run the latest Node.js LTS release to benefit from said fixes.

## Prior art

Context propagation is a big subject when talking about tracing in Node.js. If you want more information about it here are some resources:

* <https://www.npmjs.com/package/continuation-local-storage> (which was the old way of doing context propagation)
* Datadog's own implementation for their JavaScript tracer: [here][dd-js-tracer-scope]
* OpenTracing implementation: [here][opentracing-scope]
* Discussion about context propagation by the Node.js Diagnostics Working Group: [here][diag-team-scope-discussion]

## Useful links

- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
* For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
* For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
* For help or feedback on this project, join us in [GitHub Discussions][discussions-url]

## License

Expand All @@ -43,7 +49,7 @@ Apache 2.0 - See [LICENSE][license-url] for more information.
[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
[async-hooks-doc]: http://nodejs.org/dist/latest/docs/api/async_hooks.html
[def-context-manager]: https://github.com/open-telemetry/opentelemetry-js-api/blob/main/docs/context.md#context-manager
[def-context-manager]: https://opentelemetry.io/docs/instrumentation/js/api/context/#context-manager
[dd-js-tracer-scope]: https://github.com/DataDog/dd-trace-js/blob/master/packages/dd-trace/src/scope.js
[opentracing-scope]: https://github.com/opentracing/opentracing-javascript/pull/113
[diag-team-scope-discussion]: https://github.com/nodejs/diagnostics/issues/300
Expand Down
23 changes: 9 additions & 14 deletions packages/opentelemetry-exporter-jaeger/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function spanToThrift(span: ReadableSpan): ThriftSpan {
spanId: Utils.encodeInt64(span.spanContext().spanId),
parentSpanId: parentSpan,
operationName: span.name,
references: spanLinksToThriftRefs(span.links, span.parentSpanId),
references: spanLinksToThriftRefs(span.links),
flags: span.spanContext().traceFlags || DEFAULT_FLAGS,
startTime: Utils.encodeInt64(hrTimeToMicroseconds(span.startTime)),
duration: Utils.encodeInt64(hrTimeToMicroseconds(span.duration)),
Expand All @@ -120,21 +120,16 @@ export function spanToThrift(span: ReadableSpan): ThriftSpan {
/** Translate OpenTelemetry {@link Link}s to Jaeger ThriftReference. */
function spanLinksToThriftRefs(
links: Link[],
parentSpanId?: string
): ThriftReference[] {
return links
.map((link): ThriftReference | null => {
if (link.context.spanId === parentSpanId) {
const refType = ThriftReferenceType.FOLLOWS_FROM;
const traceId = link.context.traceId;
const traceIdHigh = Utils.encodeInt64(traceId.slice(0, 16));
const traceIdLow = Utils.encodeInt64(traceId.slice(16));
const spanId = Utils.encodeInt64(link.context.spanId);
return { traceIdLow, traceIdHigh, spanId, refType };
}
return null;
})
.filter(ref => !!ref) as ThriftReference[];
.map((link): ThriftReference => {
const refType = ThriftReferenceType.FOLLOWS_FROM;
const traceId = link.context.traceId;
const traceIdHigh = Utils.encodeInt64(traceId.slice(0, 16));
const traceIdLow = Utils.encodeInt64(traceId.slice(16));
const spanId = Utils.encodeInt64(link.context.spanId);
return { traceIdLow, traceIdHigh, spanId, refType };
});
}

/** Translate OpenTelemetry attribute value to Jaeger TagValue. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@ describe('transform', () => {
assert.strictEqual(tag7.key, 'cost');
assert.strictEqual(tag7.vType, 'DOUBLE');
assert.strictEqual(tag7.vDouble, 112.12);
assert.strictEqual(thriftSpan.references.length, 0);

assert.strictEqual(thriftSpan.references.length, 1);
const [reference1] = thriftSpan.references;
assert.strictEqual(reference1.refType, ThriftReferenceType.FOLLOWS_FROM);
assert.strictEqual(reference1.spanId.toString('hex'), readableSpan.links[0].context.spanId);
assert.strictEqual(reference1.traceIdLow.toString('hex'), readableSpan.links[0].context.traceId.substring(16, 32));
assert.strictEqual(reference1.traceIdHigh.toString('hex'), readableSpan.links[0].context.traceId.substring(0, 16));

assert.strictEqual(thriftSpan.logs.length, 1);
const [log1] = thriftSpan.logs;
Expand Down
24 changes: 24 additions & 0 deletions packages/opentelemetry-resources/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*!
* Copyright The 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
*
* http://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.
*/

const karmaWebpackConfig = require('../../karma.webpack');
const karmaBaseConfig = require('../../karma.base');

module.exports = (config) => {
config.set(Object.assign({}, karmaBaseConfig, {
webpack: karmaWebpackConfig
}))
};
15 changes: 14 additions & 1 deletion packages/opentelemetry-resources/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
"scripts": {
"compile": "tsc --build tsconfig.all.json",
"clean": "tsc --build --clean tsconfig.all.json",
"codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
"test:browser": "nyc karma start --single-run",
"tdd": "npm run test -- --watch-extensions ts --watch",
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"version": "node ../../scripts/version-update.js",
Expand Down Expand Up @@ -59,14 +61,25 @@
"@types/mocha": "8.2.3",
"@types/node": "14.17.33",
"@types/sinon": "10.0.6",
"@types/webpack-env": "1.16.3",
"codecov": "3.8.3",
"karma": "6.3.8",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-mocha": "2.0.1",
"karma-mocha-webworker": "1.3.0",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "4.0.2",
"mocha": "7.2.0",
"nock": "13.0.11",
"nyc": "15.1.0",
"rimraf": "3.0.2",
"sinon": "12.0.1",
"ts-mocha": "8.0.0",
"typescript": "4.4.4"
"typescript": "4.4.4",
"webpack": "4.46.0",
"webpack-cli": "4.9.1",
"webpack-merge": "5.8.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@
import { diag } from '@opentelemetry/api';
import { getEnv } from '@opentelemetry/core';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import {
Detector,
Resource,
ResourceDetectionConfig,
ResourceAttributes,
} from '../../../';
import { Resource } from '../Resource';
import { Detector, ResourceAttributes } from '../types';
import { ResourceDetectionConfig } from '../config';

/**
* EnvDetector can be used to detect the presence of and create a Resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@

import { diag } from '@opentelemetry/api';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { Detector, Resource, ResourceDetectionConfig } from '../../../';
import { ResourceAttributes } from '../../../types';
import { Resource } from '../Resource';
import { Detector, ResourceAttributes } from '../types';
import { ResourceDetectionConfig } from '../config';

/**
* ProcessDetector will be used to detect the resources related current process running
* and being instrumented from the NodeJS Process module.
*/
class ProcessDetector implements Detector {
async detect(config?: ResourceDetectionConfig): Promise<Resource> {
// Skip if not in Node.js environment.
if (typeof process !== 'object') {
return Resource.empty();
}
const processResource: ResourceAttributes = {
[SemanticResourceAttributes.PROCESS_PID]: process.pid,
[SemanticResourceAttributes.PROCESS_EXECUTABLE_NAME]: process.title || '',
Expand Down
1 change: 1 addition & 0 deletions packages/opentelemetry-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './Resource';
export * from './platform';
export * from './types';
export * from './config';
export * from './detectors';
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,3 @@

export * from './default-service-name';
export * from './detect-resources';
export * from './detectors';
13 changes: 12 additions & 1 deletion packages/opentelemetry-resources/test/Resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as assert from 'assert';
import { SDK_INFO } from '@opentelemetry/core';
import { Resource } from '../src';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { describeBrowser, describeNode } from './util';

describe('Resource', () => {
const resource1 = new Resource({
Expand Down Expand Up @@ -100,7 +101,7 @@ describe('Resource', () => {
});
});

describe('.default()', () => {
describeNode('.default()', () => {
it('should return a default resource', () => {
const resource = Resource.default();
assert.strictEqual(resource.attributes[SemanticResourceAttributes.TELEMETRY_SDK_NAME], SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_NAME]);
Expand All @@ -109,4 +110,14 @@ describe('Resource', () => {
assert.strictEqual(resource.attributes[SemanticResourceAttributes.SERVICE_NAME], `unknown_service:${process.argv0}`);
});
});

describeBrowser('.default()', () => {
it('should return a default resource', () => {
const resource = Resource.default();
assert.strictEqual(resource.attributes[SemanticResourceAttributes.TELEMETRY_SDK_NAME], SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_NAME]);
assert.strictEqual(resource.attributes[SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE], SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE]);
assert.strictEqual(resource.attributes[SemanticResourceAttributes.TELEMETRY_SDK_VERSION], SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_VERSION]);
assert.strictEqual(resource.attributes[SemanticResourceAttributes.SERVICE_NAME], 'unknown_service');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright The 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 { RAW_ENVIRONMENT } from '@opentelemetry/core';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { envDetector, Resource } from '../../../src';
import {
assertEmptyResource,
assertWebEngineResource,
} from '../../util/resource-assertions';
import { describeBrowser } from '../../util';

describeBrowser('envDetector() on web browser', () => {
describe('with valid env', () => {
before(() => {
(globalThis as typeof globalThis & RAW_ENVIRONMENT).OTEL_RESOURCE_ATTRIBUTES =
'webengine.name="chromium",webengine.version="99",webengine.description="Chromium"';
});

after(() => {
delete (globalThis as typeof globalThis & RAW_ENVIRONMENT).OTEL_RESOURCE_ATTRIBUTES;
});

it('should return resource information from environment variable', async () => {
const resource: Resource = await envDetector.detect();
assertWebEngineResource(resource, {
[SemanticResourceAttributes.WEBENGINE_NAME]: 'chromium',
[SemanticResourceAttributes.WEBENGINE_VERSION]: '99',
[SemanticResourceAttributes.WEBENGINE_DESCRIPTION]: 'Chromium',
});
});
});

describe('with empty env', () => {
it('should return empty resource', async () => {
const resource: Resource = await envDetector.detect();
assertEmptyResource(resource);
});
});
});
Loading

0 comments on commit d20010d

Please sign in to comment.