blob: 92238f390b6174dc1c2786c0161009d39cacdf6c [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show utf8;
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:zircon/zircon.dart';
const int _maxCombinedTags = 5;
const int _maxTagLength = 63;
const int _socketBufferLength = 2032;
const int _unexpectedLoggingLevel = 100;
const int _zxClockMonotonic = 0;
final Map<Level, int> _enumToFuchsiaLevelMap = <Level, int>{
Level.FINEST: -4,
Level.FINER: -3,
Level.FINE: -2,
Level.CONFIG: -1,
Level.INFO: 0,
Level.WARNING: 1,
Level.SEVERE: 2,
Level.SHOUT: 3,
};
/// A wrapper around [LogRecord] which appends additional data. This
/// is what is sent to the log writer when a record is received.
class LogMessage {
/// The initial log record
final LogRecord record;
/// Any additional tags to append to the record.
final List<String> tags;
/// The id of the process which this log message is associated with
final int processId;
/// The id of the thread which this log message is associated with
final int threadId;
/// The time that this message was created
final int systemTime = Platform.isFuchsia
? System.clockGet(_zxClockMonotonic)
: DateTime.now().microsecondsSinceEpoch * 1000;
/// The default constructor
LogMessage({
@required this.record,
@required this.processId,
@required this.threadId,
this.tags = const [],
}) : assert(record != null),
assert(processId != null),
assert(threadId != null);
/// Converts this to a ByteData which can be used to send the message to the
/// log socket.
ByteData toBytes() {
ByteData bytes = ByteData(_socketBufferLength)
..setUint64(0, processId, Endian.little)
..setUint64(8, threadId, Endian.little)
..setUint64(16, systemTime, Endian.little)
..setInt32(24, _convertLogLevel(record.level), Endian.little)
..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) {
byteOffset = _setTag(bytes, byteOffset, tag);
totalTagCount++;
}
}
// We need to skip the local tags section since we do not support them
bytes.setUint8(byteOffset++, 0);
// Write message
byteOffset = _setString(bytes, byteOffset, record.message,
_socketBufferLength - byteOffset - 1);
if (record.error != null) {
byteOffset = _setString(
bytes, byteOffset, ': ', _socketBufferLength - byteOffset - 1);
byteOffset = _setString(bytes, byteOffset, record.error.toString(),
_socketBufferLength - byteOffset - 1);
}
if (record.stackTrace != null) {
byteOffset = _setString(
bytes, byteOffset, '\n', _socketBufferLength - byteOffset - 1);
byteOffset = _setString(bytes, byteOffset, record.stackTrace.toString(),
_socketBufferLength - byteOffset - 1);
}
bytes.setUint8(byteOffset++, 0);
return bytes.buffer.asByteData(0, byteOffset);
}
int _convertLogLevel(Level logLevel) =>
_enumToFuchsiaLevelMap[logLevel] ?? _unexpectedLoggingLevel;
/// Write a string to ByteData with a leading length byte. Return the
/// byteOffstet to use for the next value. Wrie a non-terminated string to
/// ByteData. Return the byteOffset to use for the terminating byte or the
/// next value.
int _setString(
ByteData bytes, int firstByteOffset, String value, int maxLen) {
if (value == null || value.isEmpty) {
return firstByteOffset;
}
List<int> charBytes = utf8.encode(value);
int len = min(charBytes.length, maxLen);
int byteOffset = firstByteOffset;
for (int i = 0; i < len; i++) {
bytes.setUint8(byteOffset++, charBytes[i]);
}
// If the string was truncated (and there is space), add an elipsis character.
if (len < charBytes.length && len >= 3) {
const int period = 46; // UTF8 value for '.'
for (int i = 1; i <= 3; i++) {
bytes.setUint8(byteOffset - i, period);
}
}
return byteOffset;
}
int _setTag(ByteData bytes, int byteOffset, String tag) {
if (tag == null || tag == 'null') {
return byteOffset;
}
int nextOffset = _setString(bytes, byteOffset + 1, tag, _maxTagLength);
bytes.setUint8(byteOffset, nextOffset - byteOffset - 1);
return nextOffset;
}
}