Skip to content
Merged
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
2 changes: 1 addition & 1 deletion experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
### :rocket: Features

* feat(configuration): add Prometheus exporter support [#6400](https://github.com/open-telemetry/opentelemetry-js/pull/6400) @MikeGoldsmith

* feat(sampler-composite): add ComposableAnnotatingSampler and ComposableRuleBasedSampler [#6305](https://github.com/open-telemetry/opentelemetry-js/pull/6305) @trentm
* feat(configuration): parse config for rc 3 [#6304](https://github.com/open-telemetry/opentelemetry-js/pull/6304) @maryliag
* feat(instrumentation): use the `internals: true` option with import-in-the-middle hook, allowing instrumentations to hook internal files in ES modules [#6344](https://github.com/open-telemetry/opentelemetry-js/pull/6344) @trentm
* feat(api-logs,sdk-logs): add log exception support and mapping [#6379](https://github.com/open-telemetry/opentelemetry-js/issues/6379) @iblancasa
* feat(opentelemetry-sdk-node): set log provider for experimental start [#6407](https://github.com/open-telemetry/opentelemetry-js/pull/6407) @maryliag

### :bug: Bug Fixes

Expand Down
116 changes: 105 additions & 11 deletions experimental/packages/opentelemetry-sdk-node/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,36 @@
*/
import {
ConfigFactory,
ConfigurationModel,
createConfigFactory,
} from '@opentelemetry/configuration';
import { diag, DiagConsoleLogger } from '@opentelemetry/api';
import {
context,
diag,
DiagConsoleLogger,
propagation,
} from '@opentelemetry/api';
import {
getInstanceID,
getLogRecordProcessorsFromConfiguration,
getPropagatorFromConfiguration,
setupDefaultContextManager,
setupPropagator,
getResourceDetectorsFromConfiguration,
getResourceFromConfiguration,
} from './utils';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import type { SDKOptions } from './types';
import type { SDKComponents, SDKOptions } from './types';
import { LoggerProvider } from '@opentelemetry/sdk-logs';
import { logs } from '@opentelemetry/api-logs';
import {
defaultResource,
detectResources,
Resource,
ResourceDetectionConfig,
ResourceDetector,
resourceFromAttributes,
} from '@opentelemetry/resources';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { ATTR_SERVICE_INSTANCE_ID } from './semconv';

/**
* @experimental Function to start the OpenTelemetry Node SDK
Expand All @@ -47,20 +67,94 @@ export function startNodeSDK(sdkOptions: SDKOptions): {
registerInstrumentations({
instrumentations: sdkOptions?.instrumentations?.flat() ?? [],
});
setupDefaultContextManager();
setupPropagator(
sdkOptions?.textMapPropagator === null
? null // null means don't set.
: (sdkOptions?.textMapPropagator ??
getPropagatorFromConfiguration(config))
);

const components = create(config, sdkOptions);
context.setGlobalContextManager(components.contextManager);
if (components.loggerProvider) {
logs.setGlobalLoggerProvider(components.loggerProvider);
}
if (components.propagator) {
propagation.setGlobalPropagator(components.propagator);
}

const shutdownFn = async () => {
const promises: Promise<unknown>[] = [];
if (components.loggerProvider) {
promises.push(components.loggerProvider.shutdown());
}
await Promise.all(promises);
};
return { shutdown: shutdownFn };
}
const NOOP_SDK = {
shutdown: async () => {},
};

/**
* Interpret configuration model and return SDK components.
*/
function create(
config: ConfigurationModel,
sdkOptions: SDKOptions
): SDKComponents {
const defaultContextManager = new AsyncLocalStorageContextManager();
defaultContextManager.enable();
const components: SDKComponents = {
contextManager: defaultContextManager,
};
const resource = setupResource(config, sdkOptions);

const propagator =
sdkOptions?.textMapPropagator === null
? null
: (sdkOptions?.textMapPropagator ??
getPropagatorFromConfiguration(config));
if (propagator) {
components.propagator = propagator;
}

const logProcessors = getLogRecordProcessorsFromConfiguration(config);
if (logProcessors) {
const loggerProvider = new LoggerProvider({
resource: resource,
processors: logProcessors,
});
components.loggerProvider = loggerProvider;
}

return components;
}

export function setupResource(
config: ConfigurationModel,
sdkOptions: SDKOptions
): Resource {
let resource: Resource =
getResourceFromConfiguration(config) ?? defaultResource();
let resourceDetectors: ResourceDetector[] = [];

if (sdkOptions.resourceDetectors != null) {
resourceDetectors = sdkOptions.resourceDetectors;
} else if (config.node_resource_detectors) {
resourceDetectors = getResourceDetectorsFromConfiguration(config);
}

if (resourceDetectors.length > 0) {
const internalConfig: ResourceDetectionConfig = {
detectors: resourceDetectors,
};
resource = resource.merge(detectResources(internalConfig));
}

const instanceId = getInstanceID(config);
resource =
instanceId === undefined
? resource
: resource.merge(
resourceFromAttributes({
[ATTR_SERVICE_INSTANCE_ID]: instanceId,
})
);

return resource;
}
9 changes: 8 additions & 1 deletion experimental/packages/opentelemetry-sdk-node/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { ContextManager } from '@opentelemetry/api';
import { TextMapPropagator } from '@opentelemetry/api';
import { Instrumentation } from '@opentelemetry/instrumentation';
import { Resource, ResourceDetector } from '@opentelemetry/resources';
import { LogRecordProcessor } from '@opentelemetry/sdk-logs';
import { LoggerProvider, LogRecordProcessor } from '@opentelemetry/sdk-logs';
import { IMetricReader, ViewOptions } from '@opentelemetry/sdk-metrics';
import {
Sampler,
Expand Down Expand Up @@ -56,5 +56,12 @@ export interface NodeSDKConfiguration {
*/
export interface SDKOptions {
instrumentations?: (Instrumentation | Instrumentation[])[];
resourceDetectors?: ResourceDetector[];
textMapPropagator?: TextMapPropagator | null;
}

export interface SDKComponents {
contextManager: ContextManager;
loggerProvider?: LoggerProvider;
propagator?: TextMapPropagator;
}
155 changes: 148 additions & 7 deletions experimental/packages/opentelemetry-sdk-node/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ import { OTLPTraceExporter as OTLPHttpTraceExporter } from '@opentelemetry/expor
import { OTLPTraceExporter as OTLPGrpcTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import {
DetectedResourceAttributes,
envDetector,
hostDetector,
osDetector,
processDetector,
Resource,
ResourceDetector,
resourceFromAttributes,
serviceInstanceIdDetector,
} from '@opentelemetry/resources';
import {
Expand All @@ -51,7 +54,14 @@ import {
import { B3InjectEncoding, B3Propagator } from '@opentelemetry/propagator-b3';
import { JaegerPropagator } from '@opentelemetry/propagator-jaeger';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
import { ConfigurationModel } from '@opentelemetry/configuration';
import { OTLPLogExporter as OTLPHttpLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { OTLPLogExporter as OTLPGrpcLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
import {
ConfigurationModel,
LogRecordExporterModel,
} from '@opentelemetry/configuration';
import {
IMetricReader,
PeriodicExportingMetricReader,
Expand All @@ -63,8 +73,11 @@ import { OTLPMetricExporter as OTLPProtoMetricExporter } from '@opentelemetry/ex
import {
BatchLogRecordProcessor,
BufferConfig,
ConsoleLogRecordExporter,
LogRecordExporter,
LoggerProviderConfig,
LogRecordProcessor,
SimpleLogRecordProcessor,
} from '@opentelemetry/sdk-logs';

const RESOURCE_DETECTOR_ENVIRONMENT = 'env';
Expand All @@ -73,6 +86,22 @@ const RESOURCE_DETECTOR_OS = 'os';
const RESOURCE_DETECTOR_PROCESS = 'process';
const RESOURCE_DETECTOR_SERVICE_INSTANCE_ID = 'serviceinstance';

export function getResourceFromConfiguration(
config: ConfigurationModel
): Resource | undefined {
if (config.resource && config.resource.attributes) {
const attr: DetectedResourceAttributes = {};
for (let i = 0; i < config.resource.attributes.length; i++) {
const a = config.resource.attributes[i];
attr[a.name] = a.value;
}
return resourceFromAttributes(attr, {
schemaUrl: config.resource.schema_url,
});
}
return undefined;
}

export function getResourceDetectorsFromEnv(): Array<ResourceDetector> {
// When updating this list, make sure to also update the section `resourceDetectors` on README.
const resourceDetectors = new Map<string, ResourceDetector>([
Expand Down Expand Up @@ -106,6 +135,37 @@ export function getResourceDetectorsFromEnv(): Array<ResourceDetector> {
});
}

export function getResourceDetectorsFromConfiguration(
config: ConfigurationModel
): Array<ResourceDetector> {
// When updating this list, make sure to also update the section `resourceDetectors` on README.
const resourceDetectors = new Map<string, ResourceDetector>([
[RESOURCE_DETECTOR_HOST, hostDetector],
[RESOURCE_DETECTOR_OS, osDetector],
[RESOURCE_DETECTOR_SERVICE_INSTANCE_ID, serviceInstanceIdDetector],
[RESOURCE_DETECTOR_PROCESS, processDetector],
[RESOURCE_DETECTOR_ENVIRONMENT, envDetector],
]);

const resourceDetectorsFromConfig = config.node_resource_detectors ?? [];

if (resourceDetectorsFromConfig.includes('all')) {
return [...resourceDetectors.values()].flat();
}

if (resourceDetectorsFromConfig.includes('none')) {
return [];
}

return resourceDetectorsFromConfig.flatMap(detector => {
const resourceDetector = resourceDetectors.get(detector);
if (!resourceDetector) {
diag.warn(`Invalid resource detector "${detector}" specified`);
}
return resourceDetector || [];
});
}

export function getOtlpProtocolFromEnv(): string {
return (
getStringFromEnv('OTEL_EXPORTER_OTLP_TRACES_PROTOCOL') ??
Expand Down Expand Up @@ -325,12 +385,6 @@ export function setupContextManager(
context.setGlobalContextManager(contextManager);
}

export function setupDefaultContextManager() {
const defaultContextManager = new AsyncLocalStorageContextManager();
defaultContextManager.enable();
context.setGlobalContextManager(defaultContextManager);
}

export function setupPropagator(
propagator: TextMapPropagator | null | undefined
) {
Expand Down Expand Up @@ -494,3 +548,90 @@ export function getBatchLogRecordProcessorFromEnv(
getBatchLogRecordProcessorConfigFromEnv()
);
}

export function getLogRecordExporter(
exporter: LogRecordExporterModel
): LogRecordExporter | undefined {
if (exporter.otlp_http) {
const encoding = exporter.otlp_http.encoding;
if (encoding === 'json') {
return new OTLPHttpLogExporter({
compression:
exporter.otlp_http.compression === 'gzip'
? CompressionAlgorithm.GZIP
: CompressionAlgorithm.NONE,
});
}
if (encoding === 'protobuf') {
return new OTLPProtoLogExporter({
compression:
exporter.otlp_http.compression === 'gzip'
? CompressionAlgorithm.GZIP
: CompressionAlgorithm.NONE,
});
}
diag.warn(
`Unsupported OTLP logs encoding: ${encoding}. Using http/protobuf.`
);
return new OTLPProtoLogExporter({
compression:
exporter.otlp_http.compression === 'gzip'
? CompressionAlgorithm.GZIP
: CompressionAlgorithm.NONE,
});
} else if (exporter.otlp_grpc) {
return new OTLPGrpcLogExporter({
compression:
exporter.otlp_grpc.compression === 'gzip'
? CompressionAlgorithm.GZIP
: CompressionAlgorithm.NONE,
});
} else if (exporter.console) {
return new ConsoleLogRecordExporter();
}
diag.warn(`Unsupported Exporter value. No Log Record Exporter registered`);
return undefined;
}

export function getLogRecordProcessorsFromConfiguration(
config: ConfigurationModel
): LogRecordProcessor[] | undefined {
const logRecordProcessors: LogRecordProcessor[] = [];
config.logger_provider?.processors?.forEach(processor => {
if (processor.batch) {
const exporter = getLogRecordExporter(processor.batch.exporter);
if (exporter) {
logRecordProcessors.push(
new BatchLogRecordProcessor(exporter, {
maxQueueSize: processor.batch.max_queue_size,
maxExportBatchSize: processor.batch.max_export_batch_size,
scheduledDelayMillis: processor.batch.schedule_delay,
exportTimeoutMillis: processor.batch.export_timeout,
})
);
}
}
if (processor.simple) {
const exporter = getLogRecordExporter(processor.simple.exporter);
if (exporter) {
logRecordProcessors.push(new SimpleLogRecordProcessor(exporter));
}
}
});
if (logRecordProcessors.length > 0) {
return logRecordProcessors;
}
return undefined;
}

export function getInstanceID(config: ConfigurationModel): string | undefined {
if (config.resource?.attributes) {
for (let i = 0; i < config.resource.attributes.length; i++) {
const element = config.resource.attributes[i];
if (element.name === 'service.instance.id') {
return element.value?.toString();
}
}
}
return undefined;
}
Loading