[dart][logger] include logger name in tags

TEST=
- fx run-host-tests fuchsia_logger_package_unittests

Change-Id: Ia3c9374ef1947e016005158038f0786835fbdfc0
diff --git a/public/dart/fuchsia_logger/lib/src/internal/_fuchsia_log_writer.dart b/public/dart/fuchsia_logger/lib/src/internal/_fuchsia_log_writer.dart
index 35dff59..d3c94ce 100644
--- a/public/dart/fuchsia_logger/lib/src/internal/_fuchsia_log_writer.dart
+++ b/public/dart/fuchsia_logger/lib/src/internal/_fuchsia_log_writer.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:logging/logging.dart';
 import 'package:meta/meta.dart';
 import 'package:fidl_fuchsia_logger/fidl_async.dart' as fidl_logger;
@@ -19,10 +21,10 @@
   zircon.Socket _socket;
 
   /// Constructor
-  FuchsiaLogWriter({@required Logger logger})
-      : assert(logger != null),
+  FuchsiaLogWriter({@required Stream<LogRecord> logStream})
+      : assert(logStream != null),
         super(
-          logger: logger,
+          logStream: logStream,
           shouldBufferLogs: true,
         ) {
     _connectToSysLogger();
diff --git a/public/dart/fuchsia_logger/lib/src/internal/_log_message.dart b/public/dart/fuchsia_logger/lib/src/internal/_log_message.dart
index 92238f3..24b835a 100644
--- a/public/dart/fuchsia_logger/lib/src/internal/_log_message.dart
+++ b/public/dart/fuchsia_logger/lib/src/internal/_log_message.dart
@@ -34,7 +34,18 @@
   /// The initial log record
   final LogRecord record;
 
-  /// Any additional tags to append to the record.
+  /// The base name for the logger. This is the name that is assigned in
+  /// setupLogger whereas the name in the record is the name that comes from
+  /// the dart logger instance.
+  final String loggerBaseName;
+
+  /// A string that can be included to identify the call site of this log message
+  final String codeLocation;
+
+  /// Any additional tags to append to the record. When the record it sent to the
+  /// logger it will include these tags after the name and code location if they are
+  /// present. Any tags which are over the limit of the [_maxCombinedTags] will
+  /// be dropped.
   final List<String> tags;
 
   /// The id of the process which this log message is associated with
@@ -53,6 +64,8 @@
     @required this.record,
     @required this.processId,
     @required this.threadId,
+    this.loggerBaseName,
+    this.codeLocation,
     this.tags = const [],
   })  : assert(record != null),
         assert(processId != null),
@@ -69,15 +82,22 @@
       ..setUint32(28, 0, Endian.little); // TODO(120860552) droppedLogs
     int byteOffset = 32;
 
-    // Write global tags
     int totalTagCount = 0;
-    for (final tag in tags) {
-      if (tag != null && totalTagCount < _maxCombinedTags) {
+    void addTag(String tag) {
+      if (totalTagCount < _maxCombinedTags) {
         byteOffset = _setTag(bytes, byteOffset, tag);
         totalTagCount++;
       }
     }
 
+    addTag(loggerBaseName);
+    addTag(record.loggerName);
+    addTag(codeLocation);
+
+    if (tags != null) {
+      tags.forEach(addTag);
+    }
+
     // We need to skip the local tags section since we do not support them
     bytes.setUint8(byteOffset++, 0);
 
@@ -129,7 +149,7 @@
   }
 
   int _setTag(ByteData bytes, int byteOffset, String tag) {
-    if (tag == null || tag == 'null') {
+    if (tag == null || tag == 'null' || tag.isEmpty) {
       return byteOffset;
     }
     int nextOffset = _setString(bytes, byteOffset + 1, tag, _maxTagLength);
diff --git a/public/dart/fuchsia_logger/lib/src/internal/_log_writer.dart b/public/dart/fuchsia_logger/lib/src/internal/_log_writer.dart
index 2deab8f..4055a13 100644
--- a/public/dart/fuchsia_logger/lib/src/internal/_log_writer.dart
+++ b/public/dart/fuchsia_logger/lib/src/internal/_log_writer.dart
@@ -24,6 +24,9 @@
 
   StreamController<LogMessage> _controller;
 
+  /// A name which will prefix each log message and can be used for filtering.
+  String loggerBaseName;
+
   /// If set to true, this method will include the stack trace
   /// in each log record so we can later extract out the call site.
   /// This is a heavy operation and should be used with caution.
@@ -31,9 +34,9 @@
 
   /// Constructor
   LogWriter({
-    @required Logger logger,
+    @required Stream<LogRecord> logStream,
     bool shouldBufferLogs = false,
-  }) : assert(logger != null) {
+  }) : assert(logStream != null) {
     void Function(LogMessage) onMessageFunc;
 
     if (shouldBufferLogs) {
@@ -46,8 +49,7 @@
     } else {
       onMessageFunc = onMessage;
     }
-    logger.onRecord.listen(
-        (record) => onMessageFunc(_messageFromRecord(record)),
+    logStream.listen((record) => onMessageFunc(_messageFromRecord(record)),
         onDone: () => _controller?.close());
   }
 
@@ -79,7 +81,9 @@
         record: record,
         processId: pid,
         threadId: Isolate.current.hashCode,
-        tags: _tagsForLogMessage(),
+        loggerBaseName: loggerBaseName,
+        codeLocation: _codeLocation(),
+        tags: _globalTags,
       );
 
   /// A method for subclasses to implement to handle messages as they are
@@ -95,9 +99,9 @@
   List<String> _verifyGlobalTags(List<String> tags) {
     List<String> result = <String>[];
 
-    // make our own copy to allow us to remove null values an not change the
+    // make our own copy to allow us to remove null values and not change the
     // original values
-    final incomingTags = List.of(tags)
+    final incomingTags = List.of(tags ?? const [])
       ..removeWhere((t) => t == null || t.isEmpty);
 
     if (incomingTags != null) {
@@ -119,14 +123,11 @@
     return result;
   }
 
-  List<String> _tagsForLogMessage() {
+  String _codeLocation() {
     if (forceShowCodeLocation) {
-      final codeLocation = _codeLocationFromStackTrace(StackTrace.current);
-      if (codeLocation != null && codeLocation.isNotEmpty) {
-        return List.of(_globalTags)..add(codeLocation);
-      }
+      return _codeLocationFromStackTrace(StackTrace.current);
     }
-    return _globalTags;
+    return null;
   }
 
   String _codeLocationFromStackTrace(StackTrace stackTrace) {
diff --git a/public/dart/fuchsia_logger/lib/src/internal/_stdout_log_writer.dart b/public/dart/fuchsia_logger/lib/src/internal/_stdout_log_writer.dart
index b5141d0..c6e2a9d 100644
--- a/public/dart/fuchsia_logger/lib/src/internal/_stdout_log_writer.dart
+++ b/public/dart/fuchsia_logger/lib/src/internal/_stdout_log_writer.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:logging/logging.dart';
 import 'package:meta/meta.dart';
 
@@ -11,10 +13,10 @@
 /// A concrete implementation of [LogWriter] which prints the logs to stdout.
 class StdoutLogWriter extends LogWriter {
   /// Constructor
-  StdoutLogWriter({@required Logger logger})
-      : assert(logger != null),
+  StdoutLogWriter({@required Stream<LogRecord> logStream})
+      : assert(logStream != null),
         super(
-          logger: logger,
+          logStream: logStream,
           shouldBufferLogs: false,
         );
 
@@ -24,6 +26,16 @@
       getLevelString(message.record.level),
     ];
 
+    void addToScopes(String scope) {
+      if (scope != null && scope.isNotEmpty) {
+        scopes.add(scope);
+      }
+    }
+
+    addToScopes(message.loggerBaseName);
+    addToScopes(message.record.loggerName);
+    addToScopes(message.codeLocation);
+
     message.tags.forEach(scopes.add);
     String scopesString = scopes.join(':');
 
diff --git a/public/dart/fuchsia_logger/lib/src/logger/logger.dart b/public/dart/fuchsia_logger/lib/src/logger/logger.dart
index cea6821..dbf3929 100644
--- a/public/dart/fuchsia_logger/lib/src/logger/logger.dart
+++ b/public/dart/fuchsia_logger/lib/src/logger/logger.dart
@@ -47,10 +47,11 @@
 /// [Level.ALL].
 ///
 /// If [globalTags] is provided, these tags will be added to each message logged
-/// via this logger. The logger can accept 5 global tags, however, one of those
-/// tags is reserved for the logger name and one is reserved for the code
-/// location if it is requested. The logger will drop any tags that are over
-/// this limit.
+/// via this logger. The system logger can only accept 5 tags with each log
+/// record. Each record will include the name provided in the [setupLogger]
+/// method, the name of the dart logger if it is not the root logger and the
+/// code location if it is requested. Any tags that are over the maximum limit
+/// that the system is allowed to receive will be dropped.
 ///
 /// By default, the caller code location is automatically added in checked mode
 /// and not in production mode, because it is relatively expensive to calculate
@@ -71,16 +72,13 @@
   // method will be a noop. At this point, _logWriter will not be null
   _connectToLogWriterIfNeeded();
 
-  // We need to provide a name for the logger in the form of a tag because
-  // we are not using hierarchical logging.
+  final loggerBaseName = name ??
+      Platform.script?.pathSegments?.lastWhere((_) => true, orElse: () => null);
+
   // Tags get appended to each log statement. We put the name, if present
   // as the first tag so it makes it easier to identify.
   // We remove any null values before sending them to the logger
-  final List<String> tags = [
-    name ??
-        Platform.script?.pathSegments
-            ?.lastWhere((_) => true, orElse: () => null)
-  ]
+  final List<String> tags = []
     ..addAll(globalTags ?? const [])
     ..removeWhere((t) => t == null || t.isEmpty);
 
@@ -91,6 +89,7 @@
   }());
 
   _logWriter
+    ..loggerBaseName = loggerBaseName
     ..globalTags = tags
     ..forceShowCodeLocation = forceShowCodeLocation ?? inCheckedMode;
 }
@@ -104,9 +103,11 @@
     return;
   }
 
+  // we do not use a named logger here because we want to make sure that
+  // the provided name is included as a separate tag.
   if (Platform.isFuchsia) {
-    _logWriter = FuchsiaLogWriter(logger: Logger.root);
+    _logWriter = FuchsiaLogWriter(logStream: Logger.root.onRecord);
   } else {
-    _logWriter = StdoutLogWriter(logger: Logger.root);
+    _logWriter = StdoutLogWriter(logStream: Logger.root.onRecord);
   }
 }
diff --git a/public/dart/fuchsia_logger/test/internal/log_message_test.dart b/public/dart/fuchsia_logger/test/internal/log_message_test.dart
index 627afb9..6497acd 100644
--- a/public/dart/fuchsia_logger/test/internal/log_message_test.dart
+++ b/public/dart/fuchsia_logger/test/internal/log_message_test.dart
@@ -29,6 +29,7 @@
       _validateFixedBlock(buffer, 0, 1, 2);
 
       expect(buffer[32], equals(4));
+      // The name of the logger
       expect(utf8.decode(buffer.sublist(33, 37)), equals('TEST'));
       expect(utf8.decode(buffer.sublist(38, 41)), equals('foo'));
       expect(buffer[41], equals(0));
@@ -44,6 +45,7 @@
       final buffer = bytes.buffer.asUint8List();
       _validateFixedBlock(buffer, 3, 1, 2);
 
+      // The name of the logger
       expect(buffer[32], equals(4));
       expect(utf8.decode(buffer.sublist(33, 37)), equals('TEST'));
       int end = 37;
@@ -73,6 +75,7 @@
       final buffer = bytes.buffer.asUint8List();
       _validateFixedBlock(buffer, 2, 1, 2);
 
+      // The name of the logger
       expect(buffer[32], equals(4));
       expect(utf8.decode(buffer.sublist(33, 37)), equals('TEST'));
       int end = 37;
@@ -104,6 +107,7 @@
       final buffer = bytes.buffer.asUint8List();
       _validateFixedBlock(buffer, -2, 1, 2);
 
+      // The name of the logger
       expect(buffer[32], equals(4));
       expect(utf8.decode(buffer.sublist(33, 37)), equals('TEST'));
       int start = 37;
@@ -130,12 +134,50 @@
       expect(buffer[end++], equals(0));
       expect(bytes.lengthInBytes, equals(end));
     });
+
+    test('convert to bytes with tags and named Logger', () {
+      final tags = ['tag'];
+      final message = _makeMessage(
+        Level.FINE,
+        'bar',
+        tags: tags,
+        loggerName: 'test-logger',
+      );
+
+      final bytes = message.toBytes();
+      final buffer = bytes.buffer.asUint8List();
+      _validateFixedBlock(buffer, -2, 1, 2);
+
+      // The name of the logger
+      expect(buffer[32], equals(4));
+      expect(utf8.decode(buffer.sublist(33, 37)), equals('TEST'));
+      int start = 37;
+
+      // verify that the named logger gets added after the tags
+      expect(buffer[start], equals('test-logger'.length));
+      int end = start + buffer[start] + 1;
+      start++;
+      expect(utf8.decode(buffer.sublist(start, end)), equals('test-logger'));
+
+      // verify the first tag
+      start = end;
+      expect(buffer[start], equals(tags[0].length));
+      end = start + buffer[start] + 1;
+      start++;
+      expect(utf8.decode(buffer.sublist(start, end)), equals(tags[0]));
+
+      // dividing 0 byte
+      expect(buffer[end++], equals(0));
+
+      start = end;
+      expect(utf8.decode(buffer.sublist(start, start + 3)), equals('bar'));
+      end = start + 3;
+      expect(buffer[end++], equals(0));
+      expect(bytes.lengthInBytes, equals(end));
+    });
   });
 }
 
-List<String> _allTags(String name, List<String> tags) =>
-    (name != null ? [name] : [])..addAll(tags ?? []);
-
 /// Convert from little endian format bytes to an unsiged 32 bit int.
 int _bytesToInt32(List<int> bytes) {
   ByteData byteData = ByteData(4);
@@ -160,12 +202,14 @@
         String name = 'TEST',
         Object error,
         StackTrace stackTrace,
-        List<String> tags}) =>
+        List<String> tags,
+        String loggerName = ''}) =>
     LogMessage(
-      record: LogRecord(level, message, null, error, stackTrace),
+      record: LogRecord(level, message, loggerName, error, stackTrace),
       processId: processId,
       threadId: threadId,
-      tags: _allTags(name, tags),
+      loggerBaseName: name,
+      tags: tags,
     );
 
 void _validateFixedBlock(
diff --git a/public/dart/fuchsia_logger/test/internal/log_writer_test.dart b/public/dart/fuchsia_logger/test/internal/log_writer_test.dart
index 542b4c0..ce040dd 100644
--- a/public/dart/fuchsia_logger/test/internal/log_writer_test.dart
+++ b/public/dart/fuchsia_logger/test/internal/log_writer_test.dart
@@ -18,7 +18,7 @@
     Logger logger;
 
     setUp(() {
-      logger = Logger('foo');
+      logger = Logger.root;
     });
 
     tearDown(() {
@@ -128,18 +128,18 @@
     });
 
     test('setting forceShowCodeLocation includes code location in tags', () {
-      List<String> tags;
+      String codeLocation;
 
       StubLogWriter(
           logger: logger,
           onMessageFunc: (m) {
-            tags = m.tags;
+            codeLocation = m.codeLocation;
           }).forceShowCodeLocation = true;
 
       logger.info('foo');
-      expect(tags[0], matches(r'log_writer_test.dart\(\d+\)'));
+      expect(codeLocation, matches(r'log_writer_test.dart\(\d+\)'));
     });
-  }, timeout: Timeout(Duration(milliseconds: 100)));
+  });
 }
 
 class StubLogWriter extends LogWriter {
@@ -149,7 +149,7 @@
     this.onMessageFunc,
     Logger logger,
     shouldBufferLogs = false,
-  }) : super(logger: logger, shouldBufferLogs: shouldBufferLogs);
+  }) : super(logStream: logger.onRecord, shouldBufferLogs: shouldBufferLogs);
 
   @override
   void onMessage(LogMessage message) => this.onMessageFunc(message);
diff --git a/public/dart/fuchsia_logger/test/internal/stdout_log_writer_test.dart b/public/dart/fuchsia_logger/test/internal/stdout_log_writer_test.dart
index 6e957dd..60a91b1 100644
--- a/public/dart/fuchsia_logger/test/internal/stdout_log_writer_test.dart
+++ b/public/dart/fuchsia_logger/test/internal/stdout_log_writer_test.dart
@@ -74,6 +74,13 @@
         logger.info('foo');
       }, (l) => expect(l, matches(r'INFO:stdout_log_writer_test.dart\(\d+\)')));
     });
+
+    test('includes named logger name', () {
+      _overridePrint((logger, writer) {
+        logger.info('foo');
+      }, (l) => expect(l, '[INFO:named-logger] foo'),
+          namedLogger: Logger('named-logger'));
+    });
   });
 
   group('tags', () {
@@ -89,12 +96,15 @@
 // helper method for capturing stdout. Code executed within the [zoned] function
 // will pass the logged output to the [receiver] function with the line that
 // would have gone to stdout.
-void _overridePrint(void Function(Logger, StdoutLogWriter) zoned,
-    void Function(String) receiver) {
+void _overridePrint(
+  void Function(Logger, StdoutLogWriter) zoned,
+  void Function(String) receiver, {
+  Logger namedLogger,
+}) {
   runZoned(
     () {
-      final logger = Logger('foo');
-      zoned(logger, StdoutLogWriter(logger: logger));
+      final logger = namedLogger ?? Logger.root;
+      zoned(logger, StdoutLogWriter(logStream: logger.onRecord));
       logger.clearListeners();
     },
     zoneSpecification: ZoneSpecification(