Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff06c8a
Add loggerConfigurator and tests.
JacksonWeber Oct 6, 2025
cfd6b7f
Update README.md
JacksonWeber Oct 7, 2025
5ac7921
Update CHANGELOG.md
JacksonWeber Oct 7, 2025
c407f31
Fix lint & add @experimental tags.
JacksonWeber Oct 7, 2025
8e25840
Update README.md
JacksonWeber Oct 7, 2025
319ec0a
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 13, 2025
681fc14
Update Logger.test.ts
JacksonWeber Nov 13, 2025
c822cc3
Update Logger.test.ts
JacksonWeber Nov 13, 2025
0dc7941
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 13, 2025
2029cc7
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 17, 2025
93553aa
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 18, 2025
38bebd8
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 19, 2025
8cc9360
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 19, 2025
6e9324b
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 21, 2025
3cf69a5
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 24, 2025
bf23a55
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Nov 25, 2025
2e9091c
Add support for caching the config in the logger.
JacksonWeber Nov 26, 2025
8b5850e
Add further experimental annotations.
JacksonWeber Nov 26, 2025
756bee4
Create getInstrumentationScopeKey utils function.
JacksonWeber Nov 26, 2025
13e96a7
Add further experimental flags.
JacksonWeber Nov 26, 2025
d6cf204
Fix lint.
JacksonWeber Nov 26, 2025
f1c7fc2
Merge branch 'main' into jacksonweber/logger-sampling
JacksonWeber Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* feat(opentelemetry-configuration): Parse of Configuration File [#5875](https://github.com/open-telemetry/opentelemetry-js/pull/5875) @maryliag
* feat(opentelemetry-configuration): parse of array objects on configuration file [#5947](https://github.com/open-telemetry/opentelemetry-js/pull/5947) @maryliag
* feat(opentelemetry-configuration): parse of environment variables on configuration file [#5947](https://github.com/open-telemetry/opentelemetry-js/pull/5947) @maryliag
* feat(sdk-logs): add the `loggerConfigurator` including the ability to filter by minimum severity and trace [#5991](https://github.com/open-telemetry/opentelemetry-js/pull/5991)

### :bug: Bug Fixes

Expand Down
122 changes: 122 additions & 0 deletions experimental/packages/sdk-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,128 @@ logger.emit({
Logs configuration is a merge of both the user supplied configuration and the default
configuration as specified in [config.ts](./src/config.ts)

## Logger Configuration

The SDK supports advanced logger configuration through the `LoggerConfigurator` API, which allows you to:

- **Filter logs by minimum severity level** - Drop logs below a configured severity threshold
- **Filter logs by trace sampling status** - Drop logs associated with unsampled traces
- **Configure per-logger patterns** - Apply different configurations to different loggers using wildcard patterns

### Minimum Severity Filtering

Filter logs based on their severity level. Logs with severity below the configured minimum will be dropped before reaching the processor/exporter.

```js
const { LoggerProvider, createLoggerConfigurator } = require('@opentelemetry/sdk-logs');
const { SeverityNumber } = require('@opentelemetry/api-logs');

const loggerProvider = new LoggerProvider({
loggerConfigurator: createLoggerConfigurator([
{
pattern: '*', // Match all loggers
config: {
minimumSeverity: SeverityNumber.WARN // Only WARN, ERROR, and FATAL logs
}
}
]),
processors: [new SimpleLogRecordProcessor(exporter)]
});
```

**Behavior:**

- Logs with `severityNumber >= minimumSeverity` are exported
- Logs with `severityNumber = UNSPECIFIED` (0) or undefined always bypass the filter
- Default minimum severity is `UNSPECIFIED` (no filtering)

### Trace-Based Filtering

Filter logs based on their associated trace's sampling status. Logs from unsampled traces can be dropped to reduce volume while keeping sampled trace logs.

```js
const loggerProvider = new LoggerProvider({
loggerConfigurator: createLoggerConfigurator([
{
pattern: '*',
config: {
traceBased: true // Drop logs from unsampled traces
}
}
]),
processors: [new SimpleLogRecordProcessor(exporter)]
});
```

**Behavior:**

- Logs associated with **sampled traces** (TraceFlags.SAMPLED set) are exported
- Logs associated with **unsampled traces** (TraceFlags.SAMPLED not set) are dropped
- Logs **without trace context** bypass the filter and are exported
- Default is `false` (no trace-based filtering)

### Combined Filtering

Both filters can be combined. A log must pass **both** filters to be exported.

```js
const loggerProvider = new LoggerProvider({
loggerConfigurator: createLoggerConfigurator([
{
pattern: '*',
config: {
minimumSeverity: SeverityNumber.WARN, // Filter by severity
traceBased: true // AND filter by trace sampling
}
}
]),
processors: [new SimpleLogRecordProcessor(exporter)]
});
```

### Per-Logger Configuration

Use pattern matching to configure different loggers differently. Patterns are matched in order, and the first match is used.

```js
const loggerProvider = new LoggerProvider({
loggerConfigurator: createLoggerConfigurator([
{
pattern: 'critical-service', // Exact match
config: { minimumSeverity: SeverityNumber.ERROR }
},
{
pattern: 'debug-*', // Wildcard match
config: { minimumSeverity: SeverityNumber.DEBUG }
},
{
pattern: '*', // Default for all other loggers
config: { minimumSeverity: SeverityNumber.WARN }
}
])
});

// Different loggers get different configurations
const criticalLogger = loggerProvider.getLogger('critical-service'); // ERROR+
const debugLogger = loggerProvider.getLogger('debug-api'); // DEBUG+
const defaultLogger = loggerProvider.getLogger('my-service'); // WARN+
```

### Configuration Options

```typescript
interface LoggerConfig {
/** Drop logs with severity below this level (default: UNSPECIFIED = no filtering) */
minimumSeverity?: SeverityNumber;

/** Drop logs from unsampled traces (default: false) */
traceBased?: boolean;

/** Disable this logger completely (default: false) */
disabled?: boolean;
}
```

## Example

See [examples/logs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/examples/logs)
Expand Down
46 changes: 45 additions & 1 deletion experimental/packages/sdk-logs/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
*/

import type * as logsAPI from '@opentelemetry/api-logs';
import { SeverityNumber } from '@opentelemetry/api-logs';
import type { InstrumentationScope } from '@opentelemetry/core';
import { context } from '@opentelemetry/api';
import {
context,
trace,
TraceFlags,
isSpanContextValid,
} from '@opentelemetry/api';

import { LogRecordImpl } from './LogRecordImpl';
import { LoggerProviderSharedState } from './internal/LoggerProviderSharedState';
Expand All @@ -28,7 +34,45 @@ export class Logger implements logsAPI.Logger {
) {}

public emit(logRecord: logsAPI.LogRecord): void {
// Get logger configuration
const loggerConfig = this._sharedState.getLoggerConfig(
this.instrumentationScope
);

const currentContext = logRecord.context || context.active();

// Apply minimum severity filtering
const recordSeverity =
logRecord.severityNumber ?? SeverityNumber.UNSPECIFIED;

// 1. Minimum severity: If the log record's SeverityNumber is specified
// (i.e. not 0) and is less than the configured minimum_severity,
// the log record MUST be dropped.
if (
recordSeverity !== SeverityNumber.UNSPECIFIED &&
recordSeverity < loggerConfig.minimumSeverity
) {
// Log record is dropped due to minimum severity filter
return;
}

// 2. Trace-based: If trace_based is true, and if the log record has a
// SpanId and the TraceFlags SAMPLED flag is unset, the log record MUST be dropped.
if (loggerConfig.traceBased) {
const spanContext = trace.getSpanContext(currentContext);
if (spanContext && isSpanContextValid(spanContext)) {
// Check if the trace is unsampled (SAMPLED flag is unset)
const isSampled =
(spanContext.traceFlags & TraceFlags.SAMPLED) === TraceFlags.SAMPLED;
if (!isSampled) {
// Log record is dropped due to trace-based filter
return;
}
}
// If there's no valid span context, the log record is not associated with a trace
// and therefore bypasses trace-based filtering (as per spec)
}

/**
* If a Logger was obtained with include_trace_context=true,
* the LogRecords it emits MUST automatically include the Trace Context from the active Context,
Expand Down
3 changes: 2 additions & 1 deletion experimental/packages/sdk-logs/src/LoggerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class LoggerProvider implements logsAPI.LoggerProvider {
resource,
mergedConfig.forceFlushTimeoutMillis,
reconfigureLimits(mergedConfig.logRecordLimits),
config?.processors ?? []
config?.processors ?? [],
config?.loggerConfigurator
);
this._shutdownOnce = new BindOnceFuture(this._shutdown, this);
}
Expand Down
119 changes: 119 additions & 0 deletions experimental/packages/sdk-logs/src/config/LoggerConfigurators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { InstrumentationScope } from '@opentelemetry/core';
import { SeverityNumber } from '@opentelemetry/api-logs';
import type { LoggerConfig, LoggerConfigurator } from '../types';

/**
* Default LoggerConfig used when no pattern matches
*/
const DEFAULT_LOGGER_CONFIG: Required<LoggerConfig> = {
disabled: false,
minimumSeverity: SeverityNumber.UNSPECIFIED,
traceBased: false,
};

/**
* Configuration for a specific logger pattern
*/
export interface LoggerPattern {
/**
* The logger name or pattern to match.
* Use '*' for wildcard matching.
*/
pattern: string;

/**
* The configuration to apply to matching loggers.
* Partial config is allowed; unspecified properties will use defaults.
*
* @experimental This feature is in development as per the OpenTelemetry specification.
*/
config: LoggerConfig;
}

/**
* Creates a LoggerConfigurator from an array of logger patterns.
* Patterns are evaluated in order, and the first matching pattern's config is used.
* Supports exact matching and simple wildcard patterns with '*'.
*
* The returned configurator computes a complete LoggerConfig by merging the matched
* pattern's config with default values for any unspecified properties.
*
* @param patterns - Array of logger patterns with their configurations
* @returns A LoggerConfigurator function that computes complete LoggerConfig
* @experimental This feature is in development as per the OpenTelemetry specification.
*
* @example
* ```typescript
* const configurator = createLoggerConfigurator([
* { pattern: 'debug-logger', config: { minimumSeverity: SeverityNumber.DEBUG } },
* { pattern: 'prod-*', config: { minimumSeverity: SeverityNumber.WARN } },
* { pattern: '*', config: { minimumSeverity: SeverityNumber.INFO } },
* ]);
* ```
*/
export function createLoggerConfigurator(
patterns: LoggerPattern[]
): LoggerConfigurator {
return (loggerScope: InstrumentationScope): Required<LoggerConfig> => {
const loggerName = loggerScope.name;

// Find the first matching pattern
for (const { pattern, config } of patterns) {
if (matchesPattern(loggerName, pattern)) {
// Compute complete config by merging with defaults
return {
disabled: config.disabled ?? DEFAULT_LOGGER_CONFIG.disabled,
minimumSeverity:
config.minimumSeverity ?? DEFAULT_LOGGER_CONFIG.minimumSeverity,
traceBased: config.traceBased ?? DEFAULT_LOGGER_CONFIG.traceBased,
};
}
}

// No pattern matched, return default config
return { ...DEFAULT_LOGGER_CONFIG };
};
}

/**
* Matches a logger name against a pattern.
* Supports simple wildcard matching with '*'.
*
* @param name - The logger name to match
* @param pattern - The pattern to match against (supports '*' wildcard)
* @returns true if the name matches the pattern
*/
function matchesPattern(name: string, pattern: string): boolean {
// Exact match
if (pattern === name) {
return true;
}

// Wildcard pattern
if (pattern.includes('*')) {
const regexPattern = pattern
.split('*')
.map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(name);
}

return false;
}
6 changes: 6 additions & 0 deletions experimental/packages/sdk-logs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

export type {
LoggerProviderConfig,
LoggerConfig,
LoggerConfigurator,
LogRecordLimits,
BufferConfig,
BatchLogRecordProcessorBrowserConfig,
Expand All @@ -29,3 +31,7 @@ export type { LogRecordExporter } from './export/LogRecordExporter';
export { SimpleLogRecordProcessor } from './export/SimpleLogRecordProcessor';
export { InMemoryLogRecordExporter } from './export/InMemoryLogRecordExporter';
export { BatchLogRecordProcessor } from './platform';
export {
createLoggerConfigurator,
type LoggerPattern,
} from './config/LoggerConfigurators';
Loading