Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 @@ -26,6 +26,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
120 changes: 120 additions & 0 deletions experimental/packages/sdk-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,126 @@ 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
40 changes: 39 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,9 @@
*/

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 +29,44 @@ 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
116 changes: 116 additions & 0 deletions experimental/packages/sdk-logs/src/config/LoggerConfigurators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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.
*/
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
*
* @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
Loading