mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
468 lines
16 KiB
Swift
468 lines
16 KiB
Swift
//===----------------- 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.
|
|
@_transparent
|
|
public var maxOSLogArgumentCount: UInt8 { return 48 }
|
|
|
|
@usableFromInline
|
|
@_transparent
|
|
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.
|
|
@_fixed_layout
|
|
public struct OSLogInterpolation : StringInterpolationProtocol {
|
|
/// A format string constructed from the given string interpolation to be
|
|
/// passed to the os_log ABI.
|
|
@usableFromInline
|
|
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 |
|
|
/// ----------------------------
|
|
/// ...
|
|
@usableFromInline
|
|
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.
|
|
@usableFromInline
|
|
@_frozen
|
|
internal enum ArgumentFlag {
|
|
case privateFlag
|
|
case publicFlag
|
|
|
|
@inlinable
|
|
internal var rawValue: UInt8 {
|
|
switch self {
|
|
case .privateFlag:
|
|
return 0x1
|
|
case .publicFlag:
|
|
return 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.
|
|
@usableFromInline
|
|
@_frozen
|
|
internal enum ArgumentType {
|
|
case scalar
|
|
// TODO: more types will be added here.
|
|
|
|
@inlinable
|
|
internal var rawValue: UInt8 {
|
|
switch self {
|
|
case .scalar:
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The first summary byte in the byte buffer passed to the os_log ABI that
|
|
/// summarizes the privacy and nature of the arguments.
|
|
@usableFromInline
|
|
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.
|
|
@usableFromInline
|
|
@_frozen
|
|
internal enum PreambleBitMask {
|
|
case privateBitMask
|
|
case nonScalarBitMask
|
|
|
|
@inlinable
|
|
internal var rawValue: UInt8 {
|
|
switch self {
|
|
case .privateBitMask:
|
|
return 0x1
|
|
case .nonScalarBitMask:
|
|
return 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`.
|
|
@usableFromInline
|
|
internal var argumentCount: UInt8
|
|
|
|
/// Sum total of all the bytes (including header bytes) needed for
|
|
/// serializing the arguments.
|
|
@usableFromInline
|
|
internal var totalBytesForSerializingArguments: Int
|
|
|
|
// Some methods defined below are marked @_optimize(none) to prevent inlining
|
|
// of string internals (such as String._StringGuts) which will interfere with
|
|
// constant evaluation and folding. Note that these methods will be inlined,
|
|
// constant evaluated/folded and optimized in the context of a caller.
|
|
|
|
@_transparent
|
|
@_optimize(none)
|
|
public init(literalCapacity: Int, interpolationCount: Int) {
|
|
// Since the format string is fully constructed at compile time,
|
|
// the parameters `literalCapacity` and `interpolationCount` are ignored.
|
|
formatString = ""
|
|
arguments = OSLogArguments()
|
|
preamble = 0
|
|
argumentCount = 0
|
|
totalBytesForSerializingArguments = 0
|
|
}
|
|
|
|
@_transparent
|
|
@_optimize(none)
|
|
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.
|
|
@_transparent
|
|
@_optimize(none)
|
|
public mutating func appendInterpolation(
|
|
_ number: @autoclosure @escaping () -> Int,
|
|
format: IntFormat = .decimal,
|
|
privacy: Privacy = .public
|
|
) {
|
|
guard argumentCount < maxOSLogArgumentCount else { return }
|
|
|
|
addIntHeadersAndFormatSpecifier(
|
|
format,
|
|
isPrivate: isPrivate(privacy),
|
|
bitWidth: Int.bitWidth,
|
|
isSigned: true)
|
|
arguments.append(number)
|
|
argumentCount += 1
|
|
}
|
|
|
|
/// Construct/update format string and headers from the parameters of the
|
|
/// interpolation.
|
|
@_transparent
|
|
@_optimize(none)
|
|
public mutating func addIntHeadersAndFormatSpecifier(
|
|
_ format: IntFormat,
|
|
isPrivate: Bool,
|
|
bitWidth: Int,
|
|
isSigned: Bool
|
|
) {
|
|
formatString += getIntegerFormatSpecifier(
|
|
format,
|
|
isPrivate: isPrivate,
|
|
bitWidth: bitWidth,
|
|
isSigned: isSigned)
|
|
|
|
// Append argument header.
|
|
let argumentHeader =
|
|
getArgumentHeader(isPrivate: isPrivate, bitWidth: bitWidth, type: .scalar)
|
|
arguments.append(argumentHeader)
|
|
|
|
// Append number of bytes needed to serialize the argument.
|
|
let argumentByteCount = OSLogSerializationInfo.sizeForEncoding(Int.self)
|
|
arguments.append(UInt8(argumentByteCount))
|
|
|
|
// Increment total byte size by the number of bytes needed for this
|
|
// argument, which is the sum of the byte size of the argument and
|
|
// two bytes needed for the headers.
|
|
totalBytesForSerializingArguments += argumentByteCount + 2
|
|
|
|
preamble = getUpdatedPreamble(isPrivate: isPrivate)
|
|
}
|
|
|
|
/// Return true if and only if the parameter is .private.
|
|
@inlinable
|
|
@_semantics("oslog.interpolation.isPrivate")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
internal func isPrivate(_ privacy: Privacy) -> Bool {
|
|
// Do not use equality comparisons on enums as it is not supported by
|
|
// the constant evaluator.
|
|
if case .private = privacy {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// compute a byte-sized argument header consisting of flag and type.
|
|
/// Flag and type take up the least and most significant four bits
|
|
/// of the header byte, respectively.
|
|
/// This function should be constant evaluable.
|
|
@inlinable
|
|
@_semantics("oslog.interpolation.getArgumentHeader")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
internal func getArgumentHeader(
|
|
isPrivate: Bool,
|
|
bitWidth: Int,
|
|
type: ArgumentType
|
|
) -> UInt8 {
|
|
let flag: ArgumentFlag = isPrivate ? .privateFlag : .publicFlag
|
|
let flagAndType: UInt8 = (type.rawValue &<< 4) | flag.rawValue
|
|
return flagAndType
|
|
}
|
|
|
|
/// Compute the new preamble based whether the current argument is private
|
|
/// or not. This function must be constant evaluable.
|
|
@inlinable
|
|
@_semantics("oslog.interpolation.getUpdatedPreamble")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
internal func getUpdatedPreamble(isPrivate: Bool) -> UInt8 {
|
|
if isPrivate {
|
|
return preamble | PreambleBitMask.privateBitMask.rawValue
|
|
}
|
|
return preamble
|
|
}
|
|
|
|
/// Construct an os_log format specifier from the given parameters.
|
|
/// This function must be constant evaluable and all its arguments
|
|
/// must be known at compile time.
|
|
@inlinable
|
|
@_semantics("oslog.interpolation.getFormatSpecifier")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
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 {
|
|
@_semantics("string.escapePercent.get")
|
|
@_effects(readonly)
|
|
@_optimize(none)
|
|
get {
|
|
return self
|
|
.split(separator: "%", omittingEmptySubsequences: false)
|
|
.joined(separator: "%%")
|
|
}
|
|
}
|
|
}
|
|
|
|
@_fixed_layout
|
|
public struct OSLogMessage :
|
|
ExpressibleByStringInterpolation, ExpressibleByStringLiteral
|
|
{
|
|
public let interpolation: OSLogInterpolation
|
|
|
|
/// Initializer for accepting string interpolations.
|
|
@_transparent
|
|
@_optimize(none)
|
|
public init(stringInterpolation: OSLogInterpolation) {
|
|
self.interpolation = stringInterpolation
|
|
}
|
|
|
|
/// Initializer for accepting string literals.
|
|
@_transparent
|
|
@_optimize(none)
|
|
public init(stringLiteral value: String) {
|
|
// Note that the actual value of `literalCapacity` is not important as it
|
|
// is ignored by `OSLogInterpolation.init`.
|
|
var s = OSLogInterpolation(literalCapacity: 1, interpolationCount: 0)
|
|
s.appendLiteral(value)
|
|
self.init(stringInterpolation: s)
|
|
}
|
|
|
|
/// The byte size of the buffer that will be passed to the C os_log ABI.
|
|
/// It will contain the elements of `interpolation.arguments` and the two
|
|
/// summary bytes: preamble and argument count.
|
|
@_transparent
|
|
@_optimize(none)
|
|
public var bufferSize: Int {
|
|
return interpolation.totalBytesForSerializingArguments + 2
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
@usableFromInline
|
|
internal struct OSLogArguments {
|
|
/// An array of closures that captures arguments of possibly different types.
|
|
internal var argumentClosures: [(inout OSLogByteBufferBuilder) -> ()]
|
|
|
|
@usableFromInline
|
|
internal init() {
|
|
argumentClosures = []
|
|
}
|
|
|
|
/// Append a byte-sized header, constructed by
|
|
/// `OSLogMessage.appendInterpolation`, to the tracked array of closures.
|
|
@usableFromInline
|
|
internal mutating func append(_ header: UInt8) {
|
|
argumentClosures.append({ $0.serialize(header) })
|
|
}
|
|
|
|
/// Append an (autoclosured) interpolated expression of type Int, passed to
|
|
/// `OSLogMessage.appendInterpolation`, to the tracked array of closures.
|
|
@usableFromInline
|
|
internal mutating func append(_ value: @escaping () -> Int) {
|
|
argumentClosures.append({ $0.serialize(value()) })
|
|
}
|
|
|
|
@usableFromInline
|
|
internal func serialize(into bufferBuilder: inout OSLogByteBufferBuilder) {
|
|
argumentClosures.forEach { $0(&bufferBuilder) }
|
|
}
|
|
}
|
|
|
|
/// A struct that provides information regarding the serialization of types
|
|
/// to a byte buffer as specified by the C os_log ABIs.
|
|
@usableFromInline
|
|
internal struct OSLogSerializationInfo {
|
|
/// Return the number of bytes needed for serializing an UInt8 value.
|
|
@usableFromInline
|
|
@_transparent
|
|
internal static func sizeForEncoding(_ type: UInt8.Type) -> Int {
|
|
return 1
|
|
}
|
|
|
|
/// Return the number of bytes needed for serializing an Int value.
|
|
@usableFromInline
|
|
@_transparent
|
|
internal static func sizeForEncoding(_ type: Int.Type) -> Int {
|
|
return Int.bitWidth / bitsPerByte
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
@usableFromInline
|
|
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.
|
|
@usableFromInline
|
|
internal init(_ bufferStart: UnsafeMutablePointer<UInt8>) {
|
|
position = bufferStart
|
|
}
|
|
|
|
/// Serialize a UInt8 value at the buffer location pointed to by `position`.
|
|
@usableFromInline
|
|
internal mutating func serialize(_ value: UInt8) {
|
|
position[0] = value
|
|
position += 1
|
|
}
|
|
|
|
/// Serialize an Int at the buffer location pointed to by `position`.
|
|
@usableFromInline
|
|
internal mutating func serialize(_ value: Int) {
|
|
let byteCount = OSLogSerializationInfo.sizeForEncoding(Int.self)
|
|
let dest = UnsafeMutableRawBufferPointer(start: position, count: byteCount)
|
|
withUnsafeBytes(of: value) { dest.copyMemory(from: $0) }
|
|
position += byteCount
|
|
}
|
|
}
|