mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
attributes to the new os log APIs based on string interpolation so that it can be optimized by the new OSLogOptimization compiler pass.
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: Int { 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
|
|
}
|
|
}
|