From 6e60bb896830e32bbf16e65358e7f36e2bfa8466 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 20 May 2026 09:10:56 -0700 Subject: [PATCH 1/5] fix --- .../lib/src/constants.dart | 41 ++++ pkgs/google_cloud_logging/lib/src/logger.dart | 92 ++++++++- .../test/logger_test.dart | 192 ++++++++++++++++++ .../lib/src/http_logging.dart | 1 + 4 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 pkgs/google_cloud_logging/lib/src/constants.dart diff --git a/pkgs/google_cloud_logging/lib/src/constants.dart b/pkgs/google_cloud_logging/lib/src/constants.dart new file mode 100644 index 00000000..95813030 --- /dev/null +++ b/pkgs/google_cloud_logging/lib/src/constants.dart @@ -0,0 +1,41 @@ +// Copyright 2022 Google LLC +// +// 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. + +/// The standard environment variable for specifying the port a service should +/// listen on. +const portEnvironmentVariable = 'PORT'; + +/// The default port a service should listen on if [portEnvironmentVariable] is +/// not set. +const defaultListenPort = 8080; + +/// Standard HTTP header used by +/// [Cloud Trace](https://cloud.google.com/trace/docs/setup). +const cloudTraceContextHeader = 'x-cloud-trace-context'; + +/// 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'; diff --git a/pkgs/google_cloud_logging/lib/src/logger.dart b/pkgs/google_cloud_logging/lib/src/logger.dart index 2dac8dcf..a7b6d9bd 100644 --- a/pkgs/google_cloud_logging/lib/src/logger.dart +++ b/pkgs/google_cloud_logging/lib/src/logger.dart @@ -12,8 +12,11 @@ // 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 'constants.dart'; import 'structured_logging.dart' show createStructuredLog, formatStackTrace; /// Base class for logging. @@ -188,12 +191,99 @@ final class _StructuredLogger extends CloudLogger { Map? payload, StackTrace? stackTrace, }) { + var p = payload; + final traceparent = Zone.current['traceparent']; + if (traceparent is String) { + final data = _parseTraceparent(traceparent); + if (data != null) { + p = { + ...?payload, + logTraceKey: data.traceId, + logSpanIdKey: data.spanId, + if (data.traceSampled) logTraceSampledKey: true, + }; + } + } + final logEntry = createStructuredLog( message, severity, - payload: payload, + payload: p, stackTrace: stackTrace, ); print(logEntry); } } + +class _TraceparentData { + final String traceId; + final String spanId; + final bool traceSampled; + + _TraceparentData({ + required this.traceId, + required this.spanId, + required this.traceSampled, + }); +} + +_TraceparentData? _parseTraceparent(String traceparent) { + final trimmed = traceparent.trim(); + if (trimmed.length < 55) return null; + + // Check version prefix (must be 2 hex characters followed by a dash) + final versionStr = trimmed.substring(0, 2); + if (!_isHex(versionStr)) return null; + if (trimmed[2] != '-') return null; + + if (versionStr == 'ff') return null; + + final isVersion00 = versionStr == '00'; + + if (isVersion00 && trimmed.length != 55) return null; + + // Parse trace-id (32 hex chars, must be followed by a dash) + final traceId = trimmed.substring(3, 35); + if (!_isHex(traceId)) return null; + if (traceId == '00000000000000000000000000000000') return null; + if (trimmed[35] != '-') return null; + + // Parse parent-id (16 hex chars, must be followed by a dash) + final spanId = trimmed.substring(36, 52); + if (!_isHex(spanId)) return null; + if (spanId == '0000000000000000') return null; + if (trimmed[52] != '-') return null; + + // Parse trace-flags (2 hex chars) + final flagsStr = trimmed.substring(53, 55); + if (!_isHex(flagsStr)) return null; + + if (!isVersion00) { + // For higher versions, check that the next character is either the + // end of the string or a dash. + if (trimmed.length > 55 && trimmed[55] != '-') return null; + } + + final flags = int.tryParse(flagsStr, radix: 16); + if (flags == null) return null; + + final traceSampled = (flags & 1) == 1; + + return _TraceparentData( + traceId: traceId, + spanId: spanId, + traceSampled: traceSampled, + ); +} + +bool _isHex(String s) { + for (var i = 0; i < s.length; i++) { + final code = s.codeUnitAt(i); + final isDigit = code >= 48 && code <= 57; // 0-9 + final isLowerHex = code >= 97 && code <= 102; // a-f + if (!isDigit && !isLowerHex) { + return false; + } + } + return true; +} diff --git a/pkgs/google_cloud_logging/test/logger_test.dart b/pkgs/google_cloud_logging/test/logger_test.dart index 829b584c..bd8d5efa 100644 --- a/pkgs/google_cloud_logging/test/logger_test.dart +++ b/pkgs/google_cloud_logging/test/logger_test.dart @@ -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'; @@ -128,5 +129,196 @@ Payload: {foo: bar} ), ); }); + + group('traceparent correlation', () { + test('valid version 00 traceparent, sampled true', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + '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('valid version 00 traceparent, sampled false', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + '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().having( + (s) => jsonDecode(s) as Map, + '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('invalid traceparent: empty or too short', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: {'traceparent': '00-123'}, + ); + }); + + test('invalid traceparent: version ff', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: { + 'traceparent': + 'ff-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + }, + ); + }); + + test('invalid traceparent: version 00 too long', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: { + 'traceparent': + '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra', + }, + ); + }); + + test('invalid traceparent: uppercase characters', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: { + 'traceparent': + '00-4bf92f3577B34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + }, + ); + }); + + test('invalid traceparent: all zeroes trace-id', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: { + 'traceparent': + '00-00000000000000000000000000000000-00f067aa0ba902b7-01', + }, + ); + }); + + test('invalid traceparent: all zeroes parent-id', () { + runZoned( + () => expect( + () => logger.info('hello'), + prints( + isA().having( + (s) => jsonDecode(s) as Map, + 'parsed json', + {'message': 'hello', 'severity': 'INFO'}, + ), + ), + ), + zoneValues: { + 'traceparent': + '00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01', + }, + ); + }); + }); }); } diff --git a/pkgs/google_cloud_shelf/lib/src/http_logging.dart b/pkgs/google_cloud_shelf/lib/src/http_logging.dart index 638c4a01..8df6a2c6 100644 --- a/pkgs/google_cloud_shelf/lib/src/http_logging.dart +++ b/pkgs/google_cloud_shelf/lib/src/http_logging.dart @@ -306,6 +306,7 @@ Middleware cloudLoggingMiddleware(String projectId) { Zone.current .fork( zoneValues: { + 'traceparent': request.headers['traceparent'], _loggerKey: _CloudLogger( zone: currentZone, traceContext: traceContext, From 3756e9a5e673bf94dc8a9e3bf3c53c5324823edb Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 20 May 2026 11:16:31 -0700 Subject: [PATCH 2/5] fix all tests to use --- pkgs/google_cloud_logging/lib/src/logger.dart | 98 +++++-------------- .../test/logger_test.dart | 1 + 2 files changed, 26 insertions(+), 73 deletions(-) diff --git a/pkgs/google_cloud_logging/lib/src/logger.dart b/pkgs/google_cloud_logging/lib/src/logger.dart index a7b6d9bd..37aaa868 100644 --- a/pkgs/google_cloud_logging/lib/src/logger.dart +++ b/pkgs/google_cloud_logging/lib/src/logger.dart @@ -19,6 +19,31 @@ import 'package:google_cloud_logging_type/logging_type.dart' as logging_type; import 'constants.dart'; import 'structured_logging.dart' show createStructuredLog, formatStackTrace; +// See: +// https://www.w3.org/TR/trace-context/#traceparent-header-field-values +const _version = r'^(?!ff)[0-9a-f]{2}'; +const _trace = r'(?!0{32})(?[0-9a-f]{32})'; +const _parent = r'(?!0{16})(?[0-9a-f]{16})'; +const _flags = r'(?[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 @@ -214,76 +239,3 @@ final class _StructuredLogger extends CloudLogger { print(logEntry); } } - -class _TraceparentData { - final String traceId; - final String spanId; - final bool traceSampled; - - _TraceparentData({ - required this.traceId, - required this.spanId, - required this.traceSampled, - }); -} - -_TraceparentData? _parseTraceparent(String traceparent) { - final trimmed = traceparent.trim(); - if (trimmed.length < 55) return null; - - // Check version prefix (must be 2 hex characters followed by a dash) - final versionStr = trimmed.substring(0, 2); - if (!_isHex(versionStr)) return null; - if (trimmed[2] != '-') return null; - - if (versionStr == 'ff') return null; - - final isVersion00 = versionStr == '00'; - - if (isVersion00 && trimmed.length != 55) return null; - - // Parse trace-id (32 hex chars, must be followed by a dash) - final traceId = trimmed.substring(3, 35); - if (!_isHex(traceId)) return null; - if (traceId == '00000000000000000000000000000000') return null; - if (trimmed[35] != '-') return null; - - // Parse parent-id (16 hex chars, must be followed by a dash) - final spanId = trimmed.substring(36, 52); - if (!_isHex(spanId)) return null; - if (spanId == '0000000000000000') return null; - if (trimmed[52] != '-') return null; - - // Parse trace-flags (2 hex chars) - final flagsStr = trimmed.substring(53, 55); - if (!_isHex(flagsStr)) return null; - - if (!isVersion00) { - // For higher versions, check that the next character is either the - // end of the string or a dash. - if (trimmed.length > 55 && trimmed[55] != '-') return null; - } - - final flags = int.tryParse(flagsStr, radix: 16); - if (flags == null) return null; - - final traceSampled = (flags & 1) == 1; - - return _TraceparentData( - traceId: traceId, - spanId: spanId, - traceSampled: traceSampled, - ); -} - -bool _isHex(String s) { - for (var i = 0; i < s.length; i++) { - final code = s.codeUnitAt(i); - final isDigit = code >= 48 && code <= 57; // 0-9 - final isLowerHex = code >= 97 && code <= 102; // a-f - if (!isDigit && !isLowerHex) { - return false; - } - } - return true; -} diff --git a/pkgs/google_cloud_logging/test/logger_test.dart b/pkgs/google_cloud_logging/test/logger_test.dart index bd8d5efa..6a2d7a53 100644 --- a/pkgs/google_cloud_logging/test/logger_test.dart +++ b/pkgs/google_cloud_logging/test/logger_test.dart @@ -130,6 +130,7 @@ Payload: {foo: bar} ); }); + group('traceparent correlation', () { test('valid version 00 traceparent, sampled true', () { runZoned( From 512dbd86fb212721bb0c56da657ad46e7c6d7418 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 20 May 2026 13:21:12 -0700 Subject: [PATCH 3/5] fix --- pkgs/google_cloud_logging/dart_test.yaml | 23 ++ pkgs/google_cloud_logging/lib/src/logger.dart | 5 +- pkgs/google_cloud_logging/pubspec.yaml | 3 + .../google_cloud_logging/test/e2e_server.dart | 45 +++ pkgs/google_cloud_logging/test/e2e_test.dart | 282 ++++++++++++++++++ .../test/logger_test.dart | 1 - 6 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 pkgs/google_cloud_logging/dart_test.yaml create mode 100644 pkgs/google_cloud_logging/test/e2e_server.dart create mode 100644 pkgs/google_cloud_logging/test/e2e_test.dart diff --git a/pkgs/google_cloud_logging/dart_test.yaml b/pkgs/google_cloud_logging/dart_test.yaml new file mode 100644 index 00000000..762f112d --- /dev/null +++ b/pkgs/google_cloud_logging/dart_test.yaml @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# 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. + +tags: + google-cloud: + +exclude_tags: google-cloud + +presets: + google-cloud: + include_tags: google-cloud + platforms: [vm] diff --git a/pkgs/google_cloud_logging/lib/src/logger.dart b/pkgs/google_cloud_logging/lib/src/logger.dart index 37aaa868..1d79297a 100644 --- a/pkgs/google_cloud_logging/lib/src/logger.dart +++ b/pkgs/google_cloud_logging/lib/src/logger.dart @@ -21,7 +21,7 @@ import 'structured_logging.dart' show createStructuredLog, formatStackTrace; // See: // https://www.w3.org/TR/trace-context/#traceparent-header-field-values -const _version = r'^(?!ff)[0-9a-f]{2}'; +const _version = r'^(?!ff)(?[0-9a-f]{2})'; const _trace = r'(?!0{32})(?[0-9a-f]{32})'; const _parent = r'(?!0{16})(?[0-9a-f]{16})'; const _flags = r'(?[0-9a-f]{2})'; @@ -36,6 +36,9 @@ final _traceParentRegex = RegExp('$_version-$_trace-$_parent-$_flags'); final match = _traceParentRegex.firstMatch(traceparent); if (match == null) return null; + final version = match.namedGroup('version')!; + if (version == '00' && traceparent.length != 55) return null; + final flags = int.parse(match.namedGroup('flags')!, radix: 16); return ( traceId: match.namedGroup('trace')!, diff --git a/pkgs/google_cloud_logging/pubspec.yaml b/pkgs/google_cloud_logging/pubspec.yaml index 9b0ad12d..a4568671 100644 --- a/pkgs/google_cloud_logging/pubspec.yaml +++ b/pkgs/google_cloud_logging/pubspec.yaml @@ -18,4 +18,7 @@ dependencies: stack_trace: ^1.11.0 dev_dependencies: + googleapis_auth: ^2.0.0 + http: ^1.3.0 test: ^1.31.0 + test_utils: any diff --git a/pkgs/google_cloud_logging/test/e2e_server.dart b/pkgs/google_cloud_logging/test/e2e_server.dart new file mode 100644 index 00000000..5c4248bc --- /dev/null +++ b/pkgs/google_cloud_logging/test/e2e_server.dart @@ -0,0 +1,45 @@ +// Copyright 2026 Google LLC +// +// 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. + +// ignore_for_file: cascade_invocations + +import 'dart:async'; +import 'dart:io'; +import 'package:google_cloud_logging/google_cloud_logging.dart'; + +void main() async { + final portStr = Platform.environment['PORT'] ?? '8080'; + final port = int.parse(portStr); + + final server = await HttpServer.bind(InternetAddress.anyIPv4, port); + print('Server listening on port $port'); + + await for (final request in server) { + final traceparent = + request.headers.value('traceparent') ?? + request.headers.value('x-cloud-trace-context'); + + runZoned(() { + const logger = CloudLogger.structuredLogger(); + logger.info('E2E_TEST_LOG_MESSAGE'); + + final response = request.response; + response + ..statusCode = HttpStatus.ok + ..headers.contentType = ContentType.json + ..write('{"traceparent": "$traceparent"}') + ..close(); + }, zoneValues: {'traceparent': ?traceparent}); + } +} diff --git a/pkgs/google_cloud_logging/test/e2e_test.dart b/pkgs/google_cloud_logging/test/e2e_test.dart new file mode 100644 index 00000000..123486ef --- /dev/null +++ b/pkgs/google_cloud_logging/test/e2e_test.dart @@ -0,0 +1,282 @@ +// Copyright 2026 Google LLC +// +// 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. + +@Tags(['google-cloud']) +@TestOn('vm') +library; + +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; +import 'package:test_utils/cloud.dart'; + +void main() { + late String serviceName; + late String projectIdVal; + late String serviceUrl; + + setUpAll(() async { + projectIdVal = projectId; + + // Generate a unique service name for Cloud Run. + final rand = Random().nextInt(1000000); + serviceName = 'e2e-trace-test-$rand'; + + final workspaceRoot = findWorkspaceRoot(); + + // Write a temporary Dockerfile in the root directory for Cloud Build. + final dockerfile = File('${workspaceRoot.path}/Dockerfile'); + await dockerfile.writeAsString(''' +FROM dart:stable AS build +WORKDIR /app +COPY . . +RUN dart pub get +RUN dart compile exe pkgs/google_cloud_logging/test/e2e_server.dart -o /app/e2e_server + +FROM dart:stable +COPY --from=build /app/e2e_server /app/e2e_server +EXPOSE 8080 +CMD ["/app/e2e_server"] +'''); + + print('Submitting build to Google Cloud Build...'); + final buildResult = await Process.run('gcloud', [ + 'builds', + 'submit', + '--tag', + 'gcr.io/$projectIdVal/$serviceName', + workspaceRoot.path, + ]); + + // Clean up the temporary Dockerfile. + if (await dockerfile.exists()) { + await dockerfile.delete(); + } + + if (buildResult.exitCode != 0) { + fail( + 'Failed to build container using Cloud Build: ' + '${buildResult.stderr}\n${buildResult.stdout}', + ); + } + + print('Deploying to Cloud Run...'); + final deployResult = await Process.run('gcloud', [ + 'run', + 'deploy', + serviceName, + '--image', + 'gcr.io/$projectIdVal/$serviceName', + '--platform', + 'managed', + '--region', + 'us-central1', + '--allow-unauthenticated', + '--quiet', + ]); + + if (deployResult.exitCode != 0) { + fail( + 'Failed to deploy to Cloud Run: ' + '${deployResult.stderr}\n${deployResult.stdout}', + ); + } + + // Get the URL of the deployed service. + final urlResult = await Process.run('gcloud', [ + 'run', + 'services', + 'describe', + serviceName, + '--platform', + 'managed', + '--region', + 'us-central1', + '--format', + 'value(status.url)', + ]); + + if (urlResult.exitCode != 0) { + fail('Failed to get Cloud Run service URL: ${urlResult.stderr}'); + } + + serviceUrl = urlResult.stdout.toString().trim(); + print('Deployed E2E service URL: $serviceUrl'); + }); + + tearDownAll(() async { + // Ensure the deployed Cloud Run service and container image are cleaned up. + print('Cleaning up Cloud Run service: $serviceName'); + await Process.run('gcloud', [ + 'run', + 'services', + 'delete', + serviceName, + '--platform', + 'managed', + '--region', + 'us-central1', + '--quiet', + ]); + + print('Cleaning up container image: gcr.io/$projectIdVal/$serviceName'); + await Process.run('gcloud', [ + 'container', + 'images', + 'delete', + 'gcr.io/$projectIdVal/$serviceName', + '--force-delete-tags', + '--quiet', + ]); + }); + + test('Google Cloud sets traceparent header and server handles it', () async { + // Send an HTTP request without traceparent header. + // Google Cloud Load Balancer will set traceparent. + final response = await http.get(Uri.parse(serviceUrl)); + expect(response.statusCode, HttpStatus.ok); + + final body = jsonDecode(response.body) as Map; + final traceparent = body['traceparent'] as String?; + expect(traceparent, isNotNull); + print('Google Cloud injected traceparent: $traceparent'); + + // Extract the trace-id and span-id. + // W3C format: version-traceId-parentId-flags + final parts = traceparent!.split('-'); + expect(parts.length, greaterThanOrEqualTo(4)); + final traceId = parts[1]; + final spanId = parts[2]; + + print('Polling Cloud Logging for traceId: $traceId and spanId: $spanId'); + + // Poll Cloud Logging for the ingested stdout structured log. + final filter = + 'resource.type="cloud_run_revision" AND ' + 'resource.labels.service_name="$serviceName" AND ' + 'textPayload:"E2E_TEST_LOG_MESSAGE"'; + + Map? foundLog; + for (var i = 0; i < 12; i++) { + await Future.delayed(const Duration(seconds: 5)); + final result = await Process.run('gcloud', [ + 'logging', + 'read', + filter, + '--format=json', + ]); + if (result.exitCode == 0) { + print(result.stdout.toString()); + final entries = jsonDecode(result.stdout.toString()) as List; + if (entries.isNotEmpty) { + foundLog = entries.first as Map; + break; + } + } + } + + expect( + foundLog, + isNotNull, + reason: 'Ingested log entry not found in Cloud Logging', + ); + expect(foundLog!['trace'], contains(traceId)); + expect(foundLog['spanId'], spanId); + }); + + test('Server handles propagated traceparent header set by user', () async { + // Generate custom valid W3C traceparent. + const customTraceId = '123456789012345678901234567890ab'; + const customSpanId = '1234567890123456'; + const customTraceparent = '00-$customTraceId-$customSpanId-01'; + + final response = await http.get( + Uri.parse(serviceUrl), + headers: {'traceparent': customTraceparent}, + ); + expect(response.statusCode, HttpStatus.ok); + + final body = jsonDecode(response.body) as Map; + final receivedTraceparent = body['traceparent'] as String?; + expect(receivedTraceparent, isNotNull); + print('Received traceparent: $receivedTraceparent'); + + // W3C format: version-traceId-parentId-flags + final parts = receivedTraceparent!.split('-'); + expect(parts.length, greaterThanOrEqualTo(4)); + expect(parts[1], customTraceId); + + // Note: the spanId might have been regenerated by the load balancer, + // but the traceId remains! + final finalSpanId = parts[2]; + + print('Polling Cloud Logging for custom traceId: $customTraceId'); + + final filter = + 'resource.type="cloud_run_revision" AND ' + 'resource.labels.service_name="$serviceName" AND ' + 'textPayload:"E2E_TEST_LOG_MESSAGE"'; + + Map? foundLog; + for (var i = 0; i < 12; i++) { + await Future.delayed(const Duration(seconds: 5)); + final result = await Process.run('gcloud', [ + 'logging', + 'read', + filter, + '--format=json', + ]); + if (result.exitCode == 0) { + final entries = jsonDecode(result.stdout.toString()) as List; + // Find the entry matching our specific trace ID. + for (final entry in entries) { + final map = entry as Map; + if (map['trace'] != null && + map['trace'].toString().contains(customTraceId)) { + foundLog = map; + break; + } + } + } + if (foundLog != null) break; + } + + expect( + foundLog, + isNotNull, + reason: 'Ingested custom log entry not found in Cloud Logging', + ); + expect(foundLog!['trace'], contains(customTraceId)); + expect(foundLog['spanId'], finalSpanId); + }); +} + +Directory findWorkspaceRoot() { + var dir = Directory.current.absolute; + while (true) { + if (File('${dir.path}/librarian.yaml').existsSync()) { + return dir; + } + final parent = dir.parent; + if (parent.path == dir.path) { + throw StateError( + 'Could not find workspace root (librarian.yaml not found)', + ); + } + dir = parent; + } +} diff --git a/pkgs/google_cloud_logging/test/logger_test.dart b/pkgs/google_cloud_logging/test/logger_test.dart index 6a2d7a53..bd8d5efa 100644 --- a/pkgs/google_cloud_logging/test/logger_test.dart +++ b/pkgs/google_cloud_logging/test/logger_test.dart @@ -130,7 +130,6 @@ Payload: {foo: bar} ); }); - group('traceparent correlation', () { test('valid version 00 traceparent, sampled true', () { runZoned( From df9ac57e56f411c00aded1692ec8036b4c83f5b0 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 20 May 2026 14:09:01 -0700 Subject: [PATCH 4/5] fixes --- .../lib/src/constants.dart | 41 --- pkgs/google_cloud_logging/lib/src/logger.dart | 26 +- .../google_cloud_logging/test/e2e_server.dart | 45 --- pkgs/google_cloud_logging/test/e2e_test.dart | 282 ------------------ .../test/logger_test.dart | 54 +--- 5 files changed, 27 insertions(+), 421 deletions(-) delete mode 100644 pkgs/google_cloud_logging/lib/src/constants.dart delete mode 100644 pkgs/google_cloud_logging/test/e2e_server.dart delete mode 100644 pkgs/google_cloud_logging/test/e2e_test.dart diff --git a/pkgs/google_cloud_logging/lib/src/constants.dart b/pkgs/google_cloud_logging/lib/src/constants.dart deleted file mode 100644 index 95813030..00000000 --- a/pkgs/google_cloud_logging/lib/src/constants.dart +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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. - -/// The standard environment variable for specifying the port a service should -/// listen on. -const portEnvironmentVariable = 'PORT'; - -/// The default port a service should listen on if [portEnvironmentVariable] is -/// not set. -const defaultListenPort = 8080; - -/// Standard HTTP header used by -/// [Cloud Trace](https://cloud.google.com/trace/docs/setup). -const cloudTraceContextHeader = 'x-cloud-trace-context'; - -/// 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'; diff --git a/pkgs/google_cloud_logging/lib/src/logger.dart b/pkgs/google_cloud_logging/lib/src/logger.dart index 1d79297a..449b7055 100644 --- a/pkgs/google_cloud_logging/lib/src/logger.dart +++ b/pkgs/google_cloud_logging/lib/src/logger.dart @@ -16,9 +16,24 @@ import 'dart:async'; import 'package:google_cloud_logging_type/logging_type.dart' as logging_type; -import 'constants.dart'; 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)(?[0-9a-f]{2})'; @@ -36,9 +51,6 @@ final _traceParentRegex = RegExp('$_version-$_trace-$_parent-$_flags'); final match = _traceParentRegex.firstMatch(traceparent); if (match == null) return null; - final version = match.namedGroup('version')!; - if (version == '00' && traceparent.length != 55) return null; - final flags = int.parse(match.namedGroup('flags')!, radix: 16); return ( traceId: match.namedGroup('trace')!, @@ -226,9 +238,9 @@ final class _StructuredLogger extends CloudLogger { if (data != null) { p = { ...?payload, - logTraceKey: data.traceId, - logSpanIdKey: data.spanId, - if (data.traceSampled) logTraceSampledKey: true, + _logTraceKey: data.traceId, + _logSpanIdKey: data.spanId, + if (data.traceSampled) _logTraceSampledKey: true, }; } } diff --git a/pkgs/google_cloud_logging/test/e2e_server.dart b/pkgs/google_cloud_logging/test/e2e_server.dart deleted file mode 100644 index 5c4248bc..00000000 --- a/pkgs/google_cloud_logging/test/e2e_server.dart +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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. - -// ignore_for_file: cascade_invocations - -import 'dart:async'; -import 'dart:io'; -import 'package:google_cloud_logging/google_cloud_logging.dart'; - -void main() async { - final portStr = Platform.environment['PORT'] ?? '8080'; - final port = int.parse(portStr); - - final server = await HttpServer.bind(InternetAddress.anyIPv4, port); - print('Server listening on port $port'); - - await for (final request in server) { - final traceparent = - request.headers.value('traceparent') ?? - request.headers.value('x-cloud-trace-context'); - - runZoned(() { - const logger = CloudLogger.structuredLogger(); - logger.info('E2E_TEST_LOG_MESSAGE'); - - final response = request.response; - response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..write('{"traceparent": "$traceparent"}') - ..close(); - }, zoneValues: {'traceparent': ?traceparent}); - } -} diff --git a/pkgs/google_cloud_logging/test/e2e_test.dart b/pkgs/google_cloud_logging/test/e2e_test.dart deleted file mode 100644 index 123486ef..00000000 --- a/pkgs/google_cloud_logging/test/e2e_test.dart +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2026 Google LLC -// -// 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. - -@Tags(['google-cloud']) -@TestOn('vm') -library; - -import 'dart:convert'; -import 'dart:io'; -import 'dart:math'; - -import 'package:http/http.dart' as http; -import 'package:test/test.dart'; -import 'package:test_utils/cloud.dart'; - -void main() { - late String serviceName; - late String projectIdVal; - late String serviceUrl; - - setUpAll(() async { - projectIdVal = projectId; - - // Generate a unique service name for Cloud Run. - final rand = Random().nextInt(1000000); - serviceName = 'e2e-trace-test-$rand'; - - final workspaceRoot = findWorkspaceRoot(); - - // Write a temporary Dockerfile in the root directory for Cloud Build. - final dockerfile = File('${workspaceRoot.path}/Dockerfile'); - await dockerfile.writeAsString(''' -FROM dart:stable AS build -WORKDIR /app -COPY . . -RUN dart pub get -RUN dart compile exe pkgs/google_cloud_logging/test/e2e_server.dart -o /app/e2e_server - -FROM dart:stable -COPY --from=build /app/e2e_server /app/e2e_server -EXPOSE 8080 -CMD ["/app/e2e_server"] -'''); - - print('Submitting build to Google Cloud Build...'); - final buildResult = await Process.run('gcloud', [ - 'builds', - 'submit', - '--tag', - 'gcr.io/$projectIdVal/$serviceName', - workspaceRoot.path, - ]); - - // Clean up the temporary Dockerfile. - if (await dockerfile.exists()) { - await dockerfile.delete(); - } - - if (buildResult.exitCode != 0) { - fail( - 'Failed to build container using Cloud Build: ' - '${buildResult.stderr}\n${buildResult.stdout}', - ); - } - - print('Deploying to Cloud Run...'); - final deployResult = await Process.run('gcloud', [ - 'run', - 'deploy', - serviceName, - '--image', - 'gcr.io/$projectIdVal/$serviceName', - '--platform', - 'managed', - '--region', - 'us-central1', - '--allow-unauthenticated', - '--quiet', - ]); - - if (deployResult.exitCode != 0) { - fail( - 'Failed to deploy to Cloud Run: ' - '${deployResult.stderr}\n${deployResult.stdout}', - ); - } - - // Get the URL of the deployed service. - final urlResult = await Process.run('gcloud', [ - 'run', - 'services', - 'describe', - serviceName, - '--platform', - 'managed', - '--region', - 'us-central1', - '--format', - 'value(status.url)', - ]); - - if (urlResult.exitCode != 0) { - fail('Failed to get Cloud Run service URL: ${urlResult.stderr}'); - } - - serviceUrl = urlResult.stdout.toString().trim(); - print('Deployed E2E service URL: $serviceUrl'); - }); - - tearDownAll(() async { - // Ensure the deployed Cloud Run service and container image are cleaned up. - print('Cleaning up Cloud Run service: $serviceName'); - await Process.run('gcloud', [ - 'run', - 'services', - 'delete', - serviceName, - '--platform', - 'managed', - '--region', - 'us-central1', - '--quiet', - ]); - - print('Cleaning up container image: gcr.io/$projectIdVal/$serviceName'); - await Process.run('gcloud', [ - 'container', - 'images', - 'delete', - 'gcr.io/$projectIdVal/$serviceName', - '--force-delete-tags', - '--quiet', - ]); - }); - - test('Google Cloud sets traceparent header and server handles it', () async { - // Send an HTTP request without traceparent header. - // Google Cloud Load Balancer will set traceparent. - final response = await http.get(Uri.parse(serviceUrl)); - expect(response.statusCode, HttpStatus.ok); - - final body = jsonDecode(response.body) as Map; - final traceparent = body['traceparent'] as String?; - expect(traceparent, isNotNull); - print('Google Cloud injected traceparent: $traceparent'); - - // Extract the trace-id and span-id. - // W3C format: version-traceId-parentId-flags - final parts = traceparent!.split('-'); - expect(parts.length, greaterThanOrEqualTo(4)); - final traceId = parts[1]; - final spanId = parts[2]; - - print('Polling Cloud Logging for traceId: $traceId and spanId: $spanId'); - - // Poll Cloud Logging for the ingested stdout structured log. - final filter = - 'resource.type="cloud_run_revision" AND ' - 'resource.labels.service_name="$serviceName" AND ' - 'textPayload:"E2E_TEST_LOG_MESSAGE"'; - - Map? foundLog; - for (var i = 0; i < 12; i++) { - await Future.delayed(const Duration(seconds: 5)); - final result = await Process.run('gcloud', [ - 'logging', - 'read', - filter, - '--format=json', - ]); - if (result.exitCode == 0) { - print(result.stdout.toString()); - final entries = jsonDecode(result.stdout.toString()) as List; - if (entries.isNotEmpty) { - foundLog = entries.first as Map; - break; - } - } - } - - expect( - foundLog, - isNotNull, - reason: 'Ingested log entry not found in Cloud Logging', - ); - expect(foundLog!['trace'], contains(traceId)); - expect(foundLog['spanId'], spanId); - }); - - test('Server handles propagated traceparent header set by user', () async { - // Generate custom valid W3C traceparent. - const customTraceId = '123456789012345678901234567890ab'; - const customSpanId = '1234567890123456'; - const customTraceparent = '00-$customTraceId-$customSpanId-01'; - - final response = await http.get( - Uri.parse(serviceUrl), - headers: {'traceparent': customTraceparent}, - ); - expect(response.statusCode, HttpStatus.ok); - - final body = jsonDecode(response.body) as Map; - final receivedTraceparent = body['traceparent'] as String?; - expect(receivedTraceparent, isNotNull); - print('Received traceparent: $receivedTraceparent'); - - // W3C format: version-traceId-parentId-flags - final parts = receivedTraceparent!.split('-'); - expect(parts.length, greaterThanOrEqualTo(4)); - expect(parts[1], customTraceId); - - // Note: the spanId might have been regenerated by the load balancer, - // but the traceId remains! - final finalSpanId = parts[2]; - - print('Polling Cloud Logging for custom traceId: $customTraceId'); - - final filter = - 'resource.type="cloud_run_revision" AND ' - 'resource.labels.service_name="$serviceName" AND ' - 'textPayload:"E2E_TEST_LOG_MESSAGE"'; - - Map? foundLog; - for (var i = 0; i < 12; i++) { - await Future.delayed(const Duration(seconds: 5)); - final result = await Process.run('gcloud', [ - 'logging', - 'read', - filter, - '--format=json', - ]); - if (result.exitCode == 0) { - final entries = jsonDecode(result.stdout.toString()) as List; - // Find the entry matching our specific trace ID. - for (final entry in entries) { - final map = entry as Map; - if (map['trace'] != null && - map['trace'].toString().contains(customTraceId)) { - foundLog = map; - break; - } - } - } - if (foundLog != null) break; - } - - expect( - foundLog, - isNotNull, - reason: 'Ingested custom log entry not found in Cloud Logging', - ); - expect(foundLog!['trace'], contains(customTraceId)); - expect(foundLog['spanId'], finalSpanId); - }); -} - -Directory findWorkspaceRoot() { - var dir = Directory.current.absolute; - while (true) { - if (File('${dir.path}/librarian.yaml').existsSync()) { - return dir; - } - final parent = dir.parent; - if (parent.path == dir.path) { - throw StateError( - 'Could not find workspace root (librarian.yaml not found)', - ); - } - dir = parent; - } -} diff --git a/pkgs/google_cloud_logging/test/logger_test.dart b/pkgs/google_cloud_logging/test/logger_test.dart index bd8d5efa..7ebdf2b7 100644 --- a/pkgs/google_cloud_logging/test/logger_test.dart +++ b/pkgs/google_cloud_logging/test/logger_test.dart @@ -130,8 +130,8 @@ Payload: {foo: bar} ); }); - group('traceparent correlation', () { - test('valid version 00 traceparent, sampled true', () { + group('traceparent zone variable', () { + test('sampled true', () { runZoned( () => expect( () => logger.info('hello'), @@ -157,7 +157,7 @@ Payload: {foo: bar} ); }); - test('valid version 00 traceparent, sampled false', () { + test('sampled false', () { runZoned( () => expect( () => logger.info('hello'), @@ -209,7 +209,7 @@ Payload: {foo: bar} ); }); - test('invalid traceparent: empty or too short', () { + test('invalid traceparent', () { runZoned( () => expect( () => logger.info('hello'), @@ -225,10 +225,10 @@ Payload: {foo: bar} ); }); - test('invalid traceparent: version ff', () { + test('version ff', () { runZoned( () => expect( - () => logger.info('hello'), + () => logger.info('hello'), prints( isA().having( (s) => jsonDecode(s) as Map, @@ -244,45 +244,7 @@ Payload: {foo: bar} ); }); - test('invalid traceparent: version 00 too long', () { - runZoned( - () => expect( - () => logger.info('hello'), - prints( - isA().having( - (s) => jsonDecode(s) as Map, - 'parsed json', - {'message': 'hello', 'severity': 'INFO'}, - ), - ), - ), - zoneValues: { - 'traceparent': - '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-extra', - }, - ); - }); - - test('invalid traceparent: uppercase characters', () { - runZoned( - () => expect( - () => logger.info('hello'), - prints( - isA().having( - (s) => jsonDecode(s) as Map, - 'parsed json', - {'message': 'hello', 'severity': 'INFO'}, - ), - ), - ), - zoneValues: { - 'traceparent': - '00-4bf92f3577B34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', - }, - ); - }); - - test('invalid traceparent: all zeroes trace-id', () { + test('all zeroes trace-id', () { runZoned( () => expect( () => logger.info('hello'), @@ -301,7 +263,7 @@ Payload: {foo: bar} ); }); - test('invalid traceparent: all zeroes parent-id', () { + test('all zeroes parent-id', () { runZoned( () => expect( () => logger.info('hello'), From 0606790c1e4c712959978f15c8f06adbe5f79bc7 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 20 May 2026 14:18:16 -0700 Subject: [PATCH 5/5] Fix --- pkgs/google_cloud_logging/dart_test.yaml | 23 ------------------- pkgs/google_cloud_logging/lib/src/logger.dart | 10 ++++---- pkgs/google_cloud_logging/pubspec.yaml | 3 --- .../test/logger_test.dart | 2 +- 4 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 pkgs/google_cloud_logging/dart_test.yaml diff --git a/pkgs/google_cloud_logging/dart_test.yaml b/pkgs/google_cloud_logging/dart_test.yaml deleted file mode 100644 index 762f112d..00000000 --- a/pkgs/google_cloud_logging/dart_test.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2026 Google LLC -# -# 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. - -tags: - google-cloud: - -exclude_tags: google-cloud - -presets: - google-cloud: - include_tags: google-cloud - platforms: [vm] diff --git a/pkgs/google_cloud_logging/lib/src/logger.dart b/pkgs/google_cloud_logging/lib/src/logger.dart index 449b7055..364277e4 100644 --- a/pkgs/google_cloud_logging/lib/src/logger.dart +++ b/pkgs/google_cloud_logging/lib/src/logger.dart @@ -234,13 +234,13 @@ final class _StructuredLogger extends CloudLogger { var p = payload; final traceparent = Zone.current['traceparent']; if (traceparent is String) { - final data = _parseTraceparent(traceparent); - if (data != null) { + final traceData = _parseTraceparent(traceparent); + if (traceData != null) { p = { ...?payload, - _logTraceKey: data.traceId, - _logSpanIdKey: data.spanId, - if (data.traceSampled) _logTraceSampledKey: true, + _logTraceKey: traceData.traceId, + _logSpanIdKey: traceData.spanId, + if (traceData.traceSampled) _logTraceSampledKey: true, }; } } diff --git a/pkgs/google_cloud_logging/pubspec.yaml b/pkgs/google_cloud_logging/pubspec.yaml index a4568671..9b0ad12d 100644 --- a/pkgs/google_cloud_logging/pubspec.yaml +++ b/pkgs/google_cloud_logging/pubspec.yaml @@ -18,7 +18,4 @@ dependencies: stack_trace: ^1.11.0 dev_dependencies: - googleapis_auth: ^2.0.0 - http: ^1.3.0 test: ^1.31.0 - test_utils: any diff --git a/pkgs/google_cloud_logging/test/logger_test.dart b/pkgs/google_cloud_logging/test/logger_test.dart index 7ebdf2b7..934d59c6 100644 --- a/pkgs/google_cloud_logging/test/logger_test.dart +++ b/pkgs/google_cloud_logging/test/logger_test.dart @@ -209,7 +209,7 @@ Payload: {foo: bar} ); }); - test('invalid traceparent', () { + test('truncated', () { runZoned( () => expect( () => logger.info('hello'),