Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -41,6 +41,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)
* feat(opentelemetry-configuration): parse more parameters from config file [#5955](https://github.com/open-telemetry/opentelemetry-js/pull/5955) @maryliag
* feat(exporter-prometheus): support withoutTargetInfo option [#5962](https://github.com/open-telemetry/opentelemetry-js/pull/5962) @cjihrig
* feat(opentelemetry-configuration): parse trace provider from config file [#5992](https://github.com/open-telemetry/opentelemetry-js/pull/5992) @maryliag
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 @@ -30,3 +32,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