Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define instrumentation configuration API #4128

Merged
merged 8 commits into from
Aug 15, 2024
248 changes: 192 additions & 56 deletions specification/configuration/file-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,28 @@ linkTitle: File
<!-- toc -->

- [Overview](#overview)
- [Configuration Model](#configuration-model)
* [Stability Definition](#stability-definition)
- [Configuration model](#configuration-model)
* [Stability definition](#stability-definition)
- [Configuration file](#configuration-file)
* [YAML file format](#yaml-file-format)
* [Environment variable substitution](#environment-variable-substitution)
- [SDK Configuration](#sdk-configuration)
* [In-Memory Configuration Model](#in-memory-configuration-model)
* [SDK Extension Components](#sdk-extension-components)
+ [Component Provider](#component-provider)
+ [Create Plugin](#create-plugin)
* [Operations](#operations)
- [Instrumentation configuration API](#instrumentation-configuration-api)
* [ConfigProvider](#configprovider)
+ [ConfigProvider operations](#configprovider-operations)
- [Get instrumentation config](#get-instrumentation-config)
- [Convenience functions](#convenience-functions)
* [ConfigProperties](#configproperties)
- [Configuration SDK](#configuration-sdk)
* [In-Memory configuration model](#in-memory-configuration-model)
* [SDK ConfigProvider](#sdk-configprovider)
* [SDK extension components](#sdk-extension-components)
+ [ComponentProvider](#componentprovider)
- [ComponentsProvider operations](#componentsprovider-operations)
* [Create Plugin](#create-plugin)
* [File config operations](#file-config-operations)
+ [Parse](#parse)
+ [Create](#create)
+ [Register Component Provider](#register-component-provider)
+ [Register ComponentProvider](#register-componentprovider)
- [Examples](#examples)
* [Via File Configuration API](#via-file-configuration-api)
* [Via OTEL_EXPERIMENTAL_CONFIG_FILE](#via-otel_experimental_config_file)
Expand All @@ -38,18 +46,29 @@ the [environment variable](sdk-environment-variables.md) based scheme, and
language agnostic in a way not possible
with [programmatic configuration](sdk-configuration.md#programmatic).

File configuration defines a [Configuration Model](#configuration-model),
which can be expressed in a [configuration file](#configuration-file).
Configuration files can be validated against the Configuration Schema, and
interpreted to produce configured OpenTelemetry components.
File configuration consists of the following main components:

## Configuration Model
* [Configuration model](#configuration-model) defines the data model for
configuration.
* [Configuration file](#configuration-file) defines how the configuration model
is represented in files.
* [Instrumentation configuration API](#instrumentation-configuration-api) allows
instrumentation libraries to read relevant configuration options during
initialization.
* [Configuration SDK](#configuration-sdk) defines SDK capabilities around file
configuration, including an In-Memory configuration model, support for
referencing custom extension plugin interfaces in configuration files, and
operations to parse and interpret configuration files.
* [Examples](#examples) end to end examples demonstrating file configuration
concepts.

## Configuration model

The configuration model is defined
in [opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
using the [JSON Schema](https://json-schema.org/).

### Stability Definition
### Stability definition

TODO: define stability guarantees and backwards compatibility

Expand All @@ -60,6 +79,8 @@ the [Configuration Model](#configuration-model).

Configuration files SHOULD use one the following serialization formats:

* [YAML file format](#yaml-file-format)

### YAML file format

[YAML](https://yaml.org/spec/1.2.2/) configuration files SHOULD follow YAML spec
Expand Down Expand Up @@ -107,10 +128,10 @@ place. This ensures the environment string representation of boolean, integer,
or floating point fields can be properly converted to expected types.

It MUST NOT be possible to inject YAML structures by environment variables. For
example, references to `INVALID_MAP_VALUE` environment variable below.
example, see references to `INVALID_MAP_VALUE` environment variable below.

It MUST NOT be possible to inject environment variable by environment variables.
For example, references to `DO_NOT_REPLACE_ME` environment variable below.
For example, see references to `DO_NOT_REPLACE_ME` environment variable below.

For example, consider the following environment variables,
and [YAML](#yaml-file-format) configuration file:
Expand Down Expand Up @@ -165,20 +186,129 @@ ${STRING_VALUE}: value # Interpreted as type string, tag
recursive_key: ${DO_NOT_REPLACE_ME} # Interpreted as type string, tag URI tag:yaml.org,2002:str
```

## SDK Configuration
## Instrumentation configuration API
jack-berg marked this conversation as resolved.
Show resolved Hide resolved

The instrumentation configuration API (referred to as the API)
allows [instrumentation libraries](../glossary.md#instrumentation-library) to
participate in file configuration by reading relevant configuration options
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
during initialization. It consists of the following main components:

* [ConfigProvider](#configprovider) is the entry point of the API.
* [ConfigProperties](#configproperties) is a programmatic representation of a
file configuration node.

### ConfigProvider

`ConfigProvider` provides access to configuration properties relevant to
instrumentation.

Normally, the `ConfigProvider` is expected to be accessed from a central place.
Thus, the API should provide a way to set/register and access a global
default `ConfigProvider`.
jack-berg marked this conversation as resolved.
Show resolved Hide resolved

#### ConfigProvider operations

The `ConfigProvider` MUST provide the following functions:

* [Get instrumentation config](#get-instrumentation-config)
jack-berg marked this conversation as resolved.
Show resolved Hide resolved

The `ConfigProvider` MAY provide the following:

* [Convenience functions](#convenience-functions)

##### Get instrumentation config

Obtain configuration relevant to instrumentation libraries.

**Returns:** [`ConfigProperties`](#configproperties) representing
the [`.instrumentation`](https://github.com/open-telemetry/opentelemetry-configuration/blob/670901762dd5cce1eecee423b8660e69f71ef4be/examples/kitchen-sink.yaml#L438-L439)
file configuration mapping node.

Get instrumentation config MUST return nil, null, or undefined (based on what is
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
idiomatic in the language ecosystem) if file configuration is not used or if
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
the `.instrumentation` node is not set.

##### Convenience functions

Convenience functions help instrumentation libraries interpret the contents
of [Get instrumentation config](#get-instrumentation-config). Convenience
functions shift the burden of schema knowledge from instrumentation libraries to
the OpenTelemetry API authors.

For example, consider an HTTP client instrumentation library trying to read the
set of request captured headers
at [`.instrumentation.general.http.client.request_captured_headers`](https://github.com/open-telemetry/opentelemetry-configuration/blob/670901762dd5cce1eecee423b8660e69f71ef4be/examples/kitchen-sink.yaml#L465-L467).
Rather than requiring the instrumentation library to
call [Get instrumentation config](#get-instrumentation-config) and
access `.general.http.client.request_captured_headers` on the
resulting `ConfigProperties`, a convenience function provides direct access with
type safety:

```java
@Nullable
public static List<String> httpClientRequestCapturedHeaders(ConfigProvider configProvider);
```

Convenience functions MAY be part of `ConfigProvider`, or be pure functions
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
accepting `ConfigProvider` as an argument.

### ConfigProperties

`ConfigProperties` is a programmatic representation of a file configuration
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
mapping node (i.e. a YAML mapping node).

`ConfigProperties` MUST provide accessors for reading all properties from the
mapping node it represents, including:

* scalars (string, boolean, double precision floating point, 64-bit integer)
* mappings, which SHOULD be represented as `ConfigProperties`
* sequences of scalars
* sequences of mappings, which SHOULD be represented as `ConfigProperties`
* the set of properties

`ConfigProperties` SHOULD provide access to properties in a type safe manner,
based on what is idiomatic in the language.

`ConfigProperties` SHOULD allow a caller to determine if a property is present
with a null value, versus not set.

SDK configuration defines the interfaces and operations that SDKs are expected
to expose to enable file based configuration.
## Configuration SDK

### In-Memory Configuration Model
The configuration SDK (referred to as the SDK) is an implementation
of [Instrumenation Config API](#instrumentation-configuration-api) and other
user facing file configuration capabilities. It consists of the following main
components:

* [In-Memory configuration model](#in-memory-configuration-model) is an
in-memory representation of the configuration model.
* [SDK ConfigProvider](#sdk-configprovider) defines the SDK implementation
of `ConfigProvider`.
* [SDK extension components](#sdk-extension-components) defines how users and
libraries extend file configuration with custom SDK extension plugin
interfaces (exporters, processors, etc).
* [File config operations](#file-config-operations) defines user APIs to parse
configuration files and produce SDK components from their contents.

### In-Memory configuration model

SDKs SHOULD provide an in-memory representation of
the [Configuration Model](#configuration-model). In general, SDKs are encouraged
to provide this in-memory representation in a manner that is idiomatic for their
language. If an SDK needs to expose a class or interface, the
name `Configuration` is RECOMMENDED.
the [Configuration Model](#configuration-model).
Whereas [`ConfigProperties`](#configproperties) is a schemaless representation
of any mapping node from a configuration file, the in-memory configuration model
SHOULD reflect the schema of the configuration model.

SDKs are encouraged to provide this in-memory representation in a manner that is
idiomatic for their language. If an SDK needs to expose a class or interface,
the name `Configuration` is RECOMMENDED.

### SDK ConfigProvider

### SDK Extension Components
The SDK implementation of [`ConfigProvider`](#configprovider) MUST be created
using a [`ConfigProperties`](#configproperties) representing
the [`.instrumentation`](https://github.com/open-telemetry/opentelemetry-configuration/blob/670901762dd5cce1eecee423b8660e69f71ef4be/examples/kitchen-sink.yaml#L438-L439)
mapping node from a configuration file.

### SDK extension components

The SDK supports a variety of
extension [plugin interfaces](../glossary.md#sdk-plugins), allowing users and
Expand Down Expand Up @@ -206,66 +336,68 @@ tracer_provider:
Here we specify that the tracer provider has a batch span processor
paired with a custom span exporter named `my-exporter`, which is configured
with `config-parameter: value`. For this configuration to succeed,
a [component provider](#component-provider) must
be [registered](#register-component-provider) with `type: SpanExporter`,
a [`ComponentProvider`](#componentprovider) must
be [registered](#register-componentprovider) with `type: SpanExporter`,
and `name: my-exporter`. When [parse](#parse) is called, the implementation will
encounter `my-exporter` and translate the corresponding configuration to an
equivalent generic `properties` representation (
equivalent [`ConfigProperties`](#configproperties) representation (
i.e. `properties: {config-parameter: value}`). When [create](#create) is called,
the implementation will encounter `my-exporter` and
invoke [create plugin](#create-plugin) on the registered component provider
with the configuration `properties` determined during `parse`.
invoke [create plugin](#create-plugin) on the registered `ComponentProvider`with
the `ConfigProperties` determined during `parse`.

Given the inherent differences across languages, the details of extension
component mechanisms are likely to vary to a greater degree than is the case
with other APIs defined by OpenTelemetry. This is to be expected and is
acceptable so long as the implementation results in the defined behaviors.

#### Component Provider
#### ComponentProvider

A component provider is responsible for interpreting configuration and returning
A `ComponentProvider` is responsible for interpreting configuration and returning
an implementation of a particular type of SDK extension plugin interface.

Component providers are registered with an SDK implementation of configuration
via [register](#register-component-provider). This MAY be done automatically or
`ComponentProvider`s are registered with an SDK implementation of configuration
via [register](#register-componentprovider). This MAY be done automatically or
require manual intervention by the user based on what is possible and idiomatic
in the language ecosystem. For example in Java, component providers might be
in the language ecosystem. For example in Java, `ComponentProvider`s might be
registered automatically using
the [service provider interface (SPI)](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html)
mechanism.

See [create](#create), which details component provider usage in file
See [create](#create), which details `ComponentProvider` usage in file
configuration interpretation.

#### Create Plugin
##### ComponentsProvider operations

The `ComponentsProvider` MUST provide the following functions:

* [Create Plugin](#create-plugin)

###### Create Plugin

Interpret configuration to create a instance of a SDK extension plugin
interface.

**Parameters:**

* `properties` - The configuration properties. Properties MUST fully represent
the configuration as specified in
the [configuration file](#configuration-file), including the ability to access
scalars, mappings, and sequences (of scalars and other structures). It MUST be
possible to determine if a particular property is present. It SHOULD be
possible to access properties in a type safe manner, based on what is idiomatic
in the language.
* `properties` - The [`ConfigProperties`](#configproperties) representing the
configuration specified for the component in
the [configuration file](#configuration-file).

**Returns:** A configured SDK extension plugin interface implementation.

The plugin interface MAY have properties which are optional or required, and
have specific requirements around type or format. The set of properties a
component provider accepts, along with their requirement level and expected
type, comprise a configuration schema. A component provider SHOULD document its
`ComponentProvider` accepts, along with their requirement level and expected
type, comprise a configuration schema. A `ComponentProvider` SHOULD document its
configuration schema.

When Create Plugin is invoked, the component provider interprets `properties`
When Create Plugin is invoked, the `ComponentProvider` interprets `properties`
and attempts to extract data according to its configuration schema. If this
fails (e.g. a required property is not present, a type is mismatches, etc.),
Create Plugin SHOULD return an error.

### Operations
### File config operations

SDK implementations of configuration MUST provide the following operations.

Expand Down Expand Up @@ -322,6 +454,7 @@ Interpret [configuration model](#in-memory-configuration-model) and return SDK c
* `MeterProvider`
* `LoggerProvider`
* `Propagators`
* `ConfigProvider`

The multiple responses MAY be returned using a tuple, or some other data
structure encapsulating the components.
Expand All @@ -338,9 +471,9 @@ unset, Create fails fast since there is no default value for `exporter`.
When encountering a reference to
a [SDK extension component](#sdk-extension-components) which is not built in to
the SDK, Create MUST resolve the component using [Create Plugin](#create-plugin)
of the [component provider](#component-provider) of the corresponding `type`
and `name` used to [register](#register-component-provider), including the
configuration `properties` as an argument. If no component provider is
of the [`ComponentProvider`](#componentprovider) of the corresponding `type`
and `name` used to [register](#register-componentprovider), including the
configuration `properties` as an argument. If no `ComponentProvider` is
registered with the `type` and `name`, Create SHOULD return an error.
If [Create Plugin](#create-plugin) returns an error, Create SHOULD propagate the
error.
Expand All @@ -351,14 +484,14 @@ initialization [error handling principles](../error-handling.md#basic-error-hand

TODO: define behavior if some portion of configuration model is not supported

#### Register Component Provider
#### Register ComponentProvider

The file configuration implementation MUST provide a mechanism to
register [component providers](#component-provider).
The SDK MUST provide a mechanism to
register [`ComponentProvider`](#componentprovider).

**Parameters:**

* `component_provider` - The [component provider](#component-provider).
* `component_provider` - The `ComponentProvider`.
* `type` - The type of plugin interface it provides (e.g. SpanExporter, Sampler,
etc).
* `name` - The name used to identify the type of component. This is used
Expand Down Expand Up @@ -395,6 +528,7 @@ TracerProvider tracerProvider = openTelemetry.getTracerProvider();
MeterProvider meterProvider = openTelemetry.getMeterProvider();
LoggerProvider loggerProvider = openTelemetry.getLogsBridge();
ContextPropagators propagators = openTelemetry.getPropagators();
ConfigProvider configProvider = openTelemetry.getConfigProvider();
```

A more complex case might consist of parsing multiple configuration files from
Expand Down Expand Up @@ -422,6 +556,7 @@ TracerProvider tracerProvider = openTelemetry.getTracerProvider();
MeterProvider meterProvider = openTelemetry.getMeterProvider();
LoggerProvider loggerProvider = openTelemetry.getLogsBridge();
ContextPropagators propagators = openTelemetry.getPropagators();
ConfigProvider configProvider = openTelemetry.getConfigProvider();
```

### Via OTEL_EXPERIMENTAL_CONFIG_FILE
Expand All @@ -447,6 +582,7 @@ TracerProvider tracerProvider = openTelemetry.getTracerProvider();
MeterProvider meterProvider = openTelemetry.getMeterProvider();
LoggerProvider loggerProvider = openTelemetry.getLogsBridge();
ContextPropagators propagators = openTelemetry.getPropagators();
ConfigProvider configProvider = openTelemetry.getConfigProvider();
```

If using auto-instrumentation, this initialization flow might occur
Expand Down
Loading