├── .gitignore ├── .travis.yml ├── Tests ├── LinuxMain.swift ├── XCTestManifests.swift ├── SignpostTests.swift ├── ActivityTests.swift └── LoggingTests.swift ├── .swiftformat ├── Makefile ├── project.yml ├── Package.swift ├── LICENSE ├── Sources ├── Logging.swift ├── LogMessage.swift ├── Activity.swift └── Signposts.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | /.swiftpm 6 | /build 7 | /Project 8 | /TestResults 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: 3 | - xcode11 4 | - xcode11.1 5 | - xcode11.2 6 | env: 7 | - PLATFORM=macOS 8 | - PLATFORM=iOS 9 | - PLATFORM=tvOS 10 | script: 11 | - make build-test-$PLATFORM 12 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinuxMain.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import XCTest 12 | 13 | import OSLogTraceTests 14 | 15 | var tests = [XCTestCaseEntry]() 16 | tests += OSLogTraceTests.allTests() 17 | XCTMain(tests) 18 | -------------------------------------------------------------------------------- /Tests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestManifests.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import XCTest 12 | 13 | #if !canImport(ObjectiveC) 14 | public func allTests() -> [XCTestCaseEntry] { 15 | return [ 16 | testCase(OSLogTraceTests.allTests), 17 | ] 18 | } 19 | #endif 20 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --swiftversion 5.1 2 | --indent 2 3 | --elseposition next-line 4 | --patternlet inline 5 | --stripunusedargs closure-only 6 | --wraparguments after-first 7 | --binarygrouping none 8 | --decimalgrouping none 9 | --hexgrouping none 10 | --octalgrouping none 11 | --header "//\n// {file}\n// OSLogTrace\n//\n// Copyright © {created.year} Outfox, inc.\n//\n//\n// Distributed under the MIT License, See LICENSE for details.\n//" 12 | --enable blankLinesBetweenScopes -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | project:=OSLogTrace 4 | 5 | build-test: clean build-test-macOS build-test-iOS build-test-tvOS 6 | 7 | clean: 8 | rm -rf $(project).xcodeproj 9 | rm -rf Project 10 | rm -rf TestResults 11 | 12 | define buildtest 13 | xcodebuild -scheme $(project) -resultBundleVersion 3 -resultBundlePath ./TestResults/$(1) -destination '$(2)' test 14 | endef 15 | 16 | build-test-macOS: 17 | $(call buildtest,macOS,platform=macOS) 18 | 19 | build-test-iOS: 20 | $(call buildtest,iOS,name=iPhone 8) 21 | 22 | build-test-tvOS: 23 | $(call buildtest,tvOS,name=Apple TV) 24 | -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | name: OSLogTrace 2 | options: 3 | bundleIdPrefix: io.outfoxx 4 | targets: 5 | OSLogTrace: 6 | type: framework 7 | platform: [macOS, iOS, tvOS, watchOS] 8 | deploymentTarget: 9 | macOS: 10.12 10 | iOS: 10.0 11 | tvOS: 10.0 12 | watchOS: 3.0 13 | sources: [Sources] 14 | info: 15 | path: Project/${platform}/Info.plist 16 | scheme: 17 | testTargets: 18 | - OSLogTraceTests_${platform} 19 | OSLogTraceTests: 20 | type: bundle.unit-test 21 | platform: [macOS, iOS, tvOS, watchOS] 22 | sources: 23 | - path: Tests 24 | excludes: [LinuxMain.swift] 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "OSLogTrace", 7 | platforms: [ 8 | .iOS(.v10), 9 | .macOS(.v10_12), 10 | .watchOS(.v3), 11 | .tvOS(.v10), 12 | ], 13 | products: [ 14 | .library( 15 | name: "OSLogTrace", 16 | targets: ["OSLogTrace"]), 17 | ], 18 | dependencies: [ 19 | ], 20 | targets: [ 21 | .target( 22 | name: "OSLogTrace", 23 | dependencies: [], 24 | path: "./Sources" 25 | ), 26 | .testTarget( 27 | name: "OSLogTraceTests", 28 | dependencies: ["OSLogTrace"], 29 | path: "./Tests") 30 | ] 31 | ) 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Outfox, inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Tests/SignpostTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignpostTests.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import Foundation 12 | import OSLogTrace 13 | import XCTest 14 | 15 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) 16 | class SignpostTests: XCTestCase { 17 | func testManual() { 18 | let audience = "World" 19 | 20 | let log = OSLogManager.for(subsystem: "Main").for(category: "Tests") 21 | let spid = log.signpostID() 22 | log.event(name: "An event", id: spid, message: "Hello \(audience, view: .public)") 23 | log.begin(name: "An event", id: spid, message: "Hello \(audience, view: .public)") 24 | log.end(name: "An event", id: spid, message: "Hello \(audience, view: .public)") 25 | } 26 | 27 | func testAuto() { 28 | let audience = "World" 29 | 30 | let log = OSLogManager.for(subsystem: "Main").for(category: "Tests") 31 | let sp = log.signpost() 32 | sp.event(name: "An event", message: "Hello \(audience)") 33 | sp.begin(name: "An event", message: "Hello \(audience)") 34 | sp.end(name: "An event", message: "Hello \(audience)") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/ActivityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityTests.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | @testable import OSLogTrace 12 | import XCTest 13 | 14 | class ActivityTests: XCTestCase { 15 | func testRunSimple() { 16 | let activity = Activity("A Test Activity") 17 | 18 | activity.run { 19 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 20 | XCTAssertEqual(activeIDs.last, activity.id) 21 | } 22 | } 23 | 24 | func testRunResult() { 25 | let activity = Activity("A Test Activity") 26 | 27 | /// There shoulbe be **NO** `try` required (i.e. ensure "rethrows" not "throws") 28 | /// 29 | let x: Int = activity.run { 30 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 31 | XCTAssertEqual(activeIDs.last, activity.id) 32 | // Ensure synchronicity of `run` 33 | Thread.sleep(forTimeInterval: 0.2) 34 | return 10 35 | } 36 | 37 | XCTAssertEqual(x, 10) 38 | } 39 | 40 | func testRunThrows() { 41 | let activity = Activity("A Test Activity") 42 | 43 | XCTAssertThrowsError( 44 | try activity.run { 45 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 46 | XCTAssertEqual(activeIDs.last, activity.id) 47 | // Ensure synchronicity of `run` 48 | Thread.sleep(forTimeInterval: 0.2) 49 | throw URLError(.badURL) 50 | } 51 | ) 52 | } 53 | 54 | func testLabel() { 55 | Activity.labelUserAction("A Unit Test") 56 | } 57 | 58 | func testImmediate() { 59 | /// There shoulbe be **NO** `try` required (i.e. ensure "rethrows" not "throws") 60 | /// 61 | _ = Activity("A Test Activity") { 62 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 63 | XCTAssertEqual(activeIDs.count, 1) 64 | } 65 | } 66 | 67 | func testImmediateThrows() { 68 | XCTAssertThrowsError( 69 | try Activity("A Test Activity") { 70 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 71 | XCTAssertEqual(activeIDs.count, 1) 72 | throw URLError(.badURL) 73 | } 74 | ) 75 | } 76 | 77 | func testManualScope() { 78 | func doTest() { 79 | let activity = Activity("A Test Activity") 80 | var scope = activity.enter() 81 | defer { scope.leave() } 82 | 83 | let activeIDs = Activity.unsafe.getActiveIDs(max: 16) 84 | XCTAssertEqual(activeIDs.last, activity.id) 85 | } 86 | 87 | // Ensure no current activities 88 | XCTAssertTrue(Activity.unsafe.getActiveIDs(max: 16).isEmpty) 89 | 90 | doTest() 91 | 92 | // Ensure that `leave` actually left 93 | XCTAssertTrue(Activity.unsafe.getActiveIDs(max: 16).isEmpty) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/LoggingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggingTests.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | @testable import OSLogTrace 12 | import XCTest 13 | 14 | class LoggingTests: XCTestCase { 15 | func testLog() { 16 | let logging = OSLogManager.for(subsystem: Bundle(for: LoggingTests.self).bundleIdentifier ?? "none") 17 | let logger = logging.for(category: "Tests") 18 | 19 | let email = "" 20 | logger.info("Hello \(email, view: .private)") 21 | 22 | logger.log("This is interpolated \(5, type: .default, view: .default)") 23 | logger.log("This is interpolated \(5, type: .bitrate, view: .default)") 24 | logger.log("This is interpolated \(5, type: .bytes, view: .default)") 25 | logger.log("This is interpolated \(5, type: .error, view: .default)") 26 | logger.log("This is interpolated \(5, type: .time, view: .default)") 27 | 28 | logger.log("This is interpolated \(5, type: .default, view: .private)") 29 | logger.log("This is interpolated \(5, type: .bitrate, view: .private)") 30 | logger.log("This is interpolated \(5, type: .bytes, view: .private)") 31 | logger.log("This is interpolated \(5, type: .error, view: .private)") 32 | logger.log("This is interpolated \(5, type: .time, view: .private)") 33 | 34 | logger.log("This is interpolated \(5, type: .default, view: .public)") 35 | logger.log("This is interpolated \(5, type: .bitrate, view: .public)") 36 | logger.log("This is interpolated \(5, type: .bytes, view: .public)") 37 | logger.log("This is interpolated \(5, type: .error, view: .public)") 38 | logger.log("This is interpolated \(5, type: .time, view: .public)") 39 | 40 | logger.log("This is interpolated \(UInt(16), radix: .decimal, view: .default)") 41 | logger.log("This is interpolated \(UInt(16), radix: .hex, view: .default)") 42 | logger.log("This is interpolated \(UInt(16), radix: .octal, view: .default)") 43 | 44 | logger.log("This is interpolated \(UInt(16), radix: .decimal, view: .private)") 45 | logger.log("This is interpolated \(UInt(16), radix: .hex, view: .private)") 46 | logger.log("This is interpolated \(UInt(16), radix: .octal, view: .private)") 47 | 48 | logger.log("This is interpolated \(UInt(16), radix: .decimal, view: .public)") 49 | logger.log("This is interpolated \(UInt(16), radix: .hex, view: .public)") 50 | logger.log("This is interpolated \(UInt(16), radix: .octal, view: .public)") 51 | 52 | logger.log("This is interpolated \(Float(5.1), view: .default)") 53 | logger.log("This is interpolated \(Float(5.1), view: .private)") 54 | logger.log("This is interpolated \(Float(5.1), view: .public)") 55 | 56 | logger.log("This is interpolated \(5.2, view: .default)") 57 | logger.log("This is interpolated \(5.2, view: .private)") 58 | logger.log("This is interpolated \(5.2, view: .public)") 59 | 60 | logger.log("This is interpolated \(Date(), view: .default)") 61 | logger.log("This is interpolated \(Date(), view: .private)") 62 | logger.log("This is interpolated \(Date(), view: .public)") 63 | 64 | logger.log("This is interpolated \(UUID(), view: .public)") 65 | 66 | logger.log("This is interpolated \(UUID.self, view: .public)") 67 | 68 | logger.debug("This is a test") 69 | logger.info("This is a test") 70 | logger.log("This is a test") 71 | logger.error("This is a test") 72 | logger.fault("This is a test") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Logging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logging.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import _SwiftOSOverlayShims 12 | import Foundation 13 | import os 14 | @_exported import os.log 15 | 16 | public struct OSLogManager { 17 | public var subsystem: String 18 | public var baseCategory: String? 19 | 20 | public func `for`(category: String? = nil) -> OSLog { 21 | let fullCategory: String 22 | switch (category, baseCategory) { 23 | case (nil, nil): 24 | fullCategory = "" 25 | case (_, nil): 26 | fullCategory = category! 27 | case (nil, _): 28 | fullCategory = baseCategory! 29 | default: 30 | fullCategory = "\(baseCategory!).\(category!)" 31 | } 32 | 33 | return OSLog(subsystem: subsystem, category: fullCategory) 34 | } 35 | 36 | fileprivate init(subsystem: String, baseCategory: String? = nil) { 37 | self.subsystem = subsystem 38 | self.baseCategory = baseCategory 39 | } 40 | } 41 | 42 | extension OSLogManager { 43 | public static func `for`(subsystem: String, baseCategory: String? = nil, configurator: (OSLogManager) -> Void = { _ in }) -> OSLogManager { 44 | let logManager = OSLogManager(subsystem: subsystem, baseCategory: baseCategory) 45 | configurator(logManager) 46 | return logManager 47 | } 48 | } 49 | 50 | public struct OSLogConfig { 51 | public struct Prefixes { 52 | public var debug = "⚫️ " 53 | public var info = "🔵 " 54 | public var `default` = "💬 " 55 | public var error = "⚠️ " 56 | public var fault = "⛔️ " 57 | 58 | public func prefix(for type: OSLogType) -> String { 59 | switch type { 60 | case .debug: return debug 61 | case .info: return info 62 | case .default: return `default` 63 | case .error: return error 64 | case .fault: return fault 65 | default: 66 | fatalError() 67 | } 68 | } 69 | } 70 | 71 | public var prefixes = Prefixes() 72 | } 73 | 74 | public var logConfig = OSLogConfig() 75 | 76 | extension OSLog { 77 | @inline(__always) 78 | public func log(_ message: @autoclosure () -> LogMessage) { 79 | guard isEnabled(type: .default) else { return } 80 | message().log(type: .default, log: self, prefix: logConfig.prefixes.default) 81 | } 82 | 83 | @inline(__always) 84 | public func log(type: OSLogType, _ message: @autoclosure () -> LogMessage) { 85 | guard isEnabled(type: .default) else { return } 86 | message().log(type: type, log: self, prefix: logConfig.prefixes.default) 87 | } 88 | 89 | @inline(__always) 90 | public func info(_ message: @autoclosure () -> LogMessage) { 91 | guard isEnabled(type: .info) else { return } 92 | message().log(type: .info, log: self, prefix: logConfig.prefixes.info) 93 | } 94 | 95 | @inline(__always) 96 | public func debug(_ message: @autoclosure () -> LogMessage) { 97 | guard isEnabled(type: .debug) else { return } 98 | message().log(type: .debug, log: self, prefix: logConfig.prefixes.debug) 99 | } 100 | 101 | @inline(__always) 102 | public func error(_ message: @autoclosure () -> LogMessage) { 103 | guard isEnabled(type: .error) else { return } 104 | message().log(type: .error, log: self, prefix: logConfig.prefixes.error) 105 | } 106 | 107 | @inline(__always) 108 | public func fault(_ message: @autoclosure () -> LogMessage) { 109 | guard isEnabled(type: .fault) else { return } 110 | message().log(type: .fault, log: self, prefix: logConfig.prefixes.fault) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/LogMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogMessage.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import _SwiftOSOverlayShims 12 | import Foundation 13 | import os 14 | import os.log 15 | 16 | public enum LogArgumentType: String { 17 | case bytes = "iec-bytes" 18 | case bitrate = "iec-bitrate" 19 | case time = "time_t" 20 | case error = "errno" 21 | case `default` = "" 22 | } 23 | 24 | public enum LogArgumentView: String { 25 | case `public` = "public" 26 | case `private` = "private" 27 | case `default` = "" 28 | } 29 | 30 | public enum LogArgumentRadix: String { 31 | case decimal = "u" 32 | case octal = "o" 33 | case hex = "x" 34 | } 35 | 36 | public struct LogMessage: ExpressibleByStringInterpolation { 37 | public struct StringInterpolation: StringInterpolationProtocol { 38 | var format = "" 39 | var arguments: [CVarArg] = [] 40 | 41 | public init(literalCapacity: Int, interpolationCount: Int) { 42 | format.reserveCapacity(literalCapacity + (interpolationCount * 10)) 43 | arguments.reserveCapacity(interpolationCount) 44 | } 45 | 46 | public mutating func appendLiteral(_ literal: String) { 47 | format += literal 48 | } 49 | 50 | public mutating func appendInterpolation(_ value: T?, view: LogArgumentView = .default) { 51 | guard let value = value else { format += ""; return } 52 | format += "%\(spec(view: view))s" 53 | arguments.append(String(describing: value)) 54 | } 55 | 56 | public mutating func appendInterpolation(_ value: N?, view: LogArgumentView = .default) where N: NSObjectProtocol & CVarArg { 57 | guard let value = value else { format += ""; return } 58 | format += "%\(spec(view: view))@" 59 | arguments.append(value) 60 | } 61 | 62 | public mutating func appendInterpolation(_ value: S?, view: LogArgumentView = .default) where S: StringProtocol & CVarArg { 63 | guard let value = value else { format += ""; return } 64 | format += "%\(spec(view: view))s" 65 | arguments.append(value) 66 | } 67 | 68 | public mutating func appendInterpolation(_ value: F?, view: LogArgumentView = .default) where F: BinaryFloatingPoint & CVarArg { 69 | guard let value = value else { format += ""; return } 70 | format += "%\(spec(view: view))\(prefix(value))g" 71 | arguments.append(value) 72 | } 73 | 74 | public mutating func appendInterpolation(_ value: SI?, type: LogArgumentType = .default, view: LogArgumentView = .default) where SI: SignedInteger & CVarArg { 75 | guard let value = value else { format += ""; return } 76 | format += "%\(spec(view: view, type: type))\(prefix(value))d" 77 | arguments.append(value) 78 | } 79 | 80 | public mutating func appendInterpolation(_ value: UI?, radix: LogArgumentRadix, view: LogArgumentView = .default) where UI: UnsignedInteger & CVarArg { 81 | guard let value = value else { format += ""; return } 82 | format += "%\(spec(view: view))\(prefix(value))\(radix.rawValue)" 83 | arguments.append(value) 84 | } 85 | 86 | public mutating func appendInterpolation(_ value: Date?, view: LogArgumentView = .default) { 87 | guard let value = value else { format += ""; return } 88 | format += "%{time_t}d" 89 | arguments.append(Int(value.timeIntervalSince1970)) 90 | } 91 | 92 | public mutating func appendInterpolation(_ value: UUID?, view: LogArgumentView = .default) { 93 | guard let value = value else { format += ""; return } 94 | format += "%s" 95 | arguments.append(value.uuidString) 96 | } 97 | } 98 | 99 | let interpolation: StringInterpolation 100 | 101 | public init(stringLiteral value: StringLiteralType) { 102 | var interpolation = StringInterpolation(literalCapacity: 0, interpolationCount: 0) 103 | interpolation.appendLiteral(value) 104 | self.interpolation = interpolation 105 | } 106 | 107 | public init(stringInterpolation: StringInterpolation) { 108 | interpolation = stringInterpolation 109 | } 110 | 111 | public func log(type: OSLogType, log: OSLog, prefix: String, dso: UnsafeRawPointer = #dsohandle) { 112 | let ra = _swift_os_log_return_address() 113 | "\(prefix)\(interpolation.format)".withCString { str in 114 | withVaList(interpolation.arguments) { args in 115 | _swift_os_log(dso, ra, log, .default, str, args) 116 | } 117 | } 118 | } 119 | } 120 | 121 | private func spec(view: LogArgumentView = .default, type: LogArgumentType = .default) -> String { 122 | switch (view, type) { 123 | case (.default, .default): return "" 124 | case (.default, _): return "{\(type.rawValue)}" 125 | case (_, .default): return "{\(view.rawValue)}" 126 | default: return "{\(view.rawValue),\(type.rawValue)}" 127 | } 128 | } 129 | 130 | let intPrefixes = ["hh", "h", "l", "ll"] 131 | let floatPrefixes = ["", "", "L"] 132 | 133 | func prefix(_ value: T) -> String { return intPrefixes[MemoryLayout.size / 8] } 134 | func prefix(_ value: T) -> String { return floatPrefixes[Int(ceil(Double(MemoryLayout.size) / 32.0))] } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📒OSLogTrace 2 | [![Build Status](https://travis-ci.org/outfoxx/OSLogTrace.svg?branch=master)](https://travis-ci.org/outfoxx/OSLogTrace) 3 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/outfoxx/oslogtrace) 4 | ## API for Apple's System Log, Signposts and Activty tracing built for Swift 5 | The framework uses features introduced in Swift 5 to make interacting with Apple system log natural and easy from Swift code. Swift 6 | oriented convenience APIs for logging signposts and activity tracing are also included to make their use natural as well. 7 | 8 | ## Logging 9 | 10 | ### OSLogManager 11 | 12 | The log manager, `OSLogManager`, is a factory to vend configured `OSLog` instances. 13 | 14 | Use by calling the static `for` method to create an instance of `OSLogManager`: 15 | ```swift 16 | let log = OSLogManager.for(category: "My Application") 17 | ``` 18 | 19 | Further configuration can be achieved by providing a configuration block to alter the manager's properties: 20 | ```swift 21 | let logManager = OSLogManager.for(category: "My Application") { logManager in 22 | logManager.baseCategory = "General" 23 | } 24 | ``` 25 | 26 | `OSLogManager` & `OSLog` provide prefixes that can be used to help identify log messages in complex output. The default values use emoji to 27 | uniquely identify each log level. For customization, application-wide configuration of logging prefixes can be done using the global 28 | variable `logConfig`: 29 | ```swift 30 | OSLogTrace.logConfig.prefix.error = "☢️" 31 | ``` 32 | 33 | ### OSLog 34 | 35 | The logging interface uses the standard `OSLog` class provided by the `os.log` package. It is extended with typed methods for each log level and 36 | take a `LogMessage` to provide parameterized logging messages using Swift 5's string interpolation magic. 37 | 38 | ```swift 39 | extension OSLog { 40 | public func log(type: OSLogType, _ message: @autoclosure () -> LogMessage) 41 | public func log(_ message: @autoclosure () -> LogMessage) 42 | public func info(_ message: @autoclosure () -> LogMessage) 43 | public func debug(_ message: @autoclosure () -> LogMessage) 44 | public func error(_ message: @autoclosure () -> LogMessage) 45 | public func fault(_ message: @autoclosure () -> LogMessage) 46 | } 47 | ``` 48 | 49 | ### LogMessage 50 | 51 | Generating parameterized log messages is simple using `LogMessage` and thanks to Swift 5's string interpolation support 52 | (via `ExpressibleByStringInterpolation`) they can be created using standard Swift syntax... 53 | 54 | ```swift 55 | let audience = "World" 56 | log.info("Hello \(audience)") 57 | ``` 58 | 59 | This simple log message properly passes the `audience` parameter on to the `OSLog` as a _dynamic parameter_. This defers 60 | message creation until needed and allows us to control how the system log displays and reports these parameters. 61 | 62 | ##### Display 63 | 64 | Apple's system log can interpret certain types of data and OSLogTrace's logging extensions expose that capability naturally. 65 | 66 | For example, if you are logging download progress you might use: 67 | ```swift 68 | let completedBytes = 2.4 * 1024 * 1024 69 | log.debug("Downloaded \(completedBytes, unit: .bytes)") 70 | ``` 71 | 72 | The log will display the value using the best available unit. In this specfic case it would be reported as: 73 | 74 | Downloaded 2.4 MiB 75 | 76 | ##### Privacy 77 | 78 | Apple's System Log supports privacy as well. Marking parameters as `private` will ensure this information is not stored long term and redacted in 79 | the right context. 80 | 81 | For example, logging a sign-in that contains a private email address is simple: 82 | ```swift 83 | let accountEmail = "test@example.com" 84 | log.info("Sign-in from \(accountEmail, view: .private)") 85 | ``` 86 | 87 | The system log will now take care of handling the sensitive email data. 88 | 89 | For more information see "Formmatting Log Messages" in 90 | [Apple's Logging documentation](https://developer.apple.com/documentation/os/logging#topics) 91 | 92 | ### Signposts 93 | 94 | Signpost's are Apple's logging enhancement for debugging and profiling working side-by-side with `OSLog`. Signpost IDs can be 95 | created and marked using OSLogTrace's convenience API(s). 96 | 97 | Create a signpost ID unique to a specific log instance, and mark it: 98 | ```swift 99 | let log: OSLog = ... 100 | let spid = log.signpostID() // Create ID 101 | log.event("Stage 1", spid) // Mark "event" 102 | log.event("Stage 1", spid, "A \(parameterized) message") // Mark "event" with a log message 103 | ``` 104 | 105 | Utilize the `Signpost` convenience API to manage a signpost ID and log together: 106 | ```swift 107 | let log: OSLog = ... 108 | let sp = log.signpost() // Create a Signpost with a unique signpost ID 109 | sp.event("Stage 1") // Mark "event" 110 | sp.event("Stage 1", "A \(parameterized) message") // Mark "event" with a log message 111 | ``` 112 | 113 | 114 | ## Activity Tracing 115 | 116 | OSLogTrace also provides a convenience API for Apple's activity tracing. 117 | 118 | Create an `Activity` and immediately execute code in its context: 119 | ```swift 120 | Activity("Download Email") { 121 | // download the emails 122 | } 123 | ``` 124 | 125 | Create an `Activity` and execute multiple code blocks in its context: 126 | ```swift 127 | let emailDownload = Activity("Download Email") 128 | 129 | emailDownload.run { 130 | // download some emails 131 | } 132 | 133 | ... 134 | 135 | emailDownload.run { 136 | // download some emails 137 | } 138 | ``` 139 | 140 | Create an `Activity` and manually manage the entering and leaving of its scope/context: 141 | ```swift 142 | let emailDownload = Activity("Download Email") 143 | 144 | let scope = emailDownload.enter() 145 | defer { scope.leave() } 146 | 147 | // download some emails 148 | ``` 149 | -------------------------------------------------------------------------------- /Sources/Activity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Activity.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import Foundation 12 | import os.activity 13 | 14 | /// An OS activity 15 | /// 16 | public struct Activity { 17 | /// Unique ID of the activity 18 | public typealias ID = os_activity_id_t 19 | 20 | /// Activity with no current traits. 21 | /// 22 | /// When used as a parent activity, it is the equivalent of a passing the `detached` flag. 23 | /// 24 | public static let none = Activity(_none) 25 | 26 | /// Represents the activity of the current thread. 27 | /// 28 | /// When used as a parent activity, it links to the current activity if one is present. If no 29 | /// activity is present it is treated as if it is `detached`. 30 | /// 31 | public static let current = Activity(_none) 32 | 33 | /// Options to create activity objects 34 | /// 35 | public struct Options: OptionSet { 36 | public let rawValue: UInt32 37 | 38 | public init(rawValue: UInt32) { 39 | self.rawValue = rawValue 40 | } 41 | 42 | /// Use the default flags 43 | public static let `default` = Options(rawValue: OS_ACTIVITY_FLAG_DEFAULT.rawValue) 44 | 45 | /// Detach the newly created activity from the provided activity (if any). If passed in conjunction with an existing 46 | /// activity, the activity will only note what activity "created" the new one, but will make the new activity a top 47 | /// level activity. This allows users to see what activity triggered work without actually relating the activities. 48 | public static let detached = Options(rawValue: OS_ACTIVITY_FLAG_DETACHED.rawValue) 49 | 50 | /// Will only create a new activity if none present. If an activity ID is already present, a new object will be 51 | /// returned with the same activity ID underneath. 52 | public static let ifNonePresent = Options(rawValue: OS_ACTIVITY_FLAG_IF_NONE_PRESENT.rawValue) 53 | } 54 | 55 | /// Returns the ID of the this activity. 56 | /// 57 | public var id: ID { 58 | return os_activity_get_identifier(impl, nil) 59 | } 60 | 61 | /// Returns the ID of the parent activity. 62 | /// 63 | public var parentId: ID { 64 | var parentId = ID() 65 | _ = os_activity_get_identifier(impl, &parentId) 66 | return parentId 67 | } 68 | 69 | private let impl: OS_os_activity 70 | 71 | /// Initializes a new activity. 72 | /// 73 | /// - Parameters: 74 | /// - description: Description of the activty. 75 | /// - parent: Parent activity of the newly created activity. 76 | /// - options: Options of the newly created activity. 77 | /// 78 | public init(_ description: StaticString, parent: Activity = .current, options: Options = [], dso: UnsafeRawPointer? = #dsohandle) { 79 | guard let dso = dso.map({ UnsafeMutableRawPointer(mutating: $0) }) else { fatalError("No DSO handle") } 80 | impl = description.withUTF8Buffer { ptr in 81 | ptr.withMemoryRebound(to: Int8.self) { cptr in 82 | _os_activity_create(dso, cptr.baseAddress!, parent.impl, os_activity_flag_t(rawValue: options.rawValue)) 83 | } 84 | } 85 | } 86 | 87 | /// Initializes a new activity and executes a block in its context. 88 | /// 89 | /// - Parameters: 90 | /// - description: Description of the activty. 91 | /// - parent: Parent activity of the newly created activity. 92 | /// - options: Options of the newly created activity. 93 | /// - block: Block to immediately execute in the activity's context. 94 | /// 95 | public init(_ description: StaticString, parent: Activity = .current, options: Options = [], dso: UnsafeRawPointer? = #dsohandle, block: @convention(block) () -> Void) { 96 | guard let dso = dso.map({ UnsafeMutableRawPointer(mutating: $0) }) else { fatalError("No DSO handle") } 97 | impl = description.withUTF8Buffer { ptr in 98 | ptr.withMemoryRebound(to: Int8.self) { cptr in 99 | _os_activity_create(dso, cptr.baseAddress!, parent.impl, os_activity_flag_t(rawValue: options.rawValue)) 100 | } 101 | } 102 | run(block: block) 103 | } 104 | 105 | /// Initializes a new activity and executes a block in its context. 106 | /// 107 | /// - Parameters: 108 | /// - description: Description of the activty. 109 | /// - parent: Parent activity of the newly created activity. 110 | /// - options: Options of the newly created activity. 111 | /// - block: Block to immediately execute in the activity's context. 112 | /// 113 | public init(_ description: StaticString, parent: Activity = .current, options: Options = [], dso: UnsafeRawPointer? = #dsohandle, block: () throws -> Void) rethrows { 114 | guard let dso = dso.map({ UnsafeMutableRawPointer(mutating: $0) }) else { fatalError("No DSO handle") } 115 | impl = description.withUTF8Buffer { ptr in 116 | ptr.withMemoryRebound(to: Int8.self) { cptr in 117 | _os_activity_create(dso, cptr.baseAddress!, parent.impl, os_activity_flag_t(rawValue: options.rawValue)) 118 | } 119 | } 120 | try run(block: block) 121 | } 122 | 123 | /// Wraps a previously created activity 124 | /// 125 | public init(_ impl: OS_os_activity) { 126 | self.impl = impl 127 | } 128 | 129 | /// Executes a block within the context of the activty. 130 | /// 131 | /// - Parameters: 132 | /// - block: The block to execute 133 | /// 134 | public func run(block: @convention(block) () -> Void) { 135 | os_activity_apply(impl, block) 136 | } 137 | 138 | /// Executes a block within the context of the activty, optionally returning a value 139 | /// or throwing errors. 140 | /// 141 | /// - Parameters: 142 | /// - block: The block to execute 143 | /// 144 | public func run(block: () throws -> R) rethrows -> R { 145 | var result: Result? 146 | 147 | os_activity_apply(impl) { 148 | do { 149 | result = .success(try block()) 150 | } 151 | catch { 152 | result = .failure(error) 153 | } 154 | } 155 | 156 | switch result! { 157 | case .success(let value): return value 158 | case .failure(let error): try { throw error }() 159 | } 160 | 161 | fatalError() 162 | } 163 | 164 | /// Manual scope manager that allows leaving a previously entered scope at 165 | /// a specific time. 166 | /// 167 | /// - Note: Manually managing a scope is not the preferred method; using 168 | /// `Activity.run(block:)` will execute a block in an automatically 169 | /// managed scope. 170 | /// 171 | public struct Scope { 172 | fileprivate var state = os_activity_scope_state_s() 173 | 174 | /// Leaves this scope for the owning activity. 175 | /// 176 | public mutating func leave() { 177 | os_activity_scope_leave(&state) 178 | } 179 | } 180 | 181 | /// Creates and automatically enters a scope for the this 182 | /// activity. 183 | /// 184 | /// When manual control of entering and leave an activity scope is rqeuired, 185 | /// `enter()` can be used to produce a scope and automatically enter it. 186 | /// The returned `Scope` can then be used to manually leave the scope 187 | /// when needed. 188 | /// 189 | /// - Note: Manually managing a scope is not the preferred method; using 190 | /// `run(block:)` will execute a block in an automatically managed scope. 191 | /// 192 | /// - Returns: A `Scope` instance controlling the scope created and entered. 193 | /// 194 | public func enter() -> Scope { 195 | var scope = Scope() 196 | os_activity_scope_enter(impl, &scope.state) 197 | return scope 198 | } 199 | 200 | /// Label an activity that is auto-generated by AppKit/UIKit with a name that is 201 | /// useful for debugging macro-level user actions. 202 | /// 203 | /// Label an activity that is auto-generated by AppKit/UIKit with a name that is 204 | /// useful for debugging macro-level user actions. The API should be called 205 | /// early within the scope of the IBAction and before any sub-activities are 206 | /// created. The name provided will be shown in tools in additon to the 207 | /// underlying AppKit/UIKit provided name. This API can only be called once and 208 | /// only on the activity created by AppKit/UIKit. These actions help determine 209 | /// workflow of the user in order to reproduce problems that occur. For example, 210 | /// a control press and/or menu item selection can be labeled: 211 | /// 212 | /// activity.labelUserAction("New mail message") 213 | /// activity.labelUserAction("Empty trash") 214 | /// 215 | /// Where the underlying AppKit/UIKit name will be "gesture:" or "menuSelect:". 216 | /// 217 | /// - Parameters: 218 | /// - description A constant string that describes the the action. 219 | /// 220 | public static func labelUserAction(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle) { 221 | guard let dso = dso.map({ UnsafeMutableRawPointer(mutating: $0) }) else { return } 222 | description.withUTF8Buffer { ptr in 223 | ptr.withMemoryRebound(to: Int8.self) { cptr in 224 | _os_activity_label_useraction(dso, cptr.baseAddress!) 225 | } 226 | } 227 | } 228 | 229 | /// Accesses the "unsafe" interface for activities. 230 | /// 231 | /// - Important: The unsafe interface is named as such, and 232 | /// hidden behind a property, to express its volatile nature 233 | /// and that its methods and properties may change, disappear 234 | /// or stop working at any time. 235 | /// 236 | /// It is highly suggested that this only be used for debugging 237 | /// purposes. 238 | /// 239 | public static let unsafe: ActivityUnsafe = _ActivityUnsafe() 240 | } 241 | 242 | public protocol ActivityUnsafe { 243 | /// Retrieves the current active ID hierarchy 244 | /// 245 | func getActiveIDs(max: Int) -> [Activity.ID] 246 | } 247 | 248 | private struct _ActivityUnsafe: ActivityUnsafe { 249 | @available(macOS, deprecated: 10.12) 250 | @available(iOS, deprecated: 10) 251 | @available(tvOS, deprecated: 10) 252 | @available(watchOS, deprecated: 3) 253 | func getActiveIDs(max: Int = 16) -> [Activity.ID] { 254 | var ids = [os_activity_id_t](repeating: 123456, count: max) 255 | var idCount = UInt32(ids.count) 256 | os_activity_get_active(&ids, &idCount) 257 | return Array(ids.prefix(Int(idCount))) 258 | } 259 | } 260 | 261 | private let _none = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_none"), to: OS_os_activity.self) 262 | private let _current = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_current"), to: OS_os_activity.self) 263 | -------------------------------------------------------------------------------- /Sources/Signposts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signposts.swift 3 | // OSLogTrace 4 | // 5 | // Copyright © 2019 Outfox, inc. 6 | // 7 | // 8 | // Distributed under the MIT License, See LICENSE for details. 9 | // 10 | 11 | import _SwiftOSOverlayShims 12 | import Foundation 13 | import os 14 | @_exported import os.log 15 | 16 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) 17 | public extension OSLog { 18 | /// Creates a signpost ID unique to the this log instance. 19 | /// 20 | /// - Returns: A signpost ID unique to this log instance. 21 | /// 22 | func signpostID() -> OSSignpostID { 23 | return OSSignpostID(log: self) 24 | } 25 | 26 | /// Creates a signpost ID unique to this log instance as well as the `object`. 27 | /// 28 | /// - Parameters: 29 | /// - object: An object to further make the signpost unique to. 30 | /// - Returns: A signpost ID unique to this log instance as well as the `object`. 31 | /// 32 | func signpostID(object: AnyObject) -> OSSignpostID { 33 | return OSSignpostID(log: self, object: object) 34 | } 35 | 36 | /// Creates a signpost manager using the provided ID. 37 | /// 38 | /// The signpost managed allows easy marking of signpost events using this log 39 | /// instance and the provided signpost ID. 40 | /// 41 | /// - Parameters: 42 | /// - id: Signpost ID to create a manager for. 43 | /// - Returns: A signpost manager bound to this log instance and the `id`. 44 | /// 45 | func signpost(id: OSSignpostID, dso: UnsafeRawPointer = #dsohandle) -> OSSignpost { 46 | return OSSignpost(log: self, id: id) 47 | } 48 | 49 | /// Creates a signpost manager using a generated unique signpost ID. 50 | /// 51 | /// The signpost managed allows easy marking of signpost events using this log 52 | /// instance and the provided signpost ID. 53 | /// 54 | /// - Parameters: 55 | /// - object: (optional) An object to further make the unique ID. See `OSLogTrace.signpostID(object:)` 56 | /// - Returns: A signpost manager bound to this log instance and the `id` 57 | /// 58 | func signpost(object: AnyObject? = nil, dso: UnsafeRawPointer = #dsohandle) -> OSSignpost { 59 | if let object = object { 60 | return OSSignpost(log: self, id: signpostID(object: object)) 61 | } 62 | return OSSignpost(log: self, id: signpostID()) 63 | } 64 | 65 | /// Marks a signpost with the provided type. 66 | /// 67 | /// - Parameters: 68 | /// - type: Signpost type (evemt, begin, end) 69 | /// - name: Name of signpost event 70 | /// - id: ID of the signpost 71 | /// 72 | func mark(_ type: OSSignpostType, name: String, id: OSSignpostID, dso: UnsafeRawPointer = #dsohandle) { 73 | _mark(type, in: self, id: id, name: name, dso: dso) 74 | } 75 | 76 | /// Marks a signpost with the provided type and a message. 77 | /// 78 | /// - Parameters: 79 | /// - type: Signpost type (evemt, begin, end) 80 | /// - name: Name of signpost event 81 | /// - id: ID of the signpost 82 | /// - message: Formatted log message 83 | /// 84 | func mark(_ type: OSSignpostType, name: String, id: OSSignpostID, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 85 | let message = message() 86 | _mark(type, in: self, id: id, name: name, 87 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 88 | } 89 | 90 | /// Marks a signpost `event`. 91 | /// 92 | /// - Parameters: 93 | /// - name: Name of signpost event 94 | /// - id: ID of the signpost 95 | /// 96 | func event(name: String, id: OSSignpostID, dso: UnsafeRawPointer = #dsohandle) { 97 | _mark(.event, in: self, id: id, name: name, dso: dso) 98 | } 99 | 100 | /// Marks a signpost `event` with a log message. 101 | /// 102 | /// - Parameters: 103 | /// - name: Name of signpost event 104 | /// - id: ID of the signpost 105 | /// - message: Formatted log message 106 | /// 107 | func event(name: String, id: OSSignpostID, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 108 | let message = message() 109 | _mark(.event, in: self, id: id, name: name, 110 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 111 | } 112 | 113 | /// Marks a signpost `begin`. 114 | /// 115 | /// - Parameters: 116 | /// - name: Name of signpost event 117 | /// - id: ID of the signpost 118 | /// 119 | func begin(name: String, id: OSSignpostID, dso: UnsafeRawPointer = #dsohandle) { 120 | _mark(.begin, in: self, id: id, name: name, dso: dso) 121 | } 122 | 123 | /// Marks a signpost `begin` with a log message. 124 | /// 125 | /// - Parameters: 126 | /// - name: Name of signpost event 127 | /// - id: ID of the signpost 128 | /// - message: Formatted log message 129 | /// 130 | func begin(name: String, id: OSSignpostID, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 131 | let message = message() 132 | _mark(.begin, in: self, id: id, name: name, 133 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 134 | } 135 | 136 | /// Marks a signpost `end`. 137 | /// 138 | /// - Parameters: 139 | /// - name: Name of signpost event 140 | /// - id: ID of the signpost 141 | /// 142 | func end(name: String, id: OSSignpostID, dso: UnsafeRawPointer = #dsohandle) { 143 | _mark(.end, in: self, id: id, name: name, dso: dso) 144 | } 145 | 146 | /// Marks a signpost `end` with a log message. 147 | /// 148 | /// - Parameters: 149 | /// - name: Name of signpost event 150 | /// - id: ID of the signpost 151 | /// - message: Formatted log message 152 | /// 153 | func end(name: String, id: OSSignpostID, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 154 | let message = message() 155 | _mark(.end, in: self, id: id, name: name, 156 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 157 | } 158 | } 159 | 160 | /// Signpost manager for convenient marking of signposts to a specific log and with a specific ID. 161 | /// 162 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) 163 | public struct OSSignpost { 164 | public let log: OSLog 165 | public let id: OSSignpostID 166 | 167 | public init(log: OSLog, id: OSSignpostID) { 168 | self.log = log 169 | self.id = id 170 | } 171 | 172 | /// Marks a signpost event of the provided type. 173 | /// 174 | /// - Parameters: 175 | /// - type: Signpost type (evemt, begin, end) 176 | /// - name: Name of signpost event 177 | /// 178 | public func mark(_ type: OSSignpostType, name: String, dso: UnsafeRawPointer = #dsohandle) { 179 | _mark(type, in: log, id: id, name: name, dso: dso) 180 | } 181 | 182 | /// Marks a signpost event of the provided type with a log message. 183 | /// 184 | /// - Parameters: 185 | /// - type: Signpost type (evemt, begin, end) 186 | /// - name: Name of signpost event 187 | /// - message: Formatted log message 188 | /// 189 | public func mark(_ type: OSSignpostType, name: String, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 190 | let message = message() 191 | _mark(type, in: log, id: id, name: name, 192 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 193 | } 194 | 195 | /// Marks a signpost `event` with a log message. 196 | /// 197 | /// - Parameters: 198 | /// - name: Name of signpost event 199 | /// 200 | public func event(name: String, dso: UnsafeRawPointer = #dsohandle) { 201 | _mark(.event, in: log, id: id, name: name, dso: dso) 202 | } 203 | 204 | /// Marks a signpost `event`. 205 | /// 206 | /// - Parameters: 207 | /// - name: Name of signpost event 208 | /// - message: Formatted log message 209 | /// 210 | public func event(name: String, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 211 | let message = message() 212 | _mark(.event, in: log, id: id, name: name, 213 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 214 | } 215 | 216 | /// Marks a signpost `begin`. 217 | /// 218 | /// - Parameters: 219 | /// - name: Name of signpost event 220 | /// 221 | public func begin(name: String, dso: UnsafeRawPointer = #dsohandle) { 222 | _mark(.begin, in: log, id: id, name: name, dso: dso) 223 | } 224 | 225 | /// Marks a signpost `begin` with a log message. 226 | /// 227 | /// - Parameters: 228 | /// - name: Name of signpost event 229 | /// - message: Formatted log message 230 | /// 231 | public func begin(name: String, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 232 | let message = message() 233 | _mark(.begin, in: log, id: id, name: name, 234 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 235 | } 236 | 237 | /// Marks a signpost `end`. 238 | /// 239 | /// - Parameters: 240 | /// - name: Name of signpost event 241 | /// 242 | public func end(name: String, dso: UnsafeRawPointer = #dsohandle) { 243 | _mark(.end, in: log, id: id, name: name, dso: dso) 244 | } 245 | 246 | /// Marks a signpost `end` with a log message. 247 | /// 248 | /// - Parameters: 249 | /// - name: Name of signpost event 250 | /// - message: Formatted log message 251 | /// 252 | public func end(name: String, message: @autoclosure () -> LogMessage, dso: UnsafeRawPointer = #dsohandle) { 253 | let message = message() 254 | _mark(.end, in: log, id: id, name: name, 255 | format: message.interpolation.format, formatArgs: message.interpolation.arguments, dso: dso) 256 | } 257 | } 258 | 259 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) 260 | private func _mark(_ type: OSSignpostType, in log: OSLog, id: OSSignpostID, name: String, dso: UnsafeRawPointer) { 261 | let ra = _swift_os_log_return_address() 262 | name.withCString { namePtr in 263 | _swift_os_signpost(dso, ra, log, type, namePtr, id.rawValue) 264 | } 265 | } 266 | 267 | @available(macOS 10.14, iOS 12, tvOS 12, watchOS 5, *) 268 | private func _mark(_ type: OSSignpostType, in log: OSLog, id: OSSignpostID, name: String, format: String, formatArgs: [CVarArg], dso: UnsafeRawPointer) { 269 | let ra = _swift_os_log_return_address() 270 | name.withCString { namePtr in 271 | format.withCString { formatPtr in 272 | withVaList(formatArgs) { formatArgs in 273 | _swift_os_signpost_with_format(dso, ra, log, type, namePtr, id.rawValue, formatPtr, formatArgs) 274 | } 275 | } 276 | } 277 | } 278 | --------------------------------------------------------------------------------