Skip to content

Commit

Permalink
feat(exporter-collector): support config from env #2099 (#2117)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
  • Loading branch information
vmarchaud and dyladan committed May 12, 2021
1 parent 5dcec45 commit 02239b5
Show file tree
Hide file tree
Showing 30 changed files with 584 additions and 115 deletions.
5 changes: 2 additions & 3 deletions examples/collector-exporter-node/docker/collector-config.yaml
Expand Up @@ -15,15 +15,14 @@ exporters:

processors:
batch:
queued_retry:

service:
pipelines:
traces:
receivers: [otlp]
exporters: [zipkin]
processors: [batch, queued_retry]
processors: [batch]
metrics:
receivers: [otlp]
exporters: [prometheus]
processors: [batch, queued_retry]
processors: [batch]
Expand Up @@ -2,7 +2,7 @@ version: "3"
services:
# Collector
collector:
image: otel/opentelemetry-collector:0.16.0
image: otel/opentelemetry-collector:0.25.0
# image: otel/opentelemetry-collector:latest
command: ["--config=/conf/collector-config.yaml", "--log-level=DEBUG"]
volumes:
Expand Down
28 changes: 28 additions & 0 deletions packages/opentelemetry-core/src/baggage/constants.ts
@@ -0,0 +1,28 @@
/*
* 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.
*/

export const BAGGAGE_KEY_PAIR_SEPARATOR = '=';
export const BAGGAGE_PROPERTIES_SEPARATOR = ';';
export const BAGGAGE_ITEMS_SEPARATOR = ',';

// Name of the http header used to propagate the baggage
export const BAGGAGE_HEADER = 'baggage';
// Maximum number of name-value pairs allowed by w3c spec
export const BAGGAGE_MAX_NAME_VALUE_PAIRS = 180;
// Maximum number of bytes per a single name-value pair allowed by w3c spec
export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
// Maximum total length of all name-value pairs allowed by w3c spec
export const BAGGAGE_MAX_TOTAL_LENGTH = 8192;
Expand Up @@ -15,7 +15,6 @@
*/

import {
Baggage,
Context,
BaggageEntry,
getBaggage,
Expand All @@ -24,22 +23,15 @@ import {
TextMapPropagator,
TextMapSetter,
createBaggage,
baggageEntryMetadataFromString,
isInstrumentationSuppressed,
} from '@opentelemetry/api';

const KEY_PAIR_SEPARATOR = '=';
const PROPERTIES_SEPARATOR = ';';
const ITEMS_SEPARATOR = ',';

// Name of the http header used to propagate the baggage
export const BAGGAGE_HEADER = 'baggage';
// Maximum number of name-value pairs allowed by w3c spec
export const MAX_NAME_VALUE_PAIRS = 180;
// Maximum number of bytes per a single name-value pair allowed by w3c spec
export const MAX_PER_NAME_VALUE_PAIRS = 4096;
// Maximum total length of all name-value pairs allowed by w3c spec
export const MAX_TOTAL_LENGTH = 8192;
import { getKeyPairs, serializeKeyPairs, parsePairKeyValue } from '../utils';
import {
BAGGAGE_MAX_NAME_VALUE_PAIRS,
BAGGAGE_ITEMS_SEPARATOR,
BAGGAGE_HEADER,
BAGGAGE_MAX_PER_NAME_VALUE_PAIRS,
} from '../constants';

/**
* Propagates {@link Baggage} through Context format propagation.
Expand All @@ -51,45 +43,27 @@ export class HttpBaggagePropagator implements TextMapPropagator {
inject(context: Context, carrier: unknown, setter: TextMapSetter) {
const baggage = getBaggage(context);
if (!baggage || isInstrumentationSuppressed(context)) return;
const keyPairs = this._getKeyPairs(baggage)
const keyPairs = getKeyPairs(baggage)
.filter((pair: string) => {
return pair.length <= MAX_PER_NAME_VALUE_PAIRS;
return pair.length <= BAGGAGE_MAX_PER_NAME_VALUE_PAIRS;
})
.slice(0, MAX_NAME_VALUE_PAIRS);
const headerValue = this._serializeKeyPairs(keyPairs);
.slice(0, BAGGAGE_MAX_NAME_VALUE_PAIRS);
const headerValue = serializeKeyPairs(keyPairs);
if (headerValue.length > 0) {
setter.set(carrier, BAGGAGE_HEADER, headerValue);
}
}

private _serializeKeyPairs(keyPairs: string[]) {
return keyPairs.reduce((hValue: string, current: string) => {
const value = `${hValue}${
hValue !== '' ? ITEMS_SEPARATOR : ''
}${current}`;
return value.length > MAX_TOTAL_LENGTH ? hValue : value;
}, '');
}

private _getKeyPairs(baggage: Baggage): string[] {
return baggage
.getAllEntries()
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value.value)}`
);
}

extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {
const headerValue: string = getter.get(carrier, BAGGAGE_HEADER) as string;
if (!headerValue) return context;
const baggage: Record<string, BaggageEntry> = {};
if (headerValue.length === 0) {
return context;
}
const pairs = headerValue.split(ITEMS_SEPARATOR);
const pairs = headerValue.split(BAGGAGE_ITEMS_SEPARATOR);
pairs.forEach(entry => {
const keyPair = this._parsePairKeyValue(entry);
const keyPair = parsePairKeyValue(entry);
if (keyPair) {
const baggageEntry: BaggageEntry = { value: keyPair.value };
if (keyPair.metadata) {
Expand All @@ -104,24 +78,6 @@ export class HttpBaggagePropagator implements TextMapPropagator {
return setBaggage(context, createBaggage(baggage));
}

private _parsePairKeyValue(entry: string) {
const valueProps = entry.split(PROPERTIES_SEPARATOR);
if (valueProps.length <= 0) return;
const keyPairPart = valueProps.shift();
if (!keyPairPart) return;
const keyPair = keyPairPart.split(KEY_PAIR_SEPARATOR);
if (keyPair.length !== 2) return;
const key = decodeURIComponent(keyPair[0].trim());
const value = decodeURIComponent(keyPair[1].trim());
let metadata;
if (valueProps.length > 0) {
metadata = baggageEntryMetadataFromString(
valueProps.join(PROPERTIES_SEPARATOR)
);
}
return { key, value, metadata };
}

fields(): string[] {
return [BAGGAGE_HEADER];
}
Expand Down
76 changes: 76 additions & 0 deletions packages/opentelemetry-core/src/baggage/utils.ts
@@ -0,0 +1,76 @@
/*
* 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 { Baggage, baggageEntryMetadataFromString } from '@opentelemetry/api';
import {
BAGGAGE_ITEMS_SEPARATOR,
BAGGAGE_PROPERTIES_SEPARATOR,
BAGGAGE_KEY_PAIR_SEPARATOR,
BAGGAGE_MAX_TOTAL_LENGTH,
} from './constants';

export const serializeKeyPairs = (keyPairs: string[]) => {
return keyPairs.reduce((hValue: string, current: string) => {
const value = `${hValue}${
hValue !== '' ? BAGGAGE_ITEMS_SEPARATOR : ''
}${current}`;
return value.length > BAGGAGE_MAX_TOTAL_LENGTH ? hValue : value;
}, '');
};

export const getKeyPairs = (baggage: Baggage): string[] => {
return baggage
.getAllEntries()
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value.value)}`
);
};

export const parsePairKeyValue = (entry: string) => {
const valueProps = entry.split(BAGGAGE_PROPERTIES_SEPARATOR);
if (valueProps.length <= 0) return;
const keyPairPart = valueProps.shift();
if (!keyPairPart) return;
const keyPair = keyPairPart.split(BAGGAGE_KEY_PAIR_SEPARATOR);
if (keyPair.length !== 2) return;
const key = decodeURIComponent(keyPair[0].trim());
const value = decodeURIComponent(keyPair[1].trim());
let metadata;
if (valueProps.length > 0) {
metadata = baggageEntryMetadataFromString(
valueProps.join(BAGGAGE_PROPERTIES_SEPARATOR)
);
}
return { key, value, metadata };
};

/**
* Parse a string serialized in the baggage HTTP Format (without metadata):
* https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md
*/
export const parseKeyPairsIntoRecord = (value?: string) => {
if (typeof value !== 'string' || value.length === 0) return {};
return value
.split(BAGGAGE_ITEMS_SEPARATOR)
.map(entry => {
return parsePairKeyValue(entry);
})
.filter(keyPair => keyPair !== undefined && keyPair.value.length > 0)
.reduce<Record<string, string>>((headers, keyPair) => {
headers[keyPair!.key] = keyPair!.value;
return headers;
}, {});
};
2 changes: 2 additions & 0 deletions packages/opentelemetry-core/src/index.ts
Expand Up @@ -21,6 +21,8 @@ export * from './common/logging-error-handler';
export * from './common/time';
export * from './common/types';
export * from './ExportResult';
export * from './version';
export * as baggageUtils from './baggage/utils';
export * from './platform';
export * from './propagation/composite';
export * from './trace/HttpTraceContextPropagator';
Expand Down
12 changes: 12 additions & 0 deletions packages/opentelemetry-core/src/utils/environment.ts
Expand Up @@ -67,6 +67,12 @@ export type ENVIRONMENT = {
OTEL_EXPORTER_JAEGER_ENDPOINT?: string;
OTEL_EXPORTER_JAEGER_PASSWORD?: string;
OTEL_EXPORTER_JAEGER_USER?: string;
OTEL_EXPORTER_OTLP_ENDPOINT?: string;
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?: string;
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT?: string;
OTEL_EXPORTER_OTLP_HEADERS?: string;
OTEL_EXPORTER_OTLP_TRACES_HEADERS?: string;
OTEL_EXPORTER_OTLP_METRICS_HEADERS?: string;
OTEL_EXPORTER_ZIPKIN_ENDPOINT?: string;
OTEL_LOG_LEVEL?: DiagLogLevel;
OTEL_RESOURCE_ATTRIBUTES?: string;
Expand Down Expand Up @@ -97,6 +103,12 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_EXPORTER_JAEGER_ENDPOINT: '',
OTEL_EXPORTER_JAEGER_PASSWORD: '',
OTEL_EXPORTER_JAEGER_USER: '',
OTEL_EXPORTER_OTLP_ENDPOINT: '',
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '',
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: '',
OTEL_EXPORTER_OTLP_HEADERS: '',
OTEL_EXPORTER_OTLP_TRACES_HEADERS: '',
OTEL_EXPORTER_OTLP_METRICS_HEADERS: '',
OTEL_EXPORTER_ZIPKIN_ENDPOINT: 'http://localhost:9411/api/v2/spans',
OTEL_LOG_LEVEL: DiagLogLevel.INFO,
OTEL_NO_PATCH_MODULES: [],
Expand Down
Expand Up @@ -25,10 +25,8 @@ import {
} from '@opentelemetry/api';
import { ROOT_CONTEXT } from '@opentelemetry/api';
import * as assert from 'assert';
import {
BAGGAGE_HEADER,
HttpBaggagePropagator,
} from '../../src/baggage/propagation/HttpBaggagePropagator';
import { HttpBaggagePropagator } from '../../src/baggage/propagation/HttpBaggagePropagator';
import { BAGGAGE_HEADER } from '../../src/baggage/constants';

describe('HttpBaggagePropagator', () => {
const httpBaggagePropagator = new HttpBaggagePropagator();
Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-exporter-collector-grpc/README.md
Expand Up @@ -5,7 +5,7 @@
[![devDependencies][devDependencies-image]][devDependencies-url]
[![Apache License][license-image]][license-image]

This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url] - last tested with version **0.16.0**.
This module provides exporter for web and node to be used with [opentelemetry-collector][opentelemetry-collector-url] - last tested with version **0.25.0**.

## Installation

Expand All @@ -15,7 +15,7 @@ npm install --save @opentelemetry/exporter-collector-grpc

## Traces in Node - GRPC

The CollectorTraceExporter in Node expects the URL to only be the hostname. It will not work with `/v1/trace`.
The CollectorTraceExporter in Node expects the URL to only be the hostname. It will not work with `/v1/traces`.

```js
const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/tracing');
Expand Down
Expand Up @@ -21,6 +21,7 @@ import {
import { MetricRecord, MetricExporter } from '@opentelemetry/metrics';
import { CollectorExporterConfigNode, ServiceClientType } from './types';
import { CollectorExporterNodeBase } from './CollectorExporterNodeBase';
import { getEnv } from '@opentelemetry/core';

const DEFAULT_SERVICE_NAME = 'collector-metric-exporter';
const DEFAULT_COLLECTOR_URL = 'localhost:4317';
Expand All @@ -47,11 +48,14 @@ export class CollectorMetricExporter
);
}

getDefaultUrl(config: CollectorExporterConfigNode): string {
if (!config.url) {
return DEFAULT_COLLECTOR_URL;
}
return config.url;
getDefaultUrl(config: CollectorExporterConfigNode) {
return typeof config.url === 'string'
? config.url
: getEnv().OTEL_EXPORTER_OTLP_METRICS_ENDPOINT.length > 0
? getEnv().OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
: getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0
? getEnv().OTEL_EXPORTER_OTLP_ENDPOINT
: DEFAULT_COLLECTOR_URL;
}

getDefaultServiceName(config: CollectorExporterConfigNode): string {
Expand Down
Expand Up @@ -21,6 +21,7 @@ import {
toCollectorExportTraceServiceRequest,
} from '@opentelemetry/exporter-collector';
import { CollectorExporterConfigNode, ServiceClientType } from './types';
import { getEnv } from '@opentelemetry/core';

const DEFAULT_SERVICE_NAME = 'collector-trace-exporter';
const DEFAULT_COLLECTOR_URL = 'localhost:4317';
Expand All @@ -40,11 +41,14 @@ export class CollectorTraceExporter
return toCollectorExportTraceServiceRequest(spans, this);
}

getDefaultUrl(config: CollectorExporterConfigNode): string {
if (!config.url) {
return DEFAULT_COLLECTOR_URL;
}
return config.url;
getDefaultUrl(config: CollectorExporterConfigNode) {
return typeof config.url === 'string'
? config.url
: getEnv().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT.length > 0
? getEnv().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
: getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0
? getEnv().OTEL_EXPORTER_OTLP_ENDPOINT
: DEFAULT_COLLECTOR_URL;
}

getDefaultServiceName(config: CollectorExporterConfigNode): string {
Expand Down

0 comments on commit 02239b5

Please sign in to comment.