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

Clean up Tracing API spec, clarify Tracer vs TracerProvider. #619

Merged
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7cdc7d3
Clean up Tracing API spec, clarify Tracer vs TracerProvider.
Oberon00 May 25, 2020
89816ca
Typos / grammar
Oberon00 May 25, 2020
156afbc
Fix linebreaks, singleton wording.
Oberon00 May 25, 2020
438d0ec
Fix link to context.
Oberon00 May 25, 2020
e0a1401
Typo.
Oberon00 May 25, 2020
f7a6135
Avoid word 'create' for Tracers.
Oberon00 May 25, 2020
977abff
More typos / grammar
Oberon00 May 26, 2020
394adaf
Clarify wording
Oberon00 May 26, 2020
1c50341
Clarify config updates/existing tracers.
Oberon00 May 26, 2020
385ef1e
Remove confusing word "state".
Oberon00 May 26, 2020
ecec9bf
Typo.
Oberon00 May 26, 2020
915d6af
Don't call library name+version mutable, simplify sentence.
Oberon00 May 26, 2020
815ee15
Reword placeholder span requirements.
Oberon00 May 26, 2020
b474d2c
Fix link anchor.
Oberon00 May 30, 2020
b12f036
Clean up/strip down multi-tracerprovider wording.
Oberon00 Jun 2, 2020
0cf54f9
Remove mostly redundant/unclear sentence about config/Provider.
Oberon00 Jun 2, 2020
6da477a
Remove about invalid current span.
Oberon00 Jun 3, 2020
c31b87c
Remove vague statelesness wording.
Oberon00 Jun 3, 2020
547d4ee
For context handling, only state that Tracers must delegate.
Oberon00 Jun 3, 2020
80877cb
Clarify Span creation restriction (only via Tracer).
Oberon00 Jun 4, 2020
9392125
Clean up sdk config spec.
Oberon00 Jun 4, 2020
b8c510c
Reword 'however' sentence.
Oberon00 Jun 5, 2020
95eb589
Add changelog entry.
Oberon00 Jun 10, 2020
1afc409
Merge branch 'master' into cleanup-trace-api
Oberon00 Jun 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,10 @@ the release.

## Unreleased

- Clarify Tracer vs TracerProvider in tracing API and SDK spec. Most importantly:
* Configuration should be stored not per Tracer but in the TracerProvider.
* Active spans are not per Tracer.

## v0.5.0 (06-02-2020)

- Define Log Data Model.
Expand Down
113 changes: 59 additions & 54 deletions specification/trace/api.md
Expand Up @@ -36,11 +36,12 @@ Table of Contents

</details>

Tracing API consist of a few main classes:
The Tracing API consist of these main classes:

- `Tracer` is used for all operations. See [Tracer](#tracer) section.
- `Span` is a mutable object storing information about the current operation
execution. See [Span](#span) section.
- [`TracerProvider`](#tracerprovider) is the entry point of the API.
It provides access to `Tracer`s.
- [`Tracer`](#tracer) is the class responsible for creating `Span`s.
- [`Span`](#span) is the API to trace an operation.

## Data types

Expand All @@ -66,27 +67,32 @@ A duration is the elapsed time between two events.
* The minimal precision is milliseconds.
* The maximal precision is nanoseconds.

## Tracer
## TracerProvider

`Tracer`s can be accessed with a `TracerProvider`.

The OpenTelemetry library achieves in-process context propagation of `Span`s by
way of the `Tracer`.
In implementations of the API, the `TracerProvider` is expected to be the
stateful object that holds any configuration.

The `Tracer` is responsible for tracking the currently active `Span`, and
exposes functions for creating and activating new `Span`s. The `Tracer` is
configured with `Propagator`s which support transferring span context across
process boundaries.
Normally, the `TracerProvider` is expected to be accessed from a central place.
Copy link
Member

Choose a reason for hiding this comment

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

Can we say the exact opposite? I am not winning the battle against globals, but I am going to continue fighting it. It's not "normal" to access a global, it's a provably bad design pattern. I would like OpenTelemetry specs to take a stance that "fall back to globals only when nothing else works", not as the first option.

Copy link
Member Author

Choose a reason for hiding this comment

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

I feel you, but I don't think this PR is the right place for such design decisions.

Copy link
Member

Choose a reason for hiding this comment

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

By the same logic, this PR is not a place to introduce this kind of language if it wasn't in the spec previously.

Copy link
Member Author

@Oberon00 Oberon00 Jun 5, 2020

Choose a reason for hiding this comment

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

It isn't the right place for that and it was not my intention to strengthen globals in the spec. The current wording even talks about "singletons", which are a design (anti-)pattern to prevent multiple instance of the same object, so I'd say I even moved the wording a bit away from "single global instance" here. See https://github.com/open-telemetry/opentelemetry-specification/blob/v0.5.0/specification/trace/api.md#obtaining-a-tracer

TracerProviders are generally expected to be used as singletons. Implementations SHOULD provide a single global default TracerProvider.

Copy link
Member

Choose a reason for hiding this comment

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

I don't consider singleton an anti-pattern - it simply means that there should be only one (*) instance of provider, which is true in most cases. Singleton != global, singletons can be managed properly as dependencies.

(*) as we discussed, the wording about situations where one needs multiple providers is pretty odd. E.g. in a JVM-based app server that runs multiple web apps (like Tomcat), absolutely nothing changes about having a single provider per web app, because class loaders perform the required isolation.

Copy link
Member Author

Choose a reason for hiding this comment

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

What is called singleton does imply global access. Sure there are other ways to ensure only one instance is created without also providing global access, but that's not the singleton design pattern anymore then.

Thus, the API SHOULD provide a way to set/register and access
a global default `TracerProvider`.

### Obtaining a Tracer
Notwithstanding any global `TracerProvider`, some applications may want to or
have to use multiple `TracerProvider` instances,
e.g. to have different configuration (like `SpanProcessor`s) for each
(and consequently for the `Tracer`s obtained from them),
or because its easier with dependency injection frameworks.
Thus, implementations of `TracerProvider` SHOULD allow creating an arbitrary
number of `TracerProvider` instances.

New `Tracer` instances can be created via a `TracerProvider` and its `getTracer`
function. This function expects two string arguments:
### TracerProvider operations

`TracerProvider`s are generally expected to be used as singletons. Implementations
SHOULD provide a single global default `TracerProvider`.
The `TracerProvider` MUST provide functions to:

Some applications may use multiple `TracerProvider` instances, e.g. to provide
different settings (e.g. `SpanProcessor`s) to each of those instances and -
in further consequence - to the `Tracer` instances created by them.
- Get a `Tracer`

That API MUST accept the following parameters:

- `name` (required): This name must identify the [instrumentation library](../overview.md#instrumentation-libraries)
(e.g. `io.opentelemetry.contrib.mongodb`) and *not* the instrumented library.
Expand All @@ -101,51 +107,46 @@ in further consequence - to the `Tracer` instances created by them.
- `version` (optional): Specifies the version of the instrumentation library
(e.g. `semver:1.0.0`).

Implementations might require the user to specify configuration properties at
`TracerProvider` creation time, or rely on external configuration, e.g. when using the
provider pattern.
It is unspecified whether or under which conditions the same or different
`Tracer` instances are returned from this functions.

Implementations MUST NOT require users to repeatedly obtain a `Tracer` again
with the same name+version to pick up configuration changes.
This can be achieved either by allowing to work with an outdated configuration or
by ensuring that new configuration applies also to previously returned `Tracer`s.

#### Runtimes with multiple deployments/applications
Note: This could, for example, be implemented by storing any mutable
configuration in the `TracerProvider` and having `Tracer` implementation objects
have a reference to the `TracerProvider` from which they were obtained.
If configuration must be stored per-tracer (such as disabling a certain tracer),
the tracer could, for example, do a look-up with its name+version in a map in
the `TracerProvider`, or the `TracerProvider` could maintain a registry of all
returned `Tracer`s and actively update their configuration if it changes.

Runtimes that support multiple deployments or applications might need to
provide a different `TracerProvider` instance to each deployment. To support this,
the global `TracerProvider` registry may delegate calls to create new instances of
`TracerProvider` to a separate `Provider` component, and the runtime may include
its own `Provider` implementation which returns a different `TracerProvider` for
each deployment.
## Tracer

The tracer is responsible for creating `Span`s.

`Provider` instances are registered with the API via some language-specific
mechanism, for instance the `ServiceLoader` class in Java.
Note that `Tracers` should usually *not* be responsible for configuration.
This should be the responsibility of the `TracerProvider` instead.

### Tracer operations

The `Tracer` MUST provide functions to:

- Create a new `Span`
- [Create a new `Span`](#span-creation) (see the section on `Span`)

The `Tracer` SHOULD provide methods to:

- Get the currently active `Span`
- Make a given `Span` as active

The `Tracer` MUST internally leverage the `Context` in order to get and set the
current `Span` state and how `Span`s are passed across process boundaries.

When getting the current span, the `Tracer` MUST return a placeholder `Span`
with an invalid `SpanContext` if there is no currently active `Span`.
- Mark a given `Span` as active

When creating a new `Span`, the `Tracer` MUST allow the caller to specify the
new `Span`'s parent in the form of a `Span` or `SpanContext`. The `Tracer`
SHOULD create each new `Span` as a child of its active `Span` unless an
Oberon00 marked this conversation as resolved.
Show resolved Hide resolved
explicit parent is provided or the option to create a span without a parent is
selected, or the current active `Span` is invalid.

The `Tracer` SHOULD provide a way to update its active `Span` and MAY provide
convenience functions to manage a `Span`'s lifetime and the scope in which a
`Span` is active. When an active `Span` is made inactive, the previously-active
`Span` SHOULD be made active. A `Span` maybe finished (i.e. have a non-null end
time) but still active. A `Span` may be active on one thread after it has been
made inactive on another.
The `Tracer` MUST delegate to the [`Context`](../context/context.md) to perform
these tasks, i.e. the above methods MUST do the same as a single equivalent
method of the Context management system.
In particular, this implies that the active span MUST not depend on the `Tracer`
that it is queried from/was set to, as long as the tracers were obtained from
the same `TracerProvider`.

## SpanContext

Expand Down Expand Up @@ -239,9 +240,13 @@ directly. All `Span`s MUST be created via a `Tracer`.

### Span Creation

Implementations MUST provide a way to create `Span`s via a `Tracer`. By default,
the currently active `Span` is set as the new `Span`'s parent. The `Tracer`
MAY provide other default options for newly created `Span`s.
There MUST NOT be any API for creating a `Span` other than with a [`Tracer`](#tracer).

When creating a new `Span`, the `Tracer` MUST allow the caller to specify the
new `Span`'s parent in the form of a `Span` or `SpanContext`. The `Tracer`
SHOULD create each new `Span` as a child of its active `Span`, unless an
explicit parent is provided or the option to create a span without a parent is
selected.

`Span` creation MUST NOT set the newly created `Span` as the currently
active `Span` by default, but this functionality MAY be offered additionally
Expand Down
28 changes: 13 additions & 15 deletions specification/trace/sdk.md
Expand Up @@ -142,15 +142,23 @@ TODO: Split out the parent handling.
## Tracer Creation

New `Tracer` instances are always created through a `TracerProvider` (see
[API](api.md#obtaining-a-tracer)). The `name` and `version` arguments
[API](api.md#tracerprovicer)). The `name` and `version` arguments
supplied to the `TracerProvider` must be used to create an
[`InstrumentationLibrary`][otep-83] instance which is stored on the created
`Tracer`.

All configuration objects (SDK specific) and extension points (span processors,
propagators) must be provided to the `TracerProvider`. `Tracer` instances must
not duplicate this data (unless for read-only access) to avoid that different
`Tracer` instances of a `TracerProvider` have different versions of these data.
Configuration (i.e., [Span processors](#span-processor) and [`Sampler`](#sampling))
MUST be managed solely by the `TracerProvider` and it MUST provide some way to
configure them, at least when creating or initializing it.

The TracerProvider MAY provide methods to update the configuration. If
configuration is updated (e.g., adding a `SpanProcessor`),
the updated configuration MUST also apply to all already returned `Tracers`
Copy link
Member

Choose a reason for hiding this comment

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

This can be achieved either by allowing to work with an outdated configuration or
by ensuring that new configuration applies also to previously returned Tracers.

From the API, I would expect a SHOULD and not a MUST here

Copy link
Member Author

@Oberon00 Oberon00 Jun 10, 2020

Choose a reason for hiding this comment

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

This is the SDK spec. The API spec allows an arbitrary implementation to "to work with an outdated configuration", but the spec for the official OpenTelemetry SDK can be stricter.

(i.e. it MUST NOT matter whether a `Tracer` was obtained from the
`TracerProvider` before or after the configuration change).
Note: Implementation-wise, this could mean that `Tracer` instances have a
reference to their `TracerProvider` and access configuration only via this
reference.

The readable representations of all `Span` instances created by a `Tracer` must
provide a `getInstrumentationLibrary` method that returns the
Expand All @@ -168,16 +176,6 @@ exportable representation and passing batches to exporters.
Span processors can be registered directly on SDK `TracerProvider` and they are
invoked in the same order as they were registered.

All `Tracer` instances created by a `TracerProvider` share the same span processors.
Changes to this collection reflect in all `Tracer` instances.
Implementation-wise, this could mean that `Tracer` instances have a reference to
their `TracerProvider` and can access span processor objects only via this
reference.

Manipulation of the span processors collection must only happen on `TracerProvider`
instances. This means methods like `addSpanProcessor` must be implemented on
`TracerProvider`.

Each processor registered on `TracerProvider` is a start of pipeline that consist
of span processor and optional exporter. SDK MUST allow to end each pipeline with
individual exporter.
Expand Down