blob: 61a61b8d48608792c8a0e0b4aedc593204184cd3 [file] [log] [blame]
//===----------------- OSLogMessage.swift ---------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// This file contains data structures and helper functions that are used by
// the new OS log APIs. These are prototype implementations and should not be
// used outside of tests.
/// Formatting options supported by the logging APIs for logging integers.
/// These can be specified in the string interpolation passed to the log APIs.
/// For Example,
/// log.info("Writing to file with permissions: \(perm, format: .octal)")
///
/// See `OSLogInterpolation.appendInterpolation` definitions for default options
/// for integer types.
public enum IntFormat {
case decimal
case hex
case octal
}
/// Privacy qualifiers for indicating the privacy level of the logged data
/// to the logging system. These can be specified in the string interpolation
/// passed to the log APIs.
/// For Example,
/// log.info("Login request from user id \(userid, privacy: .private)")
///
/// See `OSLogInterpolation.appendInterpolation` definitions for default options
/// for each supported type.
public enum Privacy {
case `private`
case `public`
}
/// Maximum number of arguments i.e., interpolated expressions that can
/// be used in the string interpolations passed to the log APIs.
/// This limit is imposed by the ABI of os_log.
public var maxOSLogArgumentCount: Int {
return 48
}
internal var bitsPerByte: Int {
return 8
}
/// Represents a string interpolation passed to the log APIs.
///
/// This type converts (through its methods) the given string interpolation into
/// a C-style format string and a sequence of arguments, which is represented
/// by the type `OSLogArguments`.
///
/// Do not create an instance of this type directly. It is used by the compiler
/// when you pass a string interpolation to the log APIs.
/// Extend this type with more `appendInterpolation` overloads to enable
/// interpolating additional types.
public struct OSLogInterpolation : StringInterpolationProtocol {
/// A format string constructed from the given string interpolation to be
/// passed to the os_log ABI.
internal var formatString: String
/// A representation of a sequence of arguments that must be serialized
/// to a byte buffer and passed to the os_log ABI. Each argument, which is
/// an (autoclosured) expressions that is interpolated, is prepended with a
/// two byte header. The first header byte consists of a four bit flag and
/// a four bit type. The second header byte has the size of the argument in
/// bytes. This is schematically illustrated below.
/// ----------------------------
/// | 4-bit type | 4-bit flag |
/// ----------------------------
/// | 1st argument size in bytes|
/// ----------------------------
/// | 1st argument bytes |
/// ----------------------------
/// | 4-bit type | 4-bit flag |
/// -----------------------------
/// | 2nd argument size in bytes|
/// ----------------------------
/// | 2nd argument bytes |
/// ----------------------------
/// ...
internal var arguments: OSLogArguments
/// The possible values for the argument flag, as defined by the os_log ABI,
/// which occupies four least significant bits of the first byte of the
/// argument header. The first two bits are used to indicate privacy and
/// the other two are reserved.
internal enum ArgumentFlag: UInt8 {
case privateFlag = 0x1
case publicFlag = 0x2
}
/// The possible values for the argument type, as defined by the os_log ABI,
/// which occupies four most significant bits of the first byte of the
/// argument header.
internal enum ArgumentType: UInt8 {
case scalar = 0
// TODO: more types will be added here.
}
/// The first summary byte in the byte buffer passed to the os_log ABI that
/// summarizes the privacy and nature of the arguments.
internal var preamble: UInt8
/// Bit mask for setting bits in the peamble. The bits denoted by the bit
/// mask indicate whether there is an argument that is private, and whether
/// there is an argument that is non-scalar: String, NSObject or Pointer.
internal enum PreambleBitMask: UInt8 {
case privateBitMask = 0x1
case nonScalarBitMask = 0x2
}
/// The second summary byte that denotes the number of arguments, which is
/// also the number of interpolated expressions. This will be determined
/// on the fly in order to support concatenation and interpolation of
/// instances of `OSLogMessage`.
internal var argumentCount: UInt8
public init(literalCapacity: Int, interpolationCount: Int) {
// TODO: format string must be fully constructed at compile time.
// The parameters `literalCapacity` and `interpolationCount` are ignored.
formatString = ""
arguments = OSLogArguments()
preamble = 0
argumentCount = 0
}
public mutating func appendLiteral(_ literal: String) {
formatString += literal.percentEscapedString
}
/// Define interpolation for expressions of type Int. This definition enables
/// passing a formatting option and a privacy qualifier along with the
/// interpolated expression as shown below:
///
/// "\(x, format: .hex, privacy: .private\)"
///
/// - Parameters:
/// - number: the interpolated expression of type Int, which is autoclosured.
/// - format: a formatting option available for Int types, defined by the
/// enum `IntFormat`.
/// - privacy: a privacy qualifier which is either private or public.
/// The default is public.
public mutating func appendInterpolation(
_ number: @autoclosure @escaping () -> Int,
format: IntFormat = .decimal,
privacy: Privacy = .public
) {
guard argumentCount < maxOSLogArgumentCount else { return }
addIntHeadersAndFormatSpecifier(
format,
isPrivate: privacy == .private,
bitWidth: Int.bitWidth,
isSigned: true)
arguments.append(number)
}
/// Construct/update format string and headers from the qualifiers (of the
/// interpolated expression) passed as parameters.
///
/// All arguments to this function must be known at compile time.
public mutating func addIntHeadersAndFormatSpecifier(
_ format: IntFormat,
isPrivate: Bool,
bitWidth: Int,
isSigned: Bool
) {
formatString += getIntegerFormatSpecifier(
format,
isPrivate: isPrivate,
bitWidth: bitWidth,
isSigned: isSigned)
addArgumentHeaders(
flag: isPrivate ? .privateFlag : .publicFlag,
type: .scalar,
size: UInt8(bitWidth / bitsPerByte))
updateSummaryBytes(isPrivate: isPrivate)
}
/// Set the private bit of the preamble if the `isPrivate` parameter is true
/// and increment the argument count. Note that the private bit in the
/// preamable is set if any of the arguments is private.
internal mutating func updateSummaryBytes(isPrivate: Bool) {
if (isPrivate) {
preamble |= PreambleBitMask.privateBitMask.rawValue
}
argumentCount += 1
}
/// Append the given argument headers and size.
internal mutating func addArgumentHeaders(
flag: ArgumentFlag,
type: ArgumentType,
size: UInt8
) {
// Flag and type take up one byte where the least significant four bits
// is flag and most significant four bits is the type.
let flagAndType: UInt8 = (type.rawValue << 4) | flag.rawValue
arguments.append(flagAndType)
arguments.append(size)
}
/// Construct an os_log format specifier from the given parameters.
/// All arguments to this function must be known at compile time.
internal func getIntegerFormatSpecifier(
_ format: IntFormat,
isPrivate: Bool,
bitWidth: Int,
isSigned: Bool
) -> String {
var formatSpecifier: String = isPrivate ? "%{private}" : "%{public}"
// Add a length modifier, if needed, to the specifier
// TODO: more length modifiers will be added.
if (bitWidth == CLongLong.bitWidth) {
formatSpecifier += "ll"
}
// TODO: more format specifiers will be added.
switch (format) {
case .hex:
formatSpecifier += "x"
case .octal:
formatSpecifier += "o"
default:
formatSpecifier += isSigned ? "d" : "u"
}
return formatSpecifier
}
}
extension String {
/// Replace all percents "%" in the string by "%%" so that the string can be
/// interpreted as a C format string.
public var percentEscapedString: String {
get {
return self
.split(separator: "%", omittingEmptySubsequences: false)
.joined(separator: "%%")
}
}
}
public struct OSLogMessage :
ExpressibleByStringInterpolation, ExpressibleByStringLiteral
{
public let interpolation: OSLogInterpolation
/// Initializer for accepting string interpolations.
public init(stringInterpolation: OSLogInterpolation) {
interpolation = stringInterpolation
}
/// Initializer for accepting string literals.
public init(stringLiteral value: String) {
// Note that the actual value of `literalCapacity` is not important as it
// is ignored by `OSLogInterpolation.init`. However, it must be a literal.
var s = OSLogInterpolation(literalCapacity: 1, interpolationCount: 0)
s.appendLiteral(value)
self.init(stringInterpolation: s)
}
/// Format string constructed from the string interpolation.
public var formatString: String {
get { return interpolation.formatString }
}
/// The byte size of the buffer that will passed to the C os_log ABI.
/// It will contain the elements of interpolation.arguments and the two
/// summary bytes: preamble and argument count.
public var bufferSize: Int {
get { return interpolation.arguments.byteCount + 2 }
}
/// Serialize the summary bytes and arguments into the given byte-buffer
/// builder. The summary bytes are serailized first followed by the arguments.
internal func serializeArguments(
into bufferBuilder: inout OSLogByteBufferBuilder
) {
bufferBuilder.serialize(interpolation.preamble)
bufferBuilder.serialize(interpolation.argumentCount)
interpolation.arguments.serialize(into: &bufferBuilder)
}
}
/// A representation of a sequence of arguments and headers (of possibly
/// different types) that have to be serialized to a byte buffer. The arguments
/// are captured within closures and stored in an array. The closures accept an
/// instance of `OSLogByteBufferBuilder`, and when invoked, serialize the
/// argument using the passed `OSLogByteBufferBuilder` instance.
internal struct OSLogArguments {
/// An array of closures that captures arguments of possibly different types.
internal var argumentClosures: [(inout OSLogByteBufferBuilder) -> ()]
/// Sum total of the byte size of the arguments that are tracked.
internal var byteCount: Int
internal init() {
argumentClosures = []
byteCount = 0
}
/// Append a byte-sized header, constructed by
/// `OSLogMessage.appendInterpolation`, to the tracked array of closures.
internal mutating func append(_ header: UInt8) {
argumentClosures.append({ $0.serialize(header) })
byteCount += OSLogByteBufferBuilder.sizeForEncoding(UInt8.self)
}
/// Append an (autoclosured) interpolated expression of type Int, passed to
/// `OSLogMessage.appendInterpolation`, to the tracked array of closures.
internal mutating func append(_ value: @escaping () -> Int) {
argumentClosures.append({ $0.serialize(value()) })
byteCount += OSLogByteBufferBuilder.sizeForEncoding(Int.self)
}
internal func serialize(into bufferBuilder: inout OSLogByteBufferBuilder) {
argumentClosures.forEach { $0(&bufferBuilder) }
}
}
/// A struct that manages serialization of instances of specific types to a
/// byte buffer. The byte buffer is provided as an argument to the initializer
/// so that its lifetime can be managed by the caller.
internal struct OSLogByteBufferBuilder {
internal var position: UnsafeMutablePointer<UInt8>
/// Initializer that accepts a pointer to a preexisting buffer.
/// - Parameter bufferStart: the starting pointer to a byte buffer
/// that must contain the serialized bytes.
internal init(_ bufferStart: UnsafeMutablePointer<UInt8>) {
position = bufferStart
}
/// Serialize a UInt8 value at the buffer location pointed to by `position`.
internal mutating func serialize(_ value: UInt8) {
position[0] = value
position += 1
}
/// Serialize an Int at the buffer location pointed to by `position`.
internal mutating func serialize(_ value: Int) {
let byteCount = OSLogByteBufferBuilder.sizeForEncoding(Int.self)
let dest = UnsafeMutableRawBufferPointer(start: position, count: byteCount)
withUnsafeBytes(of: value) { dest.copyMemory(from: $0) }
position += byteCount
}
/// Return the number of bytes needed for serializing an UInt8 value.
internal static func sizeForEncoding(_ type: UInt8.Type) -> Int {
return 1
}
/// Return the number of bytes needed for serializing an Int value.
internal static func sizeForEncoding(_ type: Int.Type) -> Int {
return Int.bitWidth / bitsPerByte
}
}