Skip to content

Commit 8dcd67c

Browse files
committed
feat: LoggerProvider
1 parent 1b7664c commit 8dcd67c

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed

lib/src/sdk/logs/logger_provider.dart

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
import 'package:meta/meta.dart';
5+
import 'package:opentelemetry/api.dart' as api;
6+
import 'package:opentelemetry/sdk.dart' as sdk;
7+
import 'package:opentelemetry/src/experimental_api.dart' as api;
8+
import 'package:opentelemetry/src/experimental_sdk.dart' as sdk;
9+
import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart';
10+
import 'package:opentelemetry/src/sdk/logs/logger_config.dart';
11+
import 'package:quiver/core.dart';
12+
13+
const defaultLoggerName = 'unknown';
14+
15+
// https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider
16+
class LoggerProvider implements api.LoggerProvider {
17+
@protected
18+
final Map<int, api.Logger> loggers = {};
19+
20+
final LoggerConfig config;
21+
22+
final List<sdk.LogRecordProcessor> processors;
23+
24+
final sdk.Resource? resource;
25+
final sdk.LogRecordLimits logRecordLimits;
26+
27+
final sdk.TimeProvider _timeProvider;
28+
29+
LoggerProvider({
30+
this.resource,
31+
this.config = const LoggerConfig(),
32+
this.logRecordLimits = const LogRecordLimitsImpl(),
33+
this.processors = const <sdk.LogRecordProcessor>[],
34+
sdk.TimeProvider? timeProvider,
35+
}) : _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider();
36+
37+
@override
38+
api.Logger get(
39+
String name, {
40+
String version = '',
41+
String schemaUrl = '',
42+
List<api.Attribute> attributes = const [],
43+
bool? includeTraceContext,
44+
}) {
45+
final loggerName = name.isNotEmpty ? name : defaultLoggerName;
46+
final key = hash3(loggerName, version, schemaUrl);
47+
if (config.disabled) {
48+
return api.NoopLogger();
49+
}
50+
return loggers.putIfAbsent(
51+
key,
52+
() => sdk.Logger(
53+
logRecordLimits: logRecordLimits,
54+
resource: resource,
55+
instrumentationScope: sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes),
56+
timeProvider: _timeProvider,
57+
onLogEmit: (log) {
58+
for (final processor in processors) {
59+
processor.onEmit(log);
60+
}
61+
},
62+
),
63+
);
64+
}
65+
66+
void addLogRecordProcessor(sdk.LogRecordProcessor processor) {
67+
processors.add(processor);
68+
}
69+
70+
Future<void> forceFlush() async {
71+
await Future.forEach(processors, (e) => e.forceFlush());
72+
}
73+
74+
Future<void> shutdown() async {
75+
await Future.forEach(processors, (e) => e.shutdown());
76+
}
77+
}

test/unit/mocks.dart

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// Copyright 2021-2022 Workiva.
22
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
33

4+
import 'package:fixnum/fixnum.dart';
45
import 'package:http/http.dart' as http;
56
import 'package:mocktail/mocktail.dart';
7+
import 'package:opentelemetry/sdk.dart';
68
import 'package:opentelemetry/src/api/context/context.dart';
79
import 'package:opentelemetry/src/api/trace/span.dart';
8-
import 'package:opentelemetry/src/sdk/trace/read_only_span.dart';
9-
import 'package:opentelemetry/src/sdk/trace/span_processors/span_processor.dart';
10+
import 'package:opentelemetry/src/experimental_sdk.dart';
1011

1112
class MockContext extends Mock implements Context {}
1213

@@ -17,3 +18,13 @@ class MockSpan extends Mock implements Span {}
1718
class MockReadOnlySpan extends Mock implements ReadOnlySpan {}
1819

1920
class MockSpanProcessor extends Mock implements SpanProcessor {}
21+
22+
class MockLogRecordProcessor extends Mock implements LogRecordProcessor {}
23+
24+
class FakeTimeProvider extends Mock implements TimeProvider {
25+
FakeTimeProvider({required Int64 now}) : _now = now;
26+
final Int64 _now;
27+
28+
@override
29+
Int64 get now => _now;
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2021-2022 Workiva.
2+
// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information
3+
4+
@TestOn('vm')
5+
import 'package:fixnum/src/int64.dart';
6+
import 'package:mocktail/mocktail.dart';
7+
import 'package:opentelemetry/sdk.dart' as sdk;
8+
import 'package:opentelemetry/src/experimental_sdk.dart' as sdk;
9+
import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart';
10+
import 'package:test/test.dart';
11+
12+
import '../../mocks.dart';
13+
14+
void main() {
15+
setUpAll(() {
16+
registerFallbackValue(sdk.LogRecord(
17+
instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []),
18+
logRecordLimits: LogRecordLimitsImpl(),
19+
));
20+
});
21+
22+
test('getLogger stores tracers by name', () {
23+
final provider = sdk.LoggerProvider();
24+
final fooTracer = provider.get('foo');
25+
final barTracer = provider.get('bar');
26+
final fooWithVersionTracer = provider.get('foo', version: '1.0');
27+
28+
expect(fooTracer, allOf([isNot(barTracer), isNot(fooWithVersionTracer), same(provider.get('foo'))]));
29+
30+
expect(provider.processors, isA<List<sdk.LogRecordProcessor>>());
31+
});
32+
33+
test('tracerProvider custom span processors', () {
34+
final mockProcessor1 = MockLogRecordProcessor();
35+
final mockProcessor2 = MockLogRecordProcessor();
36+
final provider = sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]);
37+
38+
expect(provider.processors, [mockProcessor1, mockProcessor2]);
39+
});
40+
41+
test('traceProvider custom timeProvider', () {
42+
final mockTimeProvider = FakeTimeProvider(now: Int64(123));
43+
final mockProcessor1 = MockLogRecordProcessor();
44+
final provider = sdk.LoggerProvider(timeProvider: mockTimeProvider, processors: [mockProcessor1]);
45+
provider.get('foo').emit();
46+
verify(() => mockProcessor1.onEmit(any(
47+
that: predicate((a) {
48+
if (a is! sdk.ReadWriteLogRecord) return false;
49+
return a.timeStamp == 123 && a.observedTimestamp == 123;
50+
}),
51+
))).called(1);
52+
});
53+
54+
test('loggerProvider force flushes all processors', () async {
55+
final mockProcessor1 = MockLogRecordProcessor();
56+
final mockProcessor2 = MockLogRecordProcessor();
57+
when(mockProcessor1.forceFlush).thenAnswer((_) async => Future.value());
58+
when(mockProcessor2.forceFlush).thenAnswer((_) async => Future.value());
59+
await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).forceFlush();
60+
61+
verify(mockProcessor1.forceFlush).called(1);
62+
verify(mockProcessor2.forceFlush).called(1);
63+
});
64+
65+
test('loggerProvider shuts down all processors', () async {
66+
final mockProcessor1 = MockLogRecordProcessor();
67+
final mockProcessor2 = MockLogRecordProcessor();
68+
when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value());
69+
when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value());
70+
await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).shutdown();
71+
72+
verify(mockProcessor1.shutdown).called(1);
73+
verify(mockProcessor2.shutdown).called(1);
74+
});
75+
76+
test('logger provider test add processor', () {
77+
final provider = sdk.LoggerProvider()
78+
..addLogRecordProcessor(const sdk.NoopLogRecordProcessor())
79+
..addLogRecordProcessor(const sdk.NoopLogRecordProcessor());
80+
81+
expect(provider.processors.length, 2);
82+
});
83+
}

0 commit comments

Comments
 (0)