chore: use google_cloud_(shelf/logging)#184
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the logging infrastructure to use the google_cloud_logging and google_cloud_shelf packages, removing the custom logger implementation and manual trace propagation logic. The reviewer recommended several enhancements: providing a deprecated warn extension for backward compatibility, ensuring project context is preserved during function registration using runWithLogger, preventing duplicate logs in the emulator environment, and correctly passing the port configuration to the server handler.
| import 'package:google_cloud_logging/google_cloud_logging.dart'; | ||
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; | ||
|
|
||
| import 'package:meta/meta.dart'; | ||
|
|
||
| /// Log severity levels for Cloud Logging. | ||
| /// | ||
| /// See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity). | ||
| enum LogSeverity { | ||
| debug('DEBUG'), | ||
| info('INFO'), | ||
| notice('NOTICE'), | ||
| warning('WARNING'), | ||
| error('ERROR'), | ||
| critical('CRITICAL'), | ||
| alert('ALERT'), | ||
| emergency('EMERGENCY'); | ||
|
|
||
| const LogSeverity(this.value); | ||
|
|
||
| /// The string value used in Cloud Logging JSON entries. | ||
| final String value; | ||
| } | ||
|
|
||
| /// A structured Cloud Logging log entry. | ||
| /// | ||
| /// A [Map] where `severity` (required) and `message` (optional) are standard | ||
| /// Cloud Logging fields. All other keys are included in the `jsonPayload` | ||
| /// of the logged entry. | ||
| typedef LogEntry = Map<String, Object?>; | ||
|
|
||
| /// Removes circular references from an object graph for safe JSON | ||
| /// serialization. | ||
| /// | ||
| /// Returns a new data structure with circular references replaced by the | ||
| /// string `"[Circular]"`. Does not mutate the original object. | ||
| /// | ||
| /// Handles [DateTime] by converting to ISO 8601 UTC string and respects | ||
| /// objects with a `toJson()` method. | ||
| Object? removeCircular(Object? obj, [Set<Object>? existingRefs]) { | ||
| if (obj == null || obj is bool || obj is num || obj is String) { | ||
| return obj; | ||
| } | ||
|
|
||
| if (obj is DateTime) { | ||
| return obj.toUtc().toIso8601String(); | ||
| } | ||
|
|
||
| final refs = existingRefs ?? {}; | ||
|
|
||
| // Handle objects with toJson() (custom serializable types). | ||
| if (obj is! Map && obj is! List) { | ||
| try { | ||
| final result = (obj as dynamic).toJson(); | ||
| return removeCircular(result, refs); | ||
| } catch (_) { | ||
| return obj.toString(); | ||
| } | ||
| } | ||
|
|
||
| if (refs.contains(obj)) { | ||
| return '[Circular]'; | ||
| } | ||
| refs.add(obj); | ||
|
|
||
| late final Object? result; | ||
|
|
||
| if (obj is Map) { | ||
| final map = <String, Object?>{}; | ||
| for (final MapEntry(:key, :value) in obj.entries) { | ||
| try { | ||
| if (value != null && refs.contains(value)) { | ||
| map[key.toString()] = '[Circular]'; | ||
| } else { | ||
| map[key.toString()] = removeCircular(value, refs); | ||
| } | ||
| } catch (_) { | ||
| map[key.toString()] = '[Error - cannot serialize]'; | ||
| } | ||
| } | ||
| result = map; | ||
| } else { | ||
| // obj is List | ||
| result = List<Object?>.generate((obj as List).length, (i) { | ||
| final value = obj[i]; | ||
| try { | ||
| if (value != null && refs.contains(value)) { | ||
| return '[Circular]'; | ||
| } | ||
| return removeCircular(value, refs); | ||
| } catch (_) { | ||
| return '[Error - cannot serialize]'; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| refs.remove(obj); | ||
| return result; | ||
| } | ||
|
|
||
| /// Whether a severity level should be written to stderr. | ||
| bool _isStderrSeverity(String severity) => switch (severity) { | ||
| 'WARNING' || 'ERROR' || 'CRITICAL' || 'ALERT' || 'EMERGENCY' => true, | ||
| _ => false, | ||
| }; | ||
|
|
||
| /// Creates a new [Logger] instance. | ||
| /// | ||
| /// [stdoutWriter] and [stderrWriter] can be provided for testing. | ||
| @internal | ||
| Logger createLogger({ | ||
| void Function(String line)? stdoutWriter, | ||
| void Function(String line)? stderrWriter, | ||
| }) => Logger._(stdoutWriter: stdoutWriter, stderrWriter: stderrWriter); | ||
|
|
||
| /// Structured logger for Cloud Logging, compatible with the Firebase | ||
| /// Functions Node.js SDK `logger` namespace. | ||
| /// | ||
| /// Writes JSON-formatted [LogEntry] objects to stdout or stderr depending | ||
| /// on severity. DEBUG, INFO, and NOTICE go to stdout; WARNING, ERROR, | ||
| /// CRITICAL, ALERT, and EMERGENCY go to stderr. | ||
| /// | ||
| /// ## Usage | ||
| /// | ||
| /// ```dart | ||
| /// import 'package:firebase_functions/logger.dart'; | ||
| /// | ||
| /// // Simple message | ||
| /// logger.info('Request received'); | ||
| /// | ||
| /// // Message with structured data | ||
| /// logger.info('Processing', {'userId': '123', 'action': 'update'}); | ||
| /// | ||
| /// // Structured data only (message inside the map is preserved) | ||
| /// logger.info({'message': 'Custom', 'requestId': 'abc'}); | ||
| /// | ||
| /// // Low-level structured entry | ||
| /// logger.write({'severity': 'NOTICE', 'message': 'Custom', 'code': 42}); | ||
| /// ``` | ||
| final class Logger { | ||
| /// Creates a [Logger] instance. | ||
| /// | ||
| /// Custom [stdoutWriter] and [stderrWriter] can be provided for testing. | ||
| Logger._({ | ||
| void Function(String line)? stdoutWriter, | ||
| void Function(String line)? stderrWriter, | ||
| }) : _stdoutWriter = stdoutWriter ?? _defaultStdoutWriter, | ||
| _stderrWriter = stderrWriter ?? _defaultStderrWriter; | ||
|
|
||
| final void Function(String line) _stdoutWriter; | ||
| final void Function(String line) _stderrWriter; | ||
|
|
||
| static void _defaultStdoutWriter(String line) => io.stdout.writeln(line); | ||
| static void _defaultStderrWriter(String line) => io.stderr.writeln(line); | ||
|
|
||
| /// Writes a [LogEntry] to stdout or stderr depending on severity. | ||
| /// | ||
| /// The entry must contain a `severity` key. If a trace ID is available | ||
| /// in the current [Zone] (via [traceIdZoneKey]), it is automatically added | ||
| /// to the entry. | ||
| void write(LogEntry entry) { | ||
| // Add trace context if available. | ||
|
|
||
| final projectId = Zone.current[projectIdZoneKey] as String?; | ||
| final traceId = Zone.current[traceIdZoneKey] as String?; | ||
|
|
||
| if (projectId != null && traceId != null) { | ||
| assert(projectId.isNotEmpty, 'projectIdZoneKey value must not be empty'); | ||
| assert(traceId.isNotEmpty, 'traceIdZoneKey value must not be empty'); | ||
| entry['logging.googleapis.com/trace'] = | ||
| 'projects/$projectId/traces/$traceId'; | ||
| } | ||
|
|
||
| final sanitized = removeCircular(entry); | ||
| final json = jsonEncode(sanitized); | ||
|
|
||
| final severity = entry['severity'] as String? ?? 'INFO'; | ||
| if (_isStderrSeverity(severity)) { | ||
| _stderrWriter(json); | ||
| } else { | ||
| _stdoutWriter(json); | ||
| } | ||
| } | ||
|
|
||
| /// Writes a DEBUG severity log. | ||
| /// | ||
| /// If [messageOrPayload] is a [Map<String, Object?>] and [jsonPayload] | ||
| /// is null, the map is used directly as the structured log entry | ||
| /// (preserving any `message` key within it). | ||
| /// | ||
| /// Otherwise, [messageOrPayload] is converted to a string for the | ||
| /// `message` field, and [jsonPayload] entries are merged into the entry. | ||
| void debug(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | ||
| write(_entryFromArgs('DEBUG', messageOrPayload, jsonPayload)); | ||
| } | ||
|
|
||
| /// Writes an INFO severity log. Alias for [info]. | ||
| void log(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | ||
| write(_entryFromArgs('INFO', messageOrPayload, jsonPayload)); | ||
| } | ||
|
|
||
| /// Writes an INFO severity log. | ||
| void info(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | ||
| write(_entryFromArgs('INFO', messageOrPayload, jsonPayload)); | ||
| } | ||
|
|
||
| /// Writes a WARNING severity log. | ||
| void warn(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | ||
| write(_entryFromArgs('WARNING', messageOrPayload, jsonPayload)); | ||
| } | ||
|
|
||
| /// Writes an ERROR severity log. | ||
| void error(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | ||
| write(_entryFromArgs('ERROR', messageOrPayload, jsonPayload)); | ||
| } | ||
| } | ||
|
|
||
| /// Constructs a [LogEntry] from a severity, message, and optional JSON | ||
| /// payload. | ||
| /// | ||
| /// When [messageOrPayload] is a [Map<String, Object?>] and [jsonPayload] | ||
| /// is null, the map is used directly as structured data (matching Node.js | ||
| /// behavior where a lone plain-object argument is treated as the entry). | ||
| /// | ||
| /// Otherwise, [messageOrPayload] is stringified and set as the `message` | ||
| /// field, overwriting any `message` key from [jsonPayload]. | ||
| LogEntry _entryFromArgs( | ||
| String severity, | ||
| Object? messageOrPayload, | ||
| Map<String, Object?>? jsonPayload, | ||
| ) { | ||
| // If only a Map was passed, treat it as structured data. | ||
| if (messageOrPayload is Map<String, Object?> && jsonPayload == null) { | ||
| return {...messageOrPayload, 'severity': severity}; | ||
| } | ||
|
|
||
| final entry = <String, Object?>{...?jsonPayload, 'severity': severity}; | ||
|
|
||
| final messageStr = '$messageOrPayload'; | ||
| if (messageStr.isNotEmpty) { | ||
| entry['message'] = messageStr; | ||
| } | ||
|
|
||
| return entry; | ||
| } | ||
|
|
||
| /// Default [Logger] instance. | ||
| /// | ||
| /// This is the primary way to use the logger: | ||
| /// ```dart | ||
| /// import 'package:firebase_functions/logger.dart'; | ||
| /// | ||
| /// logger.info('Hello'); | ||
| /// logger.warn('Something is off', {'requestId': 'abc'}); | ||
| /// ``` | ||
| final logger = Logger._(); | ||
|
|
||
| /// Standard HTTP header used by | ||
| /// [Cloud Trace](https://cloud.google.com/trace/docs/setup). | ||
| @internal | ||
| const cloudTraceContextHeader = 'x-cloud-trace-context'; | ||
|
|
||
| /// Zone key for propagating trace IDs. | ||
| @internal | ||
| final Object traceIdZoneKey = Object(); | ||
|
|
||
| /// Zone key for propagating project ID. | ||
| @internal | ||
| final Object projectIdZoneKey = Object(); | ||
| /// The default instance used for logging in the current context. | ||
| CloudLogger get logger => currentLogger; |
There was a problem hiding this comment.
The logger implementation should export package:google_cloud_logging to provide users with access to the CloudLogger type and utilities like runWithLogger. Additionally, adding a deprecated warn extension helps maintain backward compatibility for existing codebases that haven't migrated to the warning method name yet.
| import 'package:google_cloud_logging/google_cloud_logging.dart'; | |
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; | |
| import 'package:meta/meta.dart'; | |
| /// Log severity levels for Cloud Logging. | |
| /// | |
| /// See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity). | |
| enum LogSeverity { | |
| debug('DEBUG'), | |
| info('INFO'), | |
| notice('NOTICE'), | |
| warning('WARNING'), | |
| error('ERROR'), | |
| critical('CRITICAL'), | |
| alert('ALERT'), | |
| emergency('EMERGENCY'); | |
| const LogSeverity(this.value); | |
| /// The string value used in Cloud Logging JSON entries. | |
| final String value; | |
| } | |
| /// A structured Cloud Logging log entry. | |
| /// | |
| /// A [Map] where `severity` (required) and `message` (optional) are standard | |
| /// Cloud Logging fields. All other keys are included in the `jsonPayload` | |
| /// of the logged entry. | |
| typedef LogEntry = Map<String, Object?>; | |
| /// Removes circular references from an object graph for safe JSON | |
| /// serialization. | |
| /// | |
| /// Returns a new data structure with circular references replaced by the | |
| /// string `"[Circular]"`. Does not mutate the original object. | |
| /// | |
| /// Handles [DateTime] by converting to ISO 8601 UTC string and respects | |
| /// objects with a `toJson()` method. | |
| Object? removeCircular(Object? obj, [Set<Object>? existingRefs]) { | |
| if (obj == null || obj is bool || obj is num || obj is String) { | |
| return obj; | |
| } | |
| if (obj is DateTime) { | |
| return obj.toUtc().toIso8601String(); | |
| } | |
| final refs = existingRefs ?? {}; | |
| // Handle objects with toJson() (custom serializable types). | |
| if (obj is! Map && obj is! List) { | |
| try { | |
| final result = (obj as dynamic).toJson(); | |
| return removeCircular(result, refs); | |
| } catch (_) { | |
| return obj.toString(); | |
| } | |
| } | |
| if (refs.contains(obj)) { | |
| return '[Circular]'; | |
| } | |
| refs.add(obj); | |
| late final Object? result; | |
| if (obj is Map) { | |
| final map = <String, Object?>{}; | |
| for (final MapEntry(:key, :value) in obj.entries) { | |
| try { | |
| if (value != null && refs.contains(value)) { | |
| map[key.toString()] = '[Circular]'; | |
| } else { | |
| map[key.toString()] = removeCircular(value, refs); | |
| } | |
| } catch (_) { | |
| map[key.toString()] = '[Error - cannot serialize]'; | |
| } | |
| } | |
| result = map; | |
| } else { | |
| // obj is List | |
| result = List<Object?>.generate((obj as List).length, (i) { | |
| final value = obj[i]; | |
| try { | |
| if (value != null && refs.contains(value)) { | |
| return '[Circular]'; | |
| } | |
| return removeCircular(value, refs); | |
| } catch (_) { | |
| return '[Error - cannot serialize]'; | |
| } | |
| }); | |
| } | |
| refs.remove(obj); | |
| return result; | |
| } | |
| /// Whether a severity level should be written to stderr. | |
| bool _isStderrSeverity(String severity) => switch (severity) { | |
| 'WARNING' || 'ERROR' || 'CRITICAL' || 'ALERT' || 'EMERGENCY' => true, | |
| _ => false, | |
| }; | |
| /// Creates a new [Logger] instance. | |
| /// | |
| /// [stdoutWriter] and [stderrWriter] can be provided for testing. | |
| @internal | |
| Logger createLogger({ | |
| void Function(String line)? stdoutWriter, | |
| void Function(String line)? stderrWriter, | |
| }) => Logger._(stdoutWriter: stdoutWriter, stderrWriter: stderrWriter); | |
| /// Structured logger for Cloud Logging, compatible with the Firebase | |
| /// Functions Node.js SDK `logger` namespace. | |
| /// | |
| /// Writes JSON-formatted [LogEntry] objects to stdout or stderr depending | |
| /// on severity. DEBUG, INFO, and NOTICE go to stdout; WARNING, ERROR, | |
| /// CRITICAL, ALERT, and EMERGENCY go to stderr. | |
| /// | |
| /// ## Usage | |
| /// | |
| /// ```dart | |
| /// import 'package:firebase_functions/logger.dart'; | |
| /// | |
| /// // Simple message | |
| /// logger.info('Request received'); | |
| /// | |
| /// // Message with structured data | |
| /// logger.info('Processing', {'userId': '123', 'action': 'update'}); | |
| /// | |
| /// // Structured data only (message inside the map is preserved) | |
| /// logger.info({'message': 'Custom', 'requestId': 'abc'}); | |
| /// | |
| /// // Low-level structured entry | |
| /// logger.write({'severity': 'NOTICE', 'message': 'Custom', 'code': 42}); | |
| /// ``` | |
| final class Logger { | |
| /// Creates a [Logger] instance. | |
| /// | |
| /// Custom [stdoutWriter] and [stderrWriter] can be provided for testing. | |
| Logger._({ | |
| void Function(String line)? stdoutWriter, | |
| void Function(String line)? stderrWriter, | |
| }) : _stdoutWriter = stdoutWriter ?? _defaultStdoutWriter, | |
| _stderrWriter = stderrWriter ?? _defaultStderrWriter; | |
| final void Function(String line) _stdoutWriter; | |
| final void Function(String line) _stderrWriter; | |
| static void _defaultStdoutWriter(String line) => io.stdout.writeln(line); | |
| static void _defaultStderrWriter(String line) => io.stderr.writeln(line); | |
| /// Writes a [LogEntry] to stdout or stderr depending on severity. | |
| /// | |
| /// The entry must contain a `severity` key. If a trace ID is available | |
| /// in the current [Zone] (via [traceIdZoneKey]), it is automatically added | |
| /// to the entry. | |
| void write(LogEntry entry) { | |
| // Add trace context if available. | |
| final projectId = Zone.current[projectIdZoneKey] as String?; | |
| final traceId = Zone.current[traceIdZoneKey] as String?; | |
| if (projectId != null && traceId != null) { | |
| assert(projectId.isNotEmpty, 'projectIdZoneKey value must not be empty'); | |
| assert(traceId.isNotEmpty, 'traceIdZoneKey value must not be empty'); | |
| entry['logging.googleapis.com/trace'] = | |
| 'projects/$projectId/traces/$traceId'; | |
| } | |
| final sanitized = removeCircular(entry); | |
| final json = jsonEncode(sanitized); | |
| final severity = entry['severity'] as String? ?? 'INFO'; | |
| if (_isStderrSeverity(severity)) { | |
| _stderrWriter(json); | |
| } else { | |
| _stdoutWriter(json); | |
| } | |
| } | |
| /// Writes a DEBUG severity log. | |
| /// | |
| /// If [messageOrPayload] is a [Map<String, Object?>] and [jsonPayload] | |
| /// is null, the map is used directly as the structured log entry | |
| /// (preserving any `message` key within it). | |
| /// | |
| /// Otherwise, [messageOrPayload] is converted to a string for the | |
| /// `message` field, and [jsonPayload] entries are merged into the entry. | |
| void debug(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | |
| write(_entryFromArgs('DEBUG', messageOrPayload, jsonPayload)); | |
| } | |
| /// Writes an INFO severity log. Alias for [info]. | |
| void log(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | |
| write(_entryFromArgs('INFO', messageOrPayload, jsonPayload)); | |
| } | |
| /// Writes an INFO severity log. | |
| void info(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | |
| write(_entryFromArgs('INFO', messageOrPayload, jsonPayload)); | |
| } | |
| /// Writes a WARNING severity log. | |
| void warn(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | |
| write(_entryFromArgs('WARNING', messageOrPayload, jsonPayload)); | |
| } | |
| /// Writes an ERROR severity log. | |
| void error(Object? messageOrPayload, [Map<String, Object?>? jsonPayload]) { | |
| write(_entryFromArgs('ERROR', messageOrPayload, jsonPayload)); | |
| } | |
| } | |
| /// Constructs a [LogEntry] from a severity, message, and optional JSON | |
| /// payload. | |
| /// | |
| /// When [messageOrPayload] is a [Map<String, Object?>] and [jsonPayload] | |
| /// is null, the map is used directly as structured data (matching Node.js | |
| /// behavior where a lone plain-object argument is treated as the entry). | |
| /// | |
| /// Otherwise, [messageOrPayload] is stringified and set as the `message` | |
| /// field, overwriting any `message` key from [jsonPayload]. | |
| LogEntry _entryFromArgs( | |
| String severity, | |
| Object? messageOrPayload, | |
| Map<String, Object?>? jsonPayload, | |
| ) { | |
| // If only a Map was passed, treat it as structured data. | |
| if (messageOrPayload is Map<String, Object?> && jsonPayload == null) { | |
| return {...messageOrPayload, 'severity': severity}; | |
| } | |
| final entry = <String, Object?>{...?jsonPayload, 'severity': severity}; | |
| final messageStr = '$messageOrPayload'; | |
| if (messageStr.isNotEmpty) { | |
| entry['message'] = messageStr; | |
| } | |
| return entry; | |
| } | |
| /// Default [Logger] instance. | |
| /// | |
| /// This is the primary way to use the logger: | |
| /// ```dart | |
| /// import 'package:firebase_functions/logger.dart'; | |
| /// | |
| /// logger.info('Hello'); | |
| /// logger.warn('Something is off', {'requestId': 'abc'}); | |
| /// ``` | |
| final logger = Logger._(); | |
| /// Standard HTTP header used by | |
| /// [Cloud Trace](https://cloud.google.com/trace/docs/setup). | |
| @internal | |
| const cloudTraceContextHeader = 'x-cloud-trace-context'; | |
| /// Zone key for propagating trace IDs. | |
| @internal | |
| final Object traceIdZoneKey = Object(); | |
| /// Zone key for propagating project ID. | |
| @internal | |
| final Object projectIdZoneKey = Object(); | |
| /// The default instance used for logging in the current context. | |
| CloudLogger get logger => currentLogger; | |
| export 'package:google_cloud_logging/google_cloud_logging.dart'; | |
| import 'package:google_cloud_logging/google_cloud_logging.dart'; | |
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; | |
| /// The default instance used for logging in the current context. | |
| CloudLogger get logger => currentLogger; | |
| extension LoggerCompatibility on CloudLogger { | |
| /// Deprecated alias for [warning]. | |
| @Deprecated('Use warning instead') | |
| void warn(Object? message, [Map<String, Object?>? payload]) => | |
| warning(message, payload: payload); | |
| } |
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; |
There was a problem hiding this comment.
Import package:google_cloud_logging to enable the use of CloudLogger and runWithLogger within the server implementation.
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; | |
| import 'package:google_cloud_logging/google_cloud_logging.dart'; | |
| import 'package:google_cloud_shelf/google_cloud_shelf.dart'; |
| // Run user's function registration code | ||
| await runner(firebase); |
There was a problem hiding this comment.
Logs emitted during the function registration phase (runner(firebase)) currently lack the projectId context because the previous runZoned wrapper was removed. Wrapping this call in runWithLogger ensures that initialization logs are correctly associated with the project in Cloud Logging.
| // Run user's function registration code | |
| await runner(firebase); | |
| // Run user's function registration code | |
| await runWithLogger(CloudLogger(projectId: projectId), () async { | |
| await runner(firebase); | |
| }); |
| middleware = middleware.addMiddleware( | ||
| createLoggingMiddleware(projectId: projectId), | ||
| ); |
There was a problem hiding this comment.
To avoid duplicate request logs in the emulator (where logRequests() is already active at line 83), you can disable request logging in createLoggingMiddleware while still maintaining the necessary logging context for the request handlers.
middleware = middleware.addMiddleware(
createLoggingMiddleware(
projectId: projectId,
logRequests: !env.isEmulator,
),
);| }); | ||
|
|
||
| // Start HTTP server | ||
| await serveHandler(handler); |
There was a problem hiding this comment.
| final env = firebase.$env; | ||
| final projectId = env.projectId; | ||
|
|
||
| await runZoned(zoneValues: {projectIdZoneKey: projectId}, () async { |
There was a problem hiding this comment.
I'm assuming that this is only done for log correlation. The cloudLoggingMiddleware creates a zone for the same purpose.
logging done by runner outside of the context of the middleware (aka request) will use currentLogger (which will be printLogger because there is no logging installed in the zone [I think]).
@kevmoo would it make sense to have an option where google_cloud_shelf can use a structuredLogger for currentLogger if no logger is specified in the zone? We'd use that option here if env.isEmulator is false (i.e. we are running in production).
There was a problem hiding this comment.
I think I see where you're going, but I need more details...
There was a problem hiding this comment.
I'm out until Monday. Let's talk then.
No description provided.