Skip to content

Commit

Permalink
Merge branch 'main' into Update-test-harness-#1467
Browse files Browse the repository at this point in the history
  • Loading branch information
toddbaert authored Jan 15, 2025
2 parents 1f1a719 + 6c673d7 commit 61837fa
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
23 changes: 22 additions & 1 deletion specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@
{
"id": "Requirement 4.1.1",
"machine_id": "requirement_4_1_1",
"content": "Hook context MUST provide: the `flag key`, `flag value type`, `evaluation context`, and the `default value`.",
"content": "Hook context MUST provide: the `flag key`, `flag value type`, `evaluation context`, `default value`, and `hook data`.",
"RFC 2119 keyword": "MUST",
"children": []
},
Expand Down Expand Up @@ -718,6 +718,13 @@
}
]
},
{
"id": "Requirement 4.1.5",
"machine_id": "requirement_4_1_5",
"content": "The `hook data` MUST be mutable.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 4.2.1",
"machine_id": "requirement_4_2_1",
Expand Down Expand Up @@ -761,6 +768,13 @@
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 4.3.2",
"machine_id": "requirement_4_3_2",
"content": "`Hook data` MUST must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. The hook data is not shared between different hooks.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Condition 4.3.2",
"machine_id": "condition_4_3_2",
Expand Down Expand Up @@ -911,6 +925,13 @@
"RFC 2119 keyword": "MUST NOT",
"children": []
},
{
"id": "Requirement 4.6.1",
"machine_id": "requirement_4_6_1",
"content": "`hook data` MUST be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 5.1.1",
"machine_id": "requirement_5_1_1",
Expand Down
3 changes: 3 additions & 0 deletions specification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ sidebar_position: 0
- [Evaluation Context](./sections/03-evaluation-context.md)
- [Hooks](./sections/04-hooks.md)
- [Events](./sections/05-events.md)
- [Tracking](./sections/06-tracking.md)
- [Appendix A: Included Utilities](./appendix-a-included-utilities.md)
- [Appendix B: Gherkin Suites](./appendix-b-gherkin-suites.md)
- [Appendix C: OFREP](./appendix-c-ofrep.md)
- [Appendix D: Observability](./appendix-d-observability.md)

## Conformance

Expand Down
60 changes: 60 additions & 0 deletions specification/appendix-d-observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: appendix-d
title: "Appendix D: Observability"
description: Conventions for OpenFeature telemetry signals
sidebar_position: 5
---

# Appendix D: Observability

This document describes conventions for extracting data from the OpenFeature SDK for use in telemetry signals.
It primarily focuses on providing recommendations for mapping well-known fields in OpenFeature to [OpenTelemetry feature-flag log records](https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/) and other semantic conventions.

## Evaluations

Flag evaluation telemetry comprises data resolved from the provider resolution (evaluation details and flag metadata) as well as metadata about the provider itself.
This is particularly relevant to telemetry-related [hooks](./sections/04-hooks.md).

### Evaluation Details

The following describes how fields on the [evaluation details](types.md#evaluation-details) are mapped to feature flag log records:

| Log Record Attribute | Source Field or Derived Value from Evaluation Details | Notes |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `feature_flag.key` | `flag key` | See: [flag key](./glossary.md#flag-key) |
| `error.type` | `error code` | See: [error code](./types.md#error-code) |
| `feature_flag.variant` | `variant` | See: [variant](./glossary.md#variant) |
| `feature_flag.evaluation.error.message` | `error message` | A human-readable error message associated with a failed evaluation. For programmatic purposes, refer to `error code`. |
| `feature_flag.evaluation.reason` | `reason` | See: [reason](./types.md#resolution-reason) |
| `feature_flag.evaluation.value.type` | One of `"array"`, `"boolean"`, `"byte_array"`, `"float"`, `"int"`, `"map"`, `"null"`, `"string"` or `"unknown"`, representing the type of the `evaluation details'` `value` field | See: [reason](./types.md#resolution-reason) |

> [!NOTE]
> The `error.type` and `feature_flag.evaluation.reason` enumerations use a lowercase "snake_case" convention (see [OpenTelemetry feature-flag log records](https://opentelemetry.io/docs/specs/semconv/feature-flags/feature-flags-logs/)).
> OpenFeature [error codes](types.md#error-code) and [resolution reasons](./types.md#resolution-reason) should be transformed accordingly by integrations which include this data.
#### Flag Value

The flag value is required if the `feature_flag.variant` is not set (and optional otherwise), and is defined in a the event body:

| Body Field | Source Field from Evaluation Details | Notes |
| ---------- | ------------------------------------ | ------------------------------------------- |
| `value` | `value` | The type of the `value` field is undefined. |

### Flag Metadata

The following describes how keys in [flag metadata](types.md#flag-metadata) are mapped to feature flag log records:

| Log Record Attribute | Flag Metadata Key | Notes |
| ------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `feature_flag.context.id` | `contextId` | The context identifier returned in the flag metadata uniquely identifies the subject of the flag evaluation. If not available, the [targeting key](./glossary.md#targeting-key) should be used. |
| `feature_flag.set.id` | `flagSetId` | A logical identifier for the [flag set](./glossary.md#flag-set). |
| `feature_flag.version` | `version` | A version string (format unspecified) for the flag or [flag set](./glossary.md#flag-set). |

> [!NOTE]
> Keys in flag metadata use the "camelCase" casing convention, while the OpenTelemetry standard uses a namespaced "snake_case" convention.
### Provider Metadata

| Log Record Attribute | Provider Metadata Field | Notes |
| ---------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ |
| `feature_flag.provider_name` | `name` | The name of the provider as defined in the `provider metadata`, available in the `hook context`. |
107 changes: 104 additions & 3 deletions specification/sections/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl
### Definitions

**Hook**: Application author/integrator-supplied logic that is called by the OpenFeature framework at a specific stage.
**Stage**: An explicit portion of the flag evaluation lifecycle. e.g. `before` being "before" the [resolution](../glossary.md#resolving-flag-values) is run.

**Stage**: An explicit portion of the flag evaluation lifecycle. e.g. `before` being "before the [resolution](../glossary.md#resolving-flag-values) is run.

**Invocation**: A single call to evaluate a flag. `client.getBooleanValue(..)` is an invocation.

**API**: The global API singleton.

### 4.1. Hook context

Hook context exists to provide hooks with information about the invocation.
Hook context exists to provide hooks with information about the invocation and propagate data between hook stages.

#### Requirement 4.1.1

> Hook context **MUST** provide: the `flag key`, `flag value type`, `evaluation context`, and the `default value`.
> Hook context **MUST** provide: the `flag key`, `flag value type`, `evaluation context`, `default value`, and `hook data`.
#### Requirement 4.1.2

Expand All @@ -62,6 +65,22 @@ see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm)

> The evaluation context **MUST** be mutable only within the `before` hook.
#### Requirement 4.1.5

> The `hook data` **MUST** be mutable.
Either the `hook data` reference itself must be mutable, or it must allow mutation of its contents.

Mutable reference:
```
hookContext.hookData = {'my-key': 'my-value'}
```

Mutable content:
```
hookContext.hookData.set('my-key', 'my-value')
```

### 4.2. Hook Hints

#### Requirement 4.2.1
Expand Down Expand Up @@ -90,6 +109,58 @@ see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm)

> Hooks **MUST** specify at least one stage.
#### Requirement 4.3.2

> `Hook data` **MUST** must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. The hook data is not shared between different hooks.
Example showing data between `before` and `after` stage for two different hooks.
```mermaid
sequenceDiagram
actor Application
participant Client
participant HookA
participant HookB
Application->>Client: getBooleanValue('my-bool', myContext, false)
activate Client
Client-->>Client: create hook data for HookA
Client->>HookA: before(hookContext: {data: {}, ... })
activate HookA
HookA-->>HookA: hookContext.hookData.set('key',' data for A')
HookA-->>Client: (return)
deactivate HookA
Client-->>Client: create hook data for HookB
Client->>HookB: before(hookContext: {data: {}, ... }, hints)
activate HookB
HookB-->>HookB: hookContext.hookData.set('key', 'data for B')
deactivate HookB
Client-->>Client: Flag evaluation
Client->>HookB: after(hookContext: {data: {key: 'data for B'}, ... }, detail, hints)
activate HookB
HookB-->>Client: (return)
deactivate HookB
Client->>HookA: after(hookContext: {data: {'key': 'data for A'}, ... })
activate HookA
HookA-->>Client: (return)
deactivate HookA
Client-->>Application: true
deactivate Client
```

#### Condition 4.3.2

> The implementation uses the dynamic-context paradigm.
Expand Down Expand Up @@ -230,3 +301,33 @@ see: [Flag evaluation options](./01-flag-evaluation.md#evaluation-options)
#### Requirement 4.5.3

> The hook **MUST NOT** alter the `hook hints` structure.

### 4.6. Hook data

Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span
for OpenTelemetry could be created in a `before` stage and closed in an `after` stage.

Hook data is scoped to a specific hook instance. The different stages of a hook share the same data,
but different hooks have different hook data instances.

```Java
public Optional<EvaluationContext> before(HookContext context, HookHints hints) {
SpanBuilder builder = tracer.spanBuilder('sample')
.setParent(Context.current().with(Span.current()));
Span span = builder.startSpan()
context.hookData.set("span", span);
}
public void after(HookContext context, FlagEvaluationDetails details, HookHints hints) {
// Only accessible by this hook for this specific evaluation.
Object value = context.hookData.get("span");
if (value instanceof Span) {
Span span = (Span) value;
span.end();
}
}
```

#### Requirement 4.6.1

> `hook data` **MUST** be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.
2 changes: 2 additions & 0 deletions specification/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ A structure which contains a subset of the fields defined in the `evaluation det
- variant (string, optional)
- flag metadata ([flag metadata](#flag-metadata), optional)

#### Resolution Reason

A set of pre-defined reasons is enumerated below:

| Reason | Explanation |
Expand Down

0 comments on commit 61837fa

Please sign in to comment.