Skip to content
Closed
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
2 changes: 2 additions & 0 deletions lib/dartastic_opentelemetry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export 'src/environment/env_constants.dart';
export 'src/environment/environment_service.dart';
export 'src/environment/otel_env.dart';
export 'src/factory/otel_sdk_factory.dart';
export 'src/log/logger.dart';
export 'src/log/logger_provider.dart';
export 'src/metrics/data/exemplar.dart';
export 'src/metrics/data/metric.dart';
export 'src/metrics/data/metric_data.dart';
Expand Down
20 changes: 19 additions & 1 deletion lib/src/factory/otel_sdk_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Copyright 2025, Michael Bushe, All rights reserved.
import 'package:dartastic_opentelemetry/src/trace/tracer_provider.dart';
import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
import '../metrics/meter_provider.dart';

import '../log/logger_provider.dart';
import '../metrics/meter_provider.dart';
import '../resource/resource.dart';

/// Factory function that creates an OTelSDKFactory with the specified configuration.
Expand Down Expand Up @@ -121,4 +122,21 @@ class OTelSDKFactory extends OTelAPIFactory {
serviceName: serviceName),
resource: resource);
}

@override
APILoggerProvider loggerProvider({
required String endpoint,
String serviceName = "@dart/opentelemetry_api",
String? serviceVersion,
Resource? resource,
}) {
return SDKLoggerProviderCreate.create(
delegate: super.loggerProvider(
endpoint: endpoint,
serviceVersion: serviceVersion,
serviceName: serviceName,
),
resource: resource,
);
}
}
68 changes: 68 additions & 0 deletions lib/src/log/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

import '../resource/resource.dart';
import 'logger_provider.dart';

part 'logger_create.dart';

/// SDK implementation of the APILogger interface.
///
/// A Logger is responsible for creating and managing logs.
///
/// This implementation delegates some functionality to the API Logger
/// implementation while adding SDK-specific.
///
///
/// More information:
/// https://opentelemetry.io/docs/specs/otel/logs/sdk/
class Logger implements APILogger {
final LoggerProvider _provider;
final APILogger _delegate;

bool _enabled = true;

/// Private constructor for creating Logger instances.
Logger._({
required LoggerProvider provider,
required APILogger delegate,
}) : _provider = provider,
_delegate = delegate;

@override
String get name => _delegate.name;

@override
String? get schemaUrl => _delegate.schemaUrl;

@override
String? get version => _delegate.version;

@override
Attributes? get attributes => _delegate.attributes;

@override
bool get enabled => _enabled;

set enabled(bool value) {
_enabled = value;
}

/// Gets the provider that created this logger.
LoggerProvider get provider => _provider;

/// Gets the resource associated with this logger's provider.
Resource? get resource => _provider.resource;

@override
void emit({
Attributes? attributes,
Context? context,
body,
DateTime? observedTimestamp,
Severity? severityNumber,
String? severityText,
DateTime? timeStamp,
}) {
// TODO: implement emit
}
}
16 changes: 16 additions & 0 deletions lib/src/log/logger_create.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
part of 'logger.dart';

/// Factory for creating Logger instances.
///
/// This factory class provides a static create method for constructing
/// properly configured Logger instances. It follows the factory pattern
/// to separate the construction logic from the Logger class itself.
class SDKLoggerCreate {
/// Creates a new Logger with the specified delegate and provider.
static Logger create({
required APILogger delegate,
required LoggerProvider provider,
}) {
return Logger._(delegate: delegate, provider: provider);
}
}
188 changes: 188 additions & 0 deletions lib/src/log/logger_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';

import '../otel.dart';
import '../resource/resource.dart';
import 'logger.dart';

part 'logger_provider_create.dart';

/// SDK implementation of the APILoggerProvider interface.
///
/// The LoggerProvider is the entry point to the logger API. It is responsible
/// for creating and managing Loggers.
///
/// This implementation delegates some functionality to the API LoggerProvider
/// implementation while adding SDK-specific behaviors.
///
///
/// More information:
/// https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider
class LoggerProvider implements APILoggerProvider {
/// Registry of loggers managed by this provider.
final Map<String, Logger> _loggers = {};

final APILoggerProvider _delegate;

// TODO: LogsProcessor type
final List<dynamic> _logProcessors = [];

/// The resource associated with this provider.
Resource? resource;

LoggerProvider._({
required APILoggerProvider delegate,
this.resource,
}) : _delegate = delegate {
if (OTelLog.isDebug()) {
OTelLog.debug('LoggerProvider: Created with resource: $resource');
if (resource != null) {
OTelLog.debug('Resource attributes:');
resource!.attributes.toList().forEach((attr) {
OTelLog.debug(' ${attr.key}: ${attr.value}');
});
}
}
}

@override
bool get isShutdown => _delegate.isShutdown;

@override
set isShutdown(bool value) {
_delegate.isShutdown = value;
}

@override
String get endpoint => _delegate.endpoint;

@override
set endpoint(String value) {
_delegate.endpoint = value;
}

@override
String get serviceName => _delegate.serviceName;

@override
set serviceName(String value) {
_delegate.serviceName = value;
}

@override
String? get serviceVersion => _delegate.serviceVersion;

@override
set serviceVersion(String? value) {
_delegate.serviceVersion = value;
}

@override
bool get enabled => _delegate.enabled;

@override
set enabled(bool value) {
_delegate.enabled = value;
}

@override
APILogger getLogger(String name,
{String? version, String? schemaUrl, Attributes? attributes}) {
if (OTelLog.isDebug()) {
OTelLog.debug(
'LoggerProvider: Getting logger with name: $name, version: $version, schemaUrl: $schemaUrl');
}
if (isShutdown) {
throw StateError('LoggerProvider has been shut down');
}

// Ensure resource is set before creating logger
ensureResourceIsSet();

final key = '$name:${version ?? ''}';
return _loggers.putIfAbsent(
key,
() => SDKLoggerCreate.create(
delegate: _delegate.getLogger(
name,
version: version,
schemaUrl: schemaUrl,
attributes: attributes,
),
provider: this,
),
);
}

@override
Future<bool> shutdown() async {
if (OTelLog.isDebug()) {
OTelLog.debug(
'LoggerProvider: Shutting down with ${_logProcessors.length} processors');
}

if (!isShutdown) {
// Shutdown all log processors
for (final processor in _logProcessors) {
if (OTelLog.isDebug()) {
OTelLog.debug(
'LoggerProvider: Shutting down processor ${processor.runtimeType}');
}
try {
// TODO: processor shutdown here.
if (OTelLog.isDebug()) {
OTelLog.debug(
'LoggerProvider: Successfully shut down processor ${processor.runtimeType}');
}
} catch (e) {
if (OTelLog.isDebug()) {
OTelLog.debug(
'LoggerProvider: Error shutting down processor ${processor.runtimeType}: $e');
}
}
}

// Clear cached loggers
_loggers.clear();
if (OTelLog.isDebug()) {
OTelLog.debug('LoggerProvider: Cleared cached loggers');
}

try {
await _delegate.shutdown();
if (OTelLog.isDebug()) {
OTelLog.debug('LoggerProvider: Delegate shutdown complete');
}
} catch (e) {
if (OTelLog.isDebug()) {
OTelLog.debug('LoggerProvider: Error during delegate shutdown: $e');
}
}

isShutdown = true;
if (OTelLog.isDebug()) OTelLog.debug('LoggerProvider: Shutdown complete');
} else {
if (OTelLog.isDebug()) OTelLog.debug('LoggerProvider: Already shut down');
}
return isShutdown;
}

/// Ensures the resource for this provider is properly set.
///
/// If no resource has been set, the default resource will be used.
void ensureResourceIsSet() {
if (resource != null) return;
resource = OTel.defaultResource;
if (!OTelLog.isDebug()) return;
OTelLog.debug('LoggerProvider: Setting resource from default');

// By right, this should already set based on [OTel.defaultResource]. In case if default is null,
// ignore next operations.
if (resource != null) return;
OTelLog.debug('Resource attributes:');
resource!.attributes.toList().forEach((attr) {
if (attr.key == 'tenant_id' || attr.key == 'service.name') {
OTelLog.debug(' ${attr.key}: ${attr.value}');
}
});
}
}
15 changes: 15 additions & 0 deletions lib/src/log/logger_provider_create.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
part of 'logger_provider.dart';

/// Factory for creating LoggerProvider instances.
///
/// This factory class provides a static create method for constructing
/// properly configured LoggerProvider instances. It follows the factory
/// pattern to separate the construction logic from the LoggerProvider
/// class itself.
class SDKLoggerProviderCreate {
/// Creates a new LoggerProvider with the specified delegate and resource.
static LoggerProvider create(
{required APILoggerProvider delegate, Resource? resource}) {
return LoggerProvider._(delegate: delegate, resource: resource);
}
}
49 changes: 49 additions & 0 deletions lib/src/otel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,37 @@ class OTel {
return meterProvider;
}

/// Gets a LoggerProvider for creating Tracers.
///
/// If name is null, this returns the global default LoggerProvider, which shares
/// the endpoint, serviceName, serviceVersion, sampler and resource set in initialize().
/// If the name is not null, it returns a LoggerProvider for the name that was added
/// with addLoggerProvider.
///
/// The endpoint, serviceName, serviceVersion, sampler and resource set flow down
/// to the [Logger]s created by the LoggerProvider
/// @param name Optional name of a specific LoggerProvider
/// @return The LoggerProvider instance
static LoggerProvider loggerProvider({String? name}) {
final loggerProvider = OTelAPI.loggerProvider(name) as LoggerProvider;
// Ensure the resource is properly set
if (loggerProvider.resource == null && defaultResource != null) {
loggerProvider.resource = defaultResource;
if (OTelLog.isDebug()) {
OTelLog.debug('OTel.loggerProvider: Setting resource from default');
if (defaultResource != null) {
defaultResource!.attributes.toList().forEach((attr) {
if (attr.key == 'tenant_id' || attr.key == 'service.name') {
OTelLog.debug(' ${attr.key}: ${attr.value}');
}
});
}
}
}

return loggerProvider;
}

/// Adds or replaces a named TracerProvider.
///
/// This allows for creating multiple TracerProviders with different configurations,
Expand Down Expand Up @@ -525,6 +556,24 @@ class OTel {
return sdkTracerProvider;
}

/// Adds or replaces a named LoggerProvider.
static LoggerProvider addLoggerProvider(
String name, {
String? endpoint,
String? serviceName,
String? serviceVersion,
Resource? resource,
}) {
final sdkLoggerProvider = OTelAPI.addLoggerProvider(
name,
endpoint: endpoint,
serviceName: serviceName,
serviceVersion: serviceVersion,
) as LoggerProvider;
sdkLoggerProvider.resource = resource ?? defaultResource;
return sdkLoggerProvider;
}

/// @return the [TracerProvider]s, the global default and named ones.
static List<APITracerProvider> tracerProviders() {
return OTelAPI.tracerProviders();
Expand Down
Loading