Repository: open-telemetry/opentelemetry-specification
Pull request #4439: Add opt-in LogRecordProcessor.Enabled
Author: login: pellared
Labels: name: area:sdk
description: Related to the SDK
name: spec:logs
description: Related to the specification/logs directory
name: changelog.opentelemetry.io
Created: 2025-03-04T09:08:14Z | Merged: 2025-03-25T21:55:00Z

Fixes https://github.com/open-telemetry/opentelemetry-specification/issues/4363

Towards https://github.com/open-telemetry/opentelemetry-specification/issues/4208 (uses Severity Level passed via Logger.Enabled)

Towards stabilization of OpenTelemetry Go Logs API and SDK.

## Use cases

Below are some use cases where the new functionality can be used:

1. Bridge features like `LogLevelEnabled` in log bridge/appender implementations. This is needed for **all** (but one) currently supported log bridges in OTel Go Contrib.
2. Configure a minimum log severity level for a certain log processor.
3. Filter out log and event records when they are inside a span that has been sampled out (span is valid and has sampled flag of `false`).
4. **Efficiently** support high-performance logging destination like [Linux user_events](https://docs.kernel.org/trace/user_events.html) and [ETW (Event Tracing for Windows)](https://learn.microsoft.com/windows/win32/etw/about-event-tracing).
5. Bridge Logs API to a language-specific logging library (the other way than usual).

## Changes

Add `Enabled` opt-in operation to the `LogRecordProcessor`.

I created an OTEP first which was a great for having a lot of discussions and evaluations of different proposals:
- https://github.com/open-telemetry/opentelemetry-specification/pull/4290

Most importantly from https://github.com/open-telemetry/opentelemetry-specification/pull/4290#issuecomment-2662708938:

> Among Go SIG we were evaluating a few times an alternative to provide some new "filter" abstraction which is decoupled from the "processor". However, we faced more issues than benefits going this route (some if this is described here, but there were more issues: https://github.com/open-telemetry/opentelemetry-go/pull/5825#issuecomment-2550967383 . With the current opt-in `Processor.Enabled`  we faced less issues so far. 
> We also do not want to replicate all features from the logging libraries. If someone prefer the log4j (or other) filter design then someone can always use a bridge and use log4j for filtering.  `Enabled` callback hook is the simplest design (yet very flexible) which makes it easy to implement in the SDKs. This design is inspired from the design of the two most popular Go structured logging libraries: https://pkg.go.dev/log/slog (standard library) and https://pkg.go.dev/go.uber.org/zap.
> 
> It is worth to adding that Rust design is similar and it also has an `Enabled` hook. See https://github.com/open-telemetry/opentelemetry-specification/issues/4363#issuecomment-2660164716. Basically we want to add something like https://docs.rs/log/latest/log/trait.Log.html#tymethod.enabled to the `LogRecordProcessor` and allow users to implement `Enabled` in the way that it will meet their requirements.
> 
> I also want to call out form https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0265-event-vision.md#open-questions:
> 
> > How to support routing logs from the Logs API to a language-specific logging library 
> 
> To support this we would need a log record processor which bridges the Logs API calls to given logging library. For such case we would need an `Enabled` hook  in `Processor` to efficiently bridge `Logger.Enabled` calls. A filterer design would not satisfy such use case.

I decided to name the new operation `Enabled` as:
1. this name is already used in logging libraries in many languages: https://github.com/open-telemetry/opentelemetry-specification/pull/4439#issuecomment-2718666805
2. it matches the name of the API call (for all trace, metrics and logs APIs).

I was also considering `OnEnabled` to have the same pattern as for `Emit` and `OnEmit`. However, we already have `ForceFlush` and `Shutdown` which does not follow this pattern so I preferred to keep the simple `Enabled` name. For `OnEmit` I could also imagine `OnEmitted` (or `OnEmitting`) which does something after (or just before like we have `OnEnding` in `SpanProcessor`)  `OnEmit` on all registered processors were called.  Yet, I do not imagine something similar for `Enabled` as calling `Enabled` should not have any side-effects. Therefore, I decided to name it `Enabled`.

I want to highlight that a processor cannot assume `Enabled` was called before `OnEmit`, because of the following reasons:

1. **Backward compatibility** – Existing processors may already perform filtering without relying on `Enabled`. For example: [Add Advanced Processing to Logs Supplementary Guidelines #4407](https://github.com/open-telemetry/opentelemetry-specification/pull/4407).  
2. **Self-sufficiency of `OnEmit`** – Since `Enabled` is optional, `OnEmit` should be able to handle filtering independently. A processor filtering events should do so in `OnEmit`, not just in `Enabled`.  
3. **Greater flexibility** – Some processors, such as the ETW processor, don’t benefit from redundant filtering. ETW already filters out events internally, making an additional check unnecessary.  
4. **Performance considerations** – Calling `Enabled` from `OnEmit` introduces overhead, as it requires converting `OnEmit` parameters to match `Enabled`'s expected input.  
5. **Avoiding fragile assumptions** – Enforcing constraints that the compiler cannot validate increases the risk of introducing bugs.  


This feature is already implemented in OpenTelemetry Go:
- https://github.com/open-telemetry/opentelemetry-go/pull/6317
We have one processor in Contrib which takes advantage of this functionality:
- https://pkg.go.dev/go.opentelemetry.io/contrib/processors/minsev

This feautre (however with some differences) is also avaiable in OTel Rust; https://github.com/open-telemetry/opentelemetry-specification/issues/4363#issuecomment-2660164716:

> OTel Rust also has this capability. Here's an example where it is leveraged to improve performance by dropping unwanted log early. https://github.com/open-telemetry/opentelemetry-rust/blob/88cae2cf7d0ff54a042d281a0df20f096d18bf82/opentelemetry-appender-tracing/benches/logs.rs#L78-L85



Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f977dee7a33..65a3f4887ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,9 @@ release.
 
 ### Logs
 
+- Add `Enabled` opt-in operation to the `LogRecordProcessor`.
+  ([#4439](https://github.com/open-telemetry/opentelemetry-specification/pull/4439))
+
 ### Baggage
 
 ### Resource
diff --git a/spec-compliance-matrix.md b/spec-compliance-matrix.md
index c67da6ed397..bf5eca600af 100644
--- a/spec-compliance-matrix.md
+++ b/spec-compliance-matrix.md
@@ -203,6 +203,7 @@ Disclaimer: this list of features is still a work in progress, please refer to t
 | SimpleLogRecordProcessor                     |          |     | +    |     | +      |      |        | +   |      | +   |      |       |
 | BatchLogRecordProcessor                      |          |     | +    |     | +      |      |        | +   |      | +   |      |       |
 | Can plug custom LogRecordProcessor           |          |     | +    |     | +      |      |        | +   |      | +   |      |       |
+| LogRecordProcessor.Enabled                   | X        | +   |      |     |        |      |        |     |      |     |      |       |
 | OTLP/gRPC exporter                           |          |     | +    |     | +      |      |        | +   |      | +   | +    |       |
 | OTLP/HTTP exporter                           |          |     | +    |     | +      |      |        | +   |      | +   | +    |       |
 | OTLP File exporter                           |          |     | -    |     | -      |      |        |     |      | +   | -    |       |
diff --git a/specification/logs/sdk.md b/specification/logs/sdk.md
index 971b9795ed7..0ee77311f96 100644
--- a/specification/logs/sdk.md
+++ b/specification/logs/sdk.md
@@ -24,6 +24,7 @@
 - [LogRecordProcessor](#logrecordprocessor)
   * [LogRecordProcessor operations](#logrecordprocessor-operations)
     + [OnEmit](#onemit)
+    + [Enabled](#enabled-1)
     + [ShutDown](#shutdown)
     + [ForceFlush](#forceflush-1)
   * [Built-in processors](#built-in-processors)
@@ -198,7 +199,9 @@ It consists of the following parameters:
 `Enabled` MUST return `false` when:
 
 - there are no registered [`LogRecordProcessors`](#logrecordprocessor),
-- `Logger` is disabled ([`LoggerConfig.disabled`](#loggerconfig) is `true`).
+- `Logger` is disabled ([`LoggerConfig.disabled`](#loggerconfig) is `true`),
+- all registered `LogRecordProcessors` implement [`Enabled`](#enabled-1),
+  and a call to `Enabled` on each of them returns `false`.
 
 Otherwise, it SHOULD return `true`.
 It MAY return `false` to support additional optimizations and features.
@@ -345,6 +348,46 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+An implementation should return `false` if a `LogRecord` (if ever created)
+is supposed to be filtered out for the given parameters.
+It should default to returning `true` for any indeterminate state, for example,
+when awaiting configuration.
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+This operation is usually called synchronously, therefore it should not block
+or throw exceptions.
+
+`LogRecordProcessor` implementations responsible for filtering and supporting
+the `Enable` operation should ensure that [`OnEmit`](#onemit) handles filtering
+independently. API users cannot be expected to call [`Enabled`](api.md#enabled)
+before invoking [`Emit a LogRecord`](api.md#emit-a-logrecord).
+Moreover, the filtering logic in `OnEmit` and `Enabled` may differ.
+
+`LogRecordProcessor` implementations that wrap other `LogRecordProcessor`
+(which may perform filtering) can implement `Enabled` and delegate to
+the wrapped processor’s `Enabled`, if available. However, the `OnEmit`
+implementation of such processors should never call the wrapped processor’s
+`Enabled`, as `OnEmit` is responsible for handling filtering independently.
+
 #### ShutDown
 
 Shuts down the processor. Called when the SDK is shut down. This is an
diff --git a/specification/logs/supplementary-guidelines.md b/specification/logs/supplementary-guidelines.md
index 8779536d78c..67346488ad6 100644
--- a/specification/logs/supplementary-guidelines.md
+++ b/specification/logs/supplementary-guidelines.md
@@ -203,6 +203,19 @@ func (p *SeverityProcessor) OnEmit(ctx context.Context, record *sdklog.Record) e
 	}
 	return p.Processor.OnEmit(ctx, record)
 }
+
+// Enabled returns false if the severity is lower than p.Min.
+func (p *SeverityProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
+	sev := param.Severity
+	if sev != log.SeverityUndefined && sev < p.Min {
+		return false
+	}
+	if fp, ok := p.Processor.(sdklog.FilterProcessor); ok {
+		// The wrapped processor is also a filtering processor.
+		return p.Processor.Enabled(ctx, param)
+	}
+	return true
+}
 ```
 
 > [!NOTE]
@@ -242,6 +255,29 @@ func (p *IsolatedProcessor) OnEmit(ctx context.Context, record *log.Record) erro
 	}
 	return rErr
 }
+
+// Enabled honors Enabled of the wrapped processors.
+func (p *IsolatedProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool  {
+	fltrProcessors := make([]sdklog.FilterProcessor, len(p.Processors))
+	for i, proc := range p.Processors {
+		fp, ok := proc.(sdklog.FilterProcessor)
+		if !ok {
+			// Processor not implementing Enabled.
+			// We assume it will be processed.
+			return true
+		}
+		fltrProcessors[i] = fp
+	}
+
+	for _, proc := range fltrProcessors {
+		if proc.Enabled(ctx, param) {
+			// At least one Processor will process the Record.
+			return true
+		}
+	}
+	// No processor will process the record.
+	return false
+}
 ```
 
 #### Routing
@@ -271,6 +307,29 @@ func (p *LogEventRouteProcessor) OnEmit(ctx context.Context, record *log.Record)
 	}
 	return p.LogProcessor.OnEmit(ctx, record)
 }
+
+// Enabled honors Enabled of the wrapped processors.
+func (p *LogEventRouteProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool  {
+	fp1, ok := p.EventProcessor.(sdklog.FilterProcessor)
+	if !ok {
+		// Processor not implementing Enabled.
+		return true
+	}
+	fp2, ok := p.LogProcessor.(sdklog.FilterProcessor)
+	if !ok {
+		// Processor not implementing Enabled.
+		return true
+	}
+
+	if fp1.Enabled(ctx, param) {
+		return true
+	}
+	if fp2.Enabled(ctx, param) {
+		return true
+	}
+	// No processor will process the record.
+	return false
+}
 ```
 
 #### Setup


Reviews:
user: login: MrAlias
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T16:32:58Z
user: login: MrAlias
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T17:10:51Z
user: login: cijothomas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T20:14:10Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T20:32:53Z
user: login: lmolkova
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T02:53:22Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T07:42:12Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T07:43:36Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T07:44:48Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T07:44:57Z
user: login: cijothomas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T14:52:45Z
user: login: cijothomas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T14:57:10Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:04:22Z
user: login: cijothomas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:16:39Z
user: login: cijothomas
body: LGTM.
I left few small comments, not a blocker from specificiation standpoint.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-05T15:20:16Z
user: login: cijothomas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:20:49Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:25:12Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:34:14Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T15:41:54Z
user: login: XSAM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-10T23:27:51Z
user: login: lmolkova
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T17:57:16Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:19:37Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:41:30Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:45:21Z
user: login: lmolkova
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T19:10:40Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T08:03:55Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T08:04:03Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T08:04:09Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T08:04:19Z
user: login: jack-berg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T15:08:18Z
user: login: pellared
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:00:06Z
user: login: jack-berg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:04:18Z
user: login: carlosalberto
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T00:31:18Z
user: login: dmathieu
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T08:19:22Z
user: login: MrAlias
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T16:20:52Z
user: login: Kielek
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T09:45:38Z
user: login: jsuereth
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T21:54:40Z

Review comments:
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` may implement
path: specification/logs/sdk.md
user: login: MrAlias
body: Does "may" here need to be a key-word?

```suggestion
`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
```
created_at: 2025-03-04T16:32:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Resource](./data-model.md#field-resource) associated with the `LoggerProvider`
path: specification/logs/sdk.md
user: login: cijothomas
body: Do we have to pass Resource on each call? It should be possible to do it more efficiently. I suggest to remove it or mention that Resource information is available to LogProcessor. Or remove it completely.
created_at: 2025-03-04T20:14:10Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Resource](./data-model.md#field-resource) associated with the `LoggerProvider`
path: specification/logs/sdk.md
user: login: pellared
body: @cijothomas, you mean not even even passing a reference/pointer to the resource, right (less copying)?

"Resource information is available to LogProcessor" seems fuzzy/unclear to me.

Personally, I am OK with removing it. I had not experienced any use case where the resource information has any information needed for filtering. We can always add it later.

@MrAlias, do you have any preferences? I am asking as you already approved and maybe you have some opinion.
created_at: 2025-03-04T20:32:53Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
path: specification/logs/sdk.md
user: login: lmolkova
body: what does opt-in mean, could you please elaborate? 
created_at: 2025-03-05T02:35:30Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
path: specification/logs/sdk.md
user: login: pellared
body: It means that it not must implemented by a `LogRecordProcessor`. I find it redundant with 

> that a `LogRecordProcessor` MAY implement in order to support filtering via [`Logger.Enabled`](api.md#enabled).

Probably removing opt-in will be less confusing.

```suggestion
`Enabled` is an operation that a `LogRecordProcessor` MAY implement
```

PTAL
created_at: 2025-03-05T07:42:12Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Resource](./data-model.md#field-resource) associated with the `LoggerProvider`
path: specification/logs/sdk.md
user: login: pellared
body: ```suggestion
```
created_at: 2025-03-05T07:43:36Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
path: specification/logs/sdk.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/2ff3f5a2870cab1431bbe20f99bb2e490fb2f352
created_at: 2025-03-05T07:44:48Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Resource](./data-model.md#field-resource) associated with the `LoggerProvider`
path: specification/logs/sdk.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/df9ebb1014156de3254bfb80fa979b82f6d2fcbe
created_at: 2025-03-05T07:44:57Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
+[`OnEmit`](#onemit). It is not expected that the caller to `OnEmit` will use the
path: specification/logs/sdk.md
user: login: cijothomas
body: Why is it that the caller is _**not expected**_ to skip calling `OnEmit`? Is it to account for the multi-processors scenario, where some of then return true and some false, so caller will call OnEmit anyway?
created_at: 2025-03-05T14:52:45Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
path: specification/logs/sdk.md
user: login: cijothomas
body: Nit: is there a need to specify this? I don't think we do that in other places of the spec.

created_at: 2025-03-05T14:57:10Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
path: specification/logs/sdk.md
user: login: pellared
body: Not sure. I preferred to call it out explicitly. I want to mitigate issues like https://github.com/open-telemetry/opentelemetry-specification/issues/3902 up front.
created_at: 2025-03-05T15:04:22Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
path: specification/logs/sdk.md
user: login: cijothomas
body: Not sure where to mention this , but it is important to call out that, if a EnrichmentProcessor is used, it must be used as a wrapper around the exporting processor to benefit from the perf gains of Enabled() check. 
Otherwise, EnrichmentProcessor would return true for Enabled(), forcing the Logger to anyway create LogRecord and invoke OnEmit(), which gets Enriched by EnrichmentProcessor, and then passed to Exporting processor which would just drop it.

its covered in the supplementary guidelines example, but wondering if we should make it more obvious in the spec itself?
created_at: 2025-03-05T15:16:39Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
path: specification/logs/sdk.md
user: login: cijothomas
body: no harm keeping it.
created_at: 2025-03-05T15:20:49Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
+[`OnEmit`](#onemit). It is not expected that the caller to `OnEmit` will use the
path: specification/logs/sdk.md
user: login: pellared
body: > Why is it that the caller is not expected to skip calling `OnEmit`?

I'm not sure if I fully understand your question.
My intent was to highlight that a processor cannot assume `Enabled` was called before `OnEmit`.

However, I will do my best to describe the reasoning behind this recommendation.
Multiple reasons such as:

1. **Backward compatibility** – Existing processors may already perform filtering without relying on `Enabled`. For example: [Add Advanced Processing to Logs Supplementary Guidelines #4407](https://github.com/open-telemetry/opentelemetry-specification/pull/4407).  
2. **Self-sufficiency of `OnEmit`** – Since `Enabled` is optional, `OnEmit` should be able to handle filtering independently. A processor filtering events should do so in `OnEmit`, not just in `Enabled`.  
3. **Greater flexibility** – Some processors, such as the ETW processor, don’t benefit from redundant filtering. ETW already filters out events internally, making an additional check unnecessary.  
4. **Performance considerations** – Calling `Enabled` from `OnEmit` introduces overhead, as it requires converting `OnEmit` parameters to match `Enabled`'s expected input.  
5. **Avoiding fragile assumptions** – Enforcing constraints that the compiler cannot validate increases the risk of introducing bugs.  

> Is it to account for the multi-processors scenario, where some of then return true and some false, so caller will call OnEmit anyway?

This should not be a problem. However, doing it correctly may indeed not be straightforward. The "multi-processor" should simply call `OnEmit` on proper (enabled) parameters.
created_at: 2025-03-05T15:25:12Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
path: specification/logs/sdk.md
user: login: pellared
body: > its covered in the supplementary guidelines example, but wondering if we should make it more obvious in the spec itself?

I find supplementary guidelines are currently the best place so that we do not make the specification too verbose. I think we can also try refining it in a separate PR unless you have some nice proposal how to describe it here. Personally, I would consider adding "tips" to the supplementary guidelines on how to compose the processors efficiently. 
created_at: 2025-03-05T15:34:14Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
+[`OnEmit`](#onemit). It is not expected that the caller to `OnEmit` will use the
path: specification/logs/sdk.md
user: login: pellared
body: I am thinking of making post-PR OTEP to have such design decisions somewhere. Maybe it would help in (re)evaluation of the design during the development and stabilization process. WDYT?
created_at: 2025-03-05T15:41:54Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
path: specification/logs/sdk.md
user: login: XSAM
body: ```suggestion
* [Context](../context/README.md) explicitly passed by the caller or the current
  Context
* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
  with the `Logger`
```

nit. swap the order of `Context` and `Instrumentation Scope`.
created_at: 2025-03-10T23:27:47Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
path: specification/logs/sdk.md
user: login: lmolkova
body: I think I understand the intention, maybe we can rephrase it to be more clear? E.g.

```suggestion
`LogRecordProcessor` implementations SHOULD support this operation if they intend to filter out `LogRecord`s. An implementation should return `false` when it's going to filter out the corresponding `LogRecord` out (if the record is ever created). It SHOULD default to returning `true` for any indeterminate state, for example, when awaiting configuration to be provided.
```

Currently it feels like processor can just decide to return false for reasons not necessarily related to its main functionality.
created_at: 2025-03-17T17:33:44Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
path: specification/logs/sdk.md
user: login: lmolkova
body: would we consider implementing `Enabled` on Batching Processor (and it'd return false when queue is full)?
created_at: 2025-03-17T17:41:36Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
path: specification/logs/sdk.md
user: login: lmolkova
body: looking at the `IsolatedProcessor` example, it's only the case when the processor does some filtering (and not the case when processor wraps others that might do the filtering). Perhaps something along these lines would be more precise?

```suggestion
`LogRecordProcessor` implementations that filter out some log records in `OnEmit` callback and
choose to support `Enable` operation are expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
```
created_at: 2025-03-17T17:54:03Z
author_association: MEMBER
diff_hunk: @@ -242,6 +255,29 @@ func (p *IsolatedProcessor) OnEmit(ctx context.Context, record *log.Record) erro
 	}
 	return rErr
 }
+
+// Enabled honors Enabled of the wrapped processors.
+func (p *IsolatedProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool  {
path: specification/logs/supplementary-guidelines.md
user: login: lmolkova
body: nit: mind adding a small comment on why we're not calling this method in the `OnEmit`?
created_at: 2025-03-17T17:54:38Z
author_association: MEMBER
diff_hunk: @@ -203,6 +203,19 @@ func (p *SeverityProcessor) OnEmit(ctx context.Context, record *sdklog.Record) e
 	}
 	return p.Processor.OnEmit(ctx, record)
 }
+
+// Enabled returns false if the severity is lower than p.Min.
+func (p *SeverityProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
path: specification/logs/supplementary-guidelines.md
user: login: lmolkova
body: mind adding a comment explaining why we're not calling this method directly in `OnEmit` ? (i.e. that processor should only filter what it's responsible for and should not decide for wrapped processors) ?
created_at: 2025-03-17T17:56:26Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
path: specification/logs/sdk.md
user: login: pellared
body: I think that an SDK can do that. However, I am not sure if it is worth doing, because:
1. it is not something that is on the hot-path (it should not happen frequently)
2. it could "race", in a few CPU cycles later the queue may no longer be full
created_at: 2025-03-17T18:19:37Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
path: specification/logs/sdk.md
user: login: pellared
body: Indeed. nice and important catch
created_at: 2025-03-17T18:41:30Z
author_association: MEMBER
diff_hunk: @@ -203,6 +203,19 @@ func (p *SeverityProcessor) OnEmit(ctx context.Context, record *sdklog.Record) e
 	}
 	return p.Processor.OnEmit(ctx, record)
 }
+
+// Enabled returns false if the severity is lower than p.Min.
+func (p *SeverityProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
path: specification/logs/supplementary-guidelines.md
user: login: pellared
body: Maybe it would be better to put the reasons somewhere here https://github.com/open-telemetry/opentelemetry-specification/pull/4439/files#r1999339113?

<!--
I mostly think of the following reasons:

`OnEmit` should handle filtering independently. This approach offers provides better flexibility and efficiency. For example, an ETW and user_events do the filtering internally, making an additional `Enabled` call unnecessary.  Moreover, calling `Enabled` from `OnEmit` would introduce overhead, as it would require converting `OnEmit` parameters to match `Enabled`'s expected input.

-->
created_at: 2025-03-17T18:45:21Z
author_association: MEMBER
diff_hunk: @@ -203,6 +203,19 @@ func (p *SeverityProcessor) OnEmit(ctx context.Context, record *sdklog.Record) e
 	}
 	return p.Processor.OnEmit(ctx, record)
 }
+
+// Enabled returns false if the severity is lower than p.Min.
+func (p *SeverityProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
path: specification/logs/supplementary-guidelines.md
user: login: lmolkova
body: yep, sounds good!
created_at: 2025-03-17T19:10:39Z
author_association: MEMBER
diff_hunk: @@ -203,6 +203,19 @@ func (p *SeverityProcessor) OnEmit(ctx context.Context, record *sdklog.Record) e
 	}
 	return p.Processor.OnEmit(ctx, record)
 }
+
+// Enabled returns false if the severity is lower than p.Min.
+func (p *SeverityProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
path: specification/logs/supplementary-guidelines.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/4f60c059b566eac83c490f35290eae3a4ed644ec
created_at: 2025-03-18T08:03:54Z
author_association: MEMBER
diff_hunk: @@ -242,6 +255,29 @@ func (p *IsolatedProcessor) OnEmit(ctx context.Context, record *log.Record) erro
 	}
 	return rErr
 }
+
+// Enabled honors Enabled of the wrapped processors.
+func (p *IsolatedProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool  {
path: specification/logs/supplementary-guidelines.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/4f60c059b566eac83c490f35290eae3a4ed644ec
created_at: 2025-03-18T08:04:03Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
+
+`LogRecordProcessor` implementations that choose to support this operation are
+expected to re-evaluate the [ReadWriteLogRecord](#readwritelogrecord) passed to
path: specification/logs/sdk.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/4f60c059b566eac83c490f35290eae3a4ed644ec
created_at: 2025-03-18T08:04:09Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,38 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+An implementation should default to returning `true` for an indeterminate
+state, but may return `false` if valid reasons in particular circumstances
+exist (e.g. performance).
path: specification/logs/sdk.md
user: login: pellared
body: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/4f60c059b566eac83c490f35290eae3a4ed644ec
created_at: 2025-03-18T08:04:19Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,46 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+An implementation should return `false` if a `LogRecord` (if ever created)
+is supposed to be filtered out for the given parameters.
+It should default to returning `true` for any indeterminate state, for example,
+when awaiting configuration.
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+This operation is usually called synchronously, therefore it should not block
path: specification/logs/sdk.md
user: login: jack-berg
body: Who calls this, and when?
created_at: 2025-03-18T15:08:18Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,46 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+An implementation should return `false` if a `LogRecord` (if ever created)
+is supposed to be filtered out for the given parameters.
+It should default to returning `true` for any indeterminate state, for example,
+when awaiting configuration.
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+This operation is usually called synchronously, therefore it should not block
path: specification/logs/sdk.md
user: login: pellared
body: It is already described in the spec.
It can be called by the `Logger.Enabled` implementation of the SDK and a processor which is wrapping other processor.
created_at: 2025-03-18T16:00:06Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,46 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Context](../context/README.md) explicitly passed by the caller or the current
+  Context
+* [Instrumentation Scope](./data-model.md#field-instrumentationscope) associated
+  with the `Logger`
+* [Severity Number](./data-model.md#field-severitynumber) passed by the caller
+
+**Returns:** `Boolean`
+
+An implementation should return `false` if a `LogRecord` (if ever created)
+is supposed to be filtered out for the given parameters.
+It should default to returning `true` for any indeterminate state, for example,
+when awaiting configuration.
+
+Any modifications to parameters inside `Enabled` MUST NOT be propagated to the
+caller. Parameters are immutable or passed by value.
+
+This operation is usually called synchronously, therefore it should not block
path: specification/logs/sdk.md
user: login: jack-berg
body: Oh I missed the line above:

> - all registered `LogRecordProcessors` implement [`Enabled`](#enabled-1), and a call to `Enabled` on each of them returns `false`.

My mistake 🙂 
created_at: 2025-03-18T16:04:17Z
author_association: MEMBER
diff_hunk: @@ -345,6 +348,39 @@ A `LogRecordProcessor` may freely modify `logRecord` for the duration of
 the `OnEmit` call. If `logRecord` is needed after `OnEmit` returns (i.e. for
 asynchronous processing) only reads are permitted.
 
+#### Enabled
+
+**Status**: [Development](../document-status.md)
+
+`Enabled` is an opt-in operation that a `LogRecordProcessor` MAY implement
+in order to support filtering via [`Logger.Enabled`](api.md#enabled).
+
+**Parameters:**
+
+* [Resource](./data-model.md#field-resource) associated with the `LoggerProvider`
path: specification/logs/sdk.md
user: login: MrAlias
body: > @MrAlias, do you have any preferences? I am asking as you already approved and maybe you have some opinion.

SGTM
created_at: 2025-03-24T16:20:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: cijothomas
created_at: 2025-03-12T17:50:46Z
body: - https://docs.rs/tracing-subscriber/0.3.19/tracing_subscriber/layer/trait.Layer.html#method.enabled
- https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger.isenabled?view=net-9.0-pp
- https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/log/slog/handler.go;l=41

"Enabled" is quite established( Above are links from Rust, .NET, Go 's popular logging libraries)
Sharing as I vaguely recall a feedback about whether the name "Enabled" is a good choice.
author_association: MEMBER
user: login: pellared
created_at: 2025-03-13T19:04:37Z
body: > * https://docs.rs/tracing-subscriber/0.3.19/tracing_subscriber/layer/trait.Layer.html#method.enabled
> 
>     * https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger.isenabled?view=net-9.0-pp
> 
>     * https://cs.opensource.google/go/go/+/refs/tags/go1.24.1:src/log/slog/handler.go;l=41
> 
> 
> "Enabled" is quite established( Above are links from Rust, .NET, Go 's popular logging libraries) Sharing as I vaguely recall a feedback about whether the name "Enabled" is a good choice.

A popular C++ logging library `spdlog` names it `should_log`: https://github.com/gabime/spdlog/blob/3335c380a08c5e0f5117a66622df6afdb3d74959/include/spdlog/logger.h#L261-L264

I think `Enabled` is better as it also matches the name of the API call (for all trace, metrics and logs APIs). I was also considering `OnEnabled` to have the same pattern as for `Emit` and `OnEmit`. However, we already have `ForceFlush` and `Shutdown` which does not follow this pattern so I preferred to keep the simple `Enabled` name. For `OnEmit` I could also imagine `OnEmitted` (or `OnEmitting`) which does something after (or just before like we have `OnEnding` in `SpanProcessor`)  `OnEmit` on all registered processors were called.  Yet, I do not imagine something similar for `Enabled` as calling `Enabled` should not have any side-effects. Therefore, I decided to name it `Enabled`.

@jack-berg, thanks for the question during the SIG meeting :pray: 
author_association: MEMBER
user: login: pellared
created_at: 2025-03-17T14:52:32Z
body: @open-telemetry/technical-committee, kindly asking for reviews
author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T08:05:14Z
body: @lmolkova, I did my best to address your feedback here: https://github.com/open-telemetry/opentelemetry-specification/pull/4439/commits/4f60c059b566eac83c490f35290eae3a4ed644ec. 
author_association: MEMBER
user: login: jack-berg
created_at: 2025-03-18T15:22:07Z
body: > Bridge features like LogLevelEnabled in log bridge/appender implementations. This is needed for all currently supported log bridges in OTel Go Contrib.

It shouldn't be. 

There are two cases to consider:

1. Bridging FROM <log API> TO OpenTelemetry
2. Bridging FROM OpenTelemetry TO <log API>

I don't understand why you would need `enabled` to efficiently bridge to OpenTelemetry (case #1), since all the filtering is done in the upstream log API before the OpenTelemetry appender is called. 

I do see the need for this capability to efficiently bridge from OpenTelemetry to other log APIs (case #2).
author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T15:58:07Z
body: > I don't understand why you would need enabled to efficiently bridge to OpenTelemetry (case 1),
> since all the filtering is done in the upstream log API before the OpenTelemetry appender is called.

Most Go logging libraries are designed  differently than the ones e.g. in Java. Here you can see that our bridges (all but one; otellogrus) call `Logger.Enabled`:
 - https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/bridges/otellogr/logsink.go
 - https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/bridges/otelslog/handler.go
 - https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/bridges/otelzap/core.go

Also from https://github.com/open-telemetry/opentelemetry-specification/pull/4203:

> Moreover, the https://pkg.go.dev/log/slog#Handler (Go standard library structured logging), https://pkg.go.dev/go.uber.org/zap/zapcore#LevelEnabler (popular Go structured logging library), https://pkg.go.dev/github.com/go-logr/logr#LogSink (opinionated common logging abstraction) allow creating a bridge/hook/sink that is checking if a logger is enabled and it accepts the logging level as an argument.

Also see https://github.com/open-telemetry/opentelemetry-specification/issues/3917

Moreover, this allows having a centralized configuration/hook for "Enabled" for code using logging bridges and Logs API directly.

> I do see the need for this capability to efficiently bridge from OpenTelemetry to other log APIs (case 2).

Then how would we make sure that Logs API `Logger.Enabled`  would call e.g. `logger.levelEnabled` of given logging library ?
author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T16:07:01Z
body: This is how a "trace-based sampling log record processor" could look like:

```go
import (
	"context"

	"go.opentelemetry.io/otel/log"
	sdklog "go.opentelemetry.io/otel/sdk/log"
	"go.opentelemetry.io/otel/trace"
)

type TraceSamplingProcessor struct {
	sdklog.Processor
}

func (p *TraceSamplingProcessor) OnEmit(ctx context.Context, record *sdklog.Record) error {
	if !trace.SpanContextFromContext(ctx).IsSampled() {
		return nil
	}
	return p.Processor.OnEmit(ctx, record)
}

func (p *TraceSamplingProcessor) Enabled(ctx context.Context, param sdklog.EnabledParameters) bool {
	if !trace.SpanContextFromContext(ctx).IsSampled() {
		return false
	}
	if fp, ok := p.Processor.(sdklog.FilterProcessor); ok {
		// The wrapped processor is also a filtering processor.
		return p.Processor.Enabled(ctx, param)
	}
	return true
}
```

One would use it in the exactly same way as the `SeverityProcessor`.

CC @trask 
author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T16:55:53Z
body: I also want to share that I do not think it is in conflict with declarative config as we are not even introducing any new component.

Processors can already be wrapped/decorated even without Enabled. In my opinion, even without this proposal the declarative config should give a way to wrap/decorate processors. E.g. the declarative config should provide a functionality to setup a SeverityProcessor to do things like https://github.com/open-telemetry/opentelemetry-specification/pull/4407. I think that the declarative config can add support for custom configuration to make it possible. There looks to be already a similar issue around that: https://github.com/open-telemetry/opentelemetry-specification/issues/4440.
author_association: MEMBER
user: login: jack-berg
created_at: 2025-03-18T20:40:52Z
body: > Most Go logging libraries are designed differently than the ones e.g. in Java. Here you can see that our bridges (all but one; otellogrus) call Logger.Enabled:

Interesting.. this means that in these libraries the decision of which logs to retain (based on severity and logger name) is not centralized, but distributed out to any handlers registered. I wonder if java and go are implementing log appenders at a different level of abstraction. 

With the java implementations:

- There's a log API (e.g. logback) called by the user / application
- There's also an implementation of this API
- The user configures the implementation of this API to do things like:
  - Register a console appender
  - Register an OpenTelemetry appender
  - Configure the console appender to receive logs of severity debug or higher
  - Configure the OpenTelemetry appender to receive logs of severity info or higher, and exclude logs from logger "foo.bar.baz"
- The log implementation only calls the OpenTelemetry appender with logs filtered according to the user configuration
- The OpenTelemetry appender forwards _all_ logs it receives to OpenTelemetry log API

With the go implementation, based on the best that I can tell:

- There's a log API (e.g. zap) called by the user / application
- The user configures the OpenTelemetry appender to be the implementation of the log API
- As a consequence, the OpenTelemetry appender and OpenTelemetry log API / SDK are suddenly burdened with the types of responsibilities that the log API implementation handles in java
  - Users want to configure logs to go to the console / stdout, in addition to OTLP
  - Users want to configure log levels and enabled loggers independently for console / stdout and OTLP
  - Since the OpenTelemetry appender directly implements the log API, its responsible for performant implementations of things like `enabled(Context, Severity)` which are handled by the log API implementation in java

If this is correct, then a bunch of the things you've been asking for suddenly make more sense, like independent log pipelines with their own configuration around log level and enabled loggers.

But I think the problems you're running into are a result of the OpenTelemetry log API not being designed to accomplish this. It was built assuming that there there is an implementation of the log API installed which allows appenders to be installed, where we can write appender implementations that bridge to OpenTelemetry. It was not designed for OpenTelemetry appenders _to be the implementation of the log API_. 

I think this is probably the root of your problems and why we've been seemingly talking past each other at times.

The obvious question to ask is: is there a way for OpenTelemetry go log appenders to be written at a different level of abstraction? I.e. instead of trying to be an implementation of the zap log API, instead rely on users leveraging the built-in zap log implementation and registering a component to their zap configuration that bridges to OpenTelemetry. Not sure exactly what that would be called in the zap vocabulary - maybe a custom sink?

> declarative config should give a way to wrap/decorate processors.

If the spec defines a processor which wraps or decorates another processor, there will be a corresponding type in declarative config. Otherwise, there's still the opportunity to reference custom processors in declarative config (and any custom SDK extension component for that matter). But almost certainly without language interoperability, since its highly unlikely that language SIGs collaborate and customer processor in contrib with identical configuration interfaces.
author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T21:32:22Z
body: > it was built assuming that there there is an implementation of the log API installed which allows appenders to be installed, where we can write appender implementations that bridge to OpenTelemetry.

I disagree with it as even the spec says "log bridge/appender" in a few places. In Go we mostly have bridges that you pretty well described. Only logrus have something similar to a log appenders (it is called hooks). Still the need to support "bridges" is not limited only to Go e.g. for Rust we should also have a bridge for https://docs.rs/log/latest/log/. We just need "more bridging capabilities" for these languages. Notice that I marked the new operation as MAY as I can understand that it may occur unnecessary for e.g. Java.

> leveraging the built-in zap log implementation and registering a component to their zap configuration that bridges to OpenTelemetry

There is no built-in zap feature like that.

We need not to forget that Logs API can also be used directly. Therefore,  whatever logging libraries offer -  we still need this kind of basic functionalities to be supported by the SDK.


author_association: MEMBER
user: login: jack-berg
created_at: 2025-03-18T22:01:04Z
body: > I disagree with it as even the spec says "log bridge/appender" in a few places.

I'm telling you the historical context as someone who was deeply involved of the original effort to design and stabilize the log API and SDK. The API and SDK were designed as simple tools to facilitate the primary use case of buffering logs in a queue to send to a OTLP network location. SDK extension points like processor and exporter were added for symmetry to traces. There was an explicit goal to _not_ require OpenTelemetry language implementation to recreate the rich ecosystems of existing log frameworks, with long lists of appenders, encoders, etc. See that goal [reflected in this text](https://github.com/open-telemetry/opentelemetry-specification/blame/main/specification/logs/README.md#L146-L154):

> Existing logging libraries generally provide a much richer set of features than what is defined in OpenTelemetry. It is NOT a goal of OpenTelemetry to ship a feature-rich logging library. 

If OpenTelemetry log appenders were supposed to be _the implementation of log APIs_, we'd have a much different / richer set of capabilities. With built-in capabilities, the log SDK is pretty much only good for buffering logs before sending to an OTLP network location and that is not an accident / coincidence. 

> There is no built-in zap feature like that.

[From the docs](https://pkg.go.dev/go.uber.org/zap#hdr-Extending_Zap):

> Extending zap to support a new encoding (e.g., BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an exception aggregation service, like Sentry or Rollbar) typically requires implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core interfaces.

This would be analog to a new log sink like the kafka example the zap authors give.

> We need not to forget that Logs API can also be used directly.

You're right, but this is a separate issue. We would still want to make sure go log appenders are implemented at the intended level of abstraction else go users will have a very different experience than other OpenTelemetry languages.



author_association: MEMBER
user: login: pellared
created_at: 2025-03-18T22:31:49Z
body: > to not require OpenTelemetry language implementation to recreate the rich ecosystems of existing log frameworks

I get it and this is why I do not want to add any sophisticated components (like filters in log4j) but add the simplest yet flexible feature that can support multiple use cases (more in PR description). At last, it is added as a MAY. SDKs are not required to support it. For OTel Go such feature is a must have (this is not "my" opinion, but a statement that all @open-telemetry/go-maintainers agree with).

> This would be analog to a new log sink like the kafka example the zap authors give.

This is can be only done by implementing a zapcore.Core and this is exactly what we are doing. This is not something which you understand as a log appender. slog and logr share similar design.
author_association: MEMBER
user: login: jack-berg
created_at: 2025-03-18T22:53:06Z
body: > At last, it is added as a MAY. SDKs are not required to support it.

I'm not making arguments for / against this PR. This is tangential, but to me is an very important realization that helps explain the "why" behind so many things that I couldn't quite understand previously. 
author_association: MEMBER
user: login: jack-berg
created_at: 2025-03-19T20:31:25Z
body: Just to complete this conversation, I've sketched out an idea of how go log appenders might be adjusted to more closely match the log appender concept in other languages: https://github.com/open-telemetry/opentelemetry-go-contrib/pull/6964

I don't want to distract from this PR's content anymore, so let's take any followup to that draft PR or an issue.
author_association: MEMBER
user: login: jsuereth
created_at: 2025-03-25T21:53:59Z
body: @pellared - I do think https://github.com/open-telemetry/opentelemetry-specification/pull/4439#issuecomment-2733831983 may be something we should specify.  It is easy to do, but something we should probably provide OOTB.
author_association: CONTRIBUTOR