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
59 changes: 58 additions & 1 deletion pkgs/google_cloud_logging/lib/src/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,53 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';

import 'package:google_cloud_logging_type/logging_type.dart' as logging_type;

import 'structured_logging.dart' show createStructuredLog, formatStackTrace;

/// The `payload` key used to correlate log entries with Cloud Trace.
///
/// See https://docs.cloud.google.com/logging/docs/agent/logging/configuration#special-fields
const _logTraceKey = 'logging.googleapis.com/trace';

/// The `payload` key used to correlate log entries with a specific span within
/// a Cloud Trace.
///
/// See https://docs.cloud.google.com/logging/docs/agent/logging/configuration#special-fields
const _logSpanIdKey = 'logging.googleapis.com/spanId';

/// The `payload` key used to indicate whether a trace is sampled.
///
/// See https://docs.cloud.google.com/logging/docs/agent/logging/configuration#special-fields
const _logTraceSampledKey = 'logging.googleapis.com/trace_sampled';

// See:
// https://www.w3.org/TR/trace-context/#traceparent-header-field-values
const _version = r'^(?!ff)(?<version>[0-9a-f]{2})';
const _trace = r'(?!0{32})(?<trace>[0-9a-f]{32})';
const _parent = r'(?!0{16})(?<parent>[0-9a-f]{16})';
const _flags = r'(?<flags>[0-9a-f]{2})';
final _traceParentRegex = RegExp('$_version-$_trace-$_parent-$_flags');

/// Parsers a `'tracecontext'` header.
///
/// See https://www.w3.org/TR/trace-context/
({String traceId, String spanId, bool traceSampled})? _parseTraceparent(
String traceparent,
) {
final match = _traceParentRegex.firstMatch(traceparent);
if (match == null) return null;

final flags = int.parse(match.namedGroup('flags')!, radix: 16);
return (
traceId: match.namedGroup('trace')!,
spanId: match.namedGroup('parent')!,
traceSampled: flags & 1 == 1,
);
}

/// Base class for logging.
///
/// Extend this class to create your own logger or use the
Expand Down Expand Up @@ -188,10 +231,24 @@ final class _StructuredLogger extends CloudLogger {
Map<String, Object?>? payload,
StackTrace? stackTrace,
}) {
var p = payload;
final traceparent = Zone.current['traceparent'];
if (traceparent is String) {
final traceData = _parseTraceparent(traceparent);
if (traceData != null) {
p = {
...?payload,
_logTraceKey: traceData.traceId,
_logSpanIdKey: traceData.spanId,
if (traceData.traceSampled) _logTraceSampledKey: true,
};
}
}

final logEntry = createStructuredLog(
message,
severity,
payload: payload,
payload: p,
stackTrace: stackTrace,
);
print(logEntry);
Expand Down
154 changes: 154 additions & 0 deletions pkgs/google_cloud_logging/test/logger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';
import 'dart:convert';

import 'package:google_cloud_logging/google_cloud_logging.dart';
Expand Down Expand Up @@ -128,5 +129,158 @@ Payload: {foo: bar}
),
);
});

group('traceparent zone variable', () {
test('sampled true', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{
'message': 'hello',
'severity': 'INFO',
'logging.googleapis.com/trace':
'4bf92f3577b34da6a3ce929d0e0e4736',
'logging.googleapis.com/spanId': '00f067aa0ba902b7',
'logging.googleapis.com/trace_sampled': true,
},
),
),
),
zoneValues: {
'traceparent':
'00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
},
);
});

test('sampled false', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{
'message': 'hello',
'severity': 'INFO',
'logging.googleapis.com/trace':
'4bf92f3577b34da6a3ce929d0e0e4736',
'logging.googleapis.com/spanId': '00f067aa0ba902b7',
},
),
),
),
zoneValues: {
'traceparent':
'00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00',
},
);
});

test('valid higher version traceparent with extra fields', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{
'message': 'hello',
'severity': 'INFO',
'logging.googleapis.com/trace':
'4bf92f3577b34da6a3ce929d0e0e4736',
'logging.googleapis.com/spanId': '00f067aa0ba902b7',
'logging.googleapis.com/trace_sampled': true,
},
),
),
),
zoneValues: {
'traceparent':
'01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
'-extra-fields',
},
);
});

test('truncated', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{'message': 'hello', 'severity': 'INFO'},
),
),
),
zoneValues: {'traceparent': '00-123'},
);
});

test('version ff', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{'message': 'hello', 'severity': 'INFO'},
),
),
),
zoneValues: {
'traceparent':
'ff-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
},
);
});

test('all zeroes trace-id', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{'message': 'hello', 'severity': 'INFO'},
),
),
),
zoneValues: {
'traceparent':
'00-00000000000000000000000000000000-00f067aa0ba902b7-01',
},
);
});

test('all zeroes parent-id', () {
runZoned(
() => expect(
() => logger.info('hello'),
prints(
isA<String>().having(
(s) => jsonDecode(s) as Map<String, Object?>,
'parsed json',
{'message': 'hello', 'severity': 'INFO'},
),
),
),
zoneValues: {
'traceparent':
'00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01',
},
);
});
});
});
}
1 change: 1 addition & 0 deletions pkgs/google_cloud_shelf/lib/src/http_logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ Middleware cloudLoggingMiddleware(String projectId) {
Zone.current
.fork(
zoneValues: {
'traceparent': request.headers['traceparent'],
_loggerKey: _CloudLogger(
zone: currentZone,
traceContext: traceContext,
Expand Down
Loading