Skip to content
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* refactor(configuration): dont have a default value for node resource detectors [#6131](https://github.com/open-telemetry/opentelemetry-js/pull/6131) @maryliag
* feat(configuration): doesnt set meter,tracer,logger provider by default [#6130](https://github.com/open-telemetry/opentelemetry-js/pull/6130) @maryliag
* feat(opentelemetry-sdk-node): set instrumentation and propagators for experimental start [#6148](https://github.com/open-telemetry/opentelemetry-js/pull/6148) @maryliag
* feat(opentelemetry-sdk-node): set resources and log provider for experimental start [#6152](https://github.com/open-telemetry/opentelemetry-js/pull/6152) @maryliag

### :bug: Bug Fixes

Expand Down
151 changes: 151 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,46 @@
*/
import {
ConfigFactory,
ConfigurationModel,
createConfigFactory,
LogRecordExporterModel,
} from '@opentelemetry/configuration';
import { diag, DiagConsoleLogger } from '@opentelemetry/api';
import {
getPropagatorFromConfiguration,
getResourceDetectorsFromConfiguration,
setupDefaultContextManager,
setupPropagator,
} from './utils';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import type { SDKOptions } from './types';
import {
BatchLogRecordProcessor,
ConsoleLogRecordExporter,
LoggerProvider,
LogRecordExporter,
LogRecordProcessor,
SimpleLogRecordProcessor,
} from '@opentelemetry/sdk-logs';
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 { logs } from '@opentelemetry/api-logs';
import {
defaultResource,
detectResources,
envDetector,
hostDetector,
osDetector,
processDetector,
Resource,
ResourceDetectionConfig,
ResourceDetector,
resourceFromAttributes,
serviceInstanceIdDetector,
} from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

/**
* @experimental Function to start the OpenTelemetry Node SDK
Expand Down Expand Up @@ -54,13 +84,134 @@ export function startNodeSDK(sdkOptions: SDKOptions): {
: (sdkOptions?.textMapPropagator ??
getPropagatorFromConfiguration(config))
);
const resource = setupResource(config, sdkOptions);
const loggerProvider = setupLoggerProvider(config, sdkOptions, resource);

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

function setupResource(
config: ConfigurationModel,
sdkOptions: SDKOptions
): Resource {
let resource: Resource = sdkOptions.resource ?? defaultResource();
const autoDetectResources = sdkOptions.autoDetectResources ?? true;
let resourceDetectors: ResourceDetector[];

if (!autoDetectResources) {
resourceDetectors = [];
} else if (sdkOptions.resourceDetectors != null) {
resourceDetectors = sdkOptions.resourceDetectors;
} else if (config.node_resource_detectors) {
resourceDetectors = getResourceDetectorsFromConfiguration(config);
} else {
resourceDetectors = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note for reviewers: this is the only thing that I changed compared to the "original".
Before we had only env, process and host, but if the value was all, or there was no value on env variable it was setting to all, and that included os and service instance id. We do say on our docs that the default is to have all detectors on.
We didn't add os and service instance id because we wanted to have them baking for awhile before adding, which I think we can do now.

But let me know if you don't think is a good idea to turn any of them by default

envDetector,
processDetector,
hostDetector,
osDetector,
serviceInstanceIdDetector,
];
}

if (autoDetectResources) {
const internalConfig: ResourceDetectionConfig = {
detectors: resourceDetectors,
};

resource = resource.merge(detectResources(internalConfig));
}

resource =
sdkOptions.serviceName === undefined
? resource
: resource.merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: sdkOptions.serviceName,
})
);

return resource;
}

function setupLoggerProvider(
config: ConfigurationModel,
sdkOptions: SDKOptions,
resource: Resource | undefined
): LoggerProvider | undefined {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
): LoggerProvider | undefined {
): LoggerProvider {

Is there a scenario where setupLoggerProvider would be undefined? setupResource only returns a Resource not undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if you don't have any log provider on env variables or config file, there is no logger provider to be setup, so it will return undefined

const logProcessors =
sdkOptions.logRecordProcessors ??
getLogRecordProcessorsFromConfiguration(config);

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

logs.setGlobalLoggerProvider(loggerProvider);
return loggerProvider;
}
return undefined;
}

function getExporterType(exporter: LogRecordExporterModel): LogRecordExporter {
if (exporter.otlp_http) {
if (exporter.otlp_http.encoding === 'json') {
return new OTLPHttpLogExporter({
compression:
exporter.otlp_http.compression === 'gzip'
? CompressionAlgorithm.GZIP
: CompressionAlgorithm.NONE,
});
}
if (exporter.otlp_http.encoding === 'protobuf') {
return new OTLPProtoLogExporter();
}
diag.warn(`Unsupported OTLP logs protocol. Using http/protobuf.`);
return new OTLPProtoLogExporter();
} else if (exporter.otlp_grpc) {
return new OTLPGrpcLogExporter();
} else if (exporter.console) {
return new ConsoleLogRecordExporter();
}
diag.warn(`Unsupported Exporter value. Using OTLP http/protobuf.`);
Copy link
Member

Choose a reason for hiding this comment

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

🤔 If someone uses a config file but neglects to specify a valid exporter (otlp_http, otlp_grpc, otlp_file/development(soon), console)... should we default an exporter for them or should we consider it a noop because it's invalid? It's not clear to me.

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 believe I saw the default should be otlp_http in this case, so I kept at that.

return new OTLPProtoLogExporter();
}

function getLogRecordProcessorsFromConfiguration(
config: ConfigurationModel
): LogRecordProcessor[] | undefined {
const logRecordProcessors: LogRecordProcessor[] = [];
config.logger_provider?.processors?.forEach(processor => {
if (processor.batch) {
logRecordProcessors.push(
new BatchLogRecordProcessor(getExporterType(processor.batch.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) {
logRecordProcessors.push(
new SimpleLogRecordProcessor(getExporterType(processor.simple.exporter))
);
}
});
if (logRecordProcessors.length > 0) {
return logRecordProcessors;
}
return undefined;
}
5 changes: 5 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export interface NodeSDKConfiguration {
* @experimental Options for new experimental SDK setup
*/
export interface SDKOptions {
autoDetectResources?: boolean;
instrumentations?: (Instrumentation | Instrumentation[])[];
logRecordProcessors?: LogRecordProcessor[];
resource?: Resource;
resourceDetectors?: ResourceDetector[];
serviceName?: string;
textMapPropagator?: TextMapPropagator | null;
}
31 changes: 31 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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_ENVIRONMENT, envDetector],
[RESOURCE_DETECTOR_HOST, hostDetector],
[RESOURCE_DETECTOR_OS, osDetector],
[RESOURCE_DETECTOR_SERVICE_INSTANCE_ID, serviceInstanceIdDetector],
[RESOURCE_DETECTOR_PROCESS, processDetector],
]);

const resourceDetectorsFromConfig = config.node_resource_detectors ?? ['all'];

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