├── .gitignore ├── Package.swift ├── Tests └── LoggingTests │ ├── Test+Double.swift │ ├── Support.swift │ ├── Test+Integer.swift │ ├── Test+Floats.swift │ ├── Test+Private.swift │ └── Test+Public.swift ├── LICENSE.md ├── .swiftpm └── xcode │ └── xcshareddata │ └── xcschemes │ └── Logging.xcscheme ├── README.md └── Sources └── Logging ├── Message.swift ├── MetadataProvider.swift ├── FormattedLogHandler.swift ├── Interpolation.swift ├── Locks.swift ├── LogHandler.swift └── Logging.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Logging", 6 | platforms: [ 7 | .iOS(.v13), 8 | .tvOS(.v13), 9 | .macOS(.v10_15), 10 | .watchOS(.v6), 11 | ], 12 | products: [ 13 | .library( 14 | name: "Logging", 15 | targets: ["Logging"] 16 | ), 17 | ], 18 | targets: [ 19 | .target(name: "Logging"), 20 | .testTarget( 21 | name: "LoggingTests", 22 | dependencies: ["Logging"] 23 | ), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Tests/LoggingTests/Test+Double.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Logging 3 | 4 | final class DoubleTests: XCTestCase { 5 | 6 | func testDouble() { 7 | let min: Double = .leastNonzeroMagnitude 8 | let max: Double = .greatestFiniteMagnitude 9 | let value: Double = 3.14159265359 10 | let logging = TestLogging() 11 | LoggingSystem.bootstrapInternal(logging.make) 12 | 13 | var logger = Logger(label: "test") 14 | logger.logLevel = .trace 15 | 16 | logger.debug("\(min, privacy: .public)") 17 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", Double.leastNonzeroMagnitude)) 18 | 19 | logger.debug("\(max, privacy: .public)") 20 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", Double.greatestFiniteMagnitude)) 21 | 22 | logger.debug("\(value, format: .fixed(precision: 2), privacy: .public)") 23 | XCTAssertEqual(logging.recorder.message, "3.14") 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Shaps Benkau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Tests/LoggingTests/Support.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import Logging 3 | 4 | struct Mock: CustomStringConvertible { 5 | var description: String { "mock" } 6 | } 7 | 8 | struct TestLogHandler: LogHandler { 9 | var metadata = Logger.Metadata() 10 | var logLevel: Logger.Level = .trace 11 | var label: String 12 | 13 | private let recorder: Recorder 14 | 15 | subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 16 | get { metadata[metadataKey] } 17 | set { metadata[metadataKey] = newValue } 18 | } 19 | 20 | init(label: String, recorder: Recorder) { 21 | self.label = label 22 | self.recorder = recorder 23 | } 24 | 25 | func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt) { 26 | recorder.record(message: message) 27 | } 28 | } 29 | 30 | struct TestLogging { 31 | let recorder = Recorder() 32 | 33 | func make(label: String) -> LogHandler { 34 | TestLogHandler(label: label, recorder: recorder) 35 | } 36 | } 37 | 38 | final class Recorder { 39 | var message: String = "" 40 | 41 | func record(message: Logger.Message) { 42 | self.message = message.description 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/LoggingTests/Test+Integer.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Logging 3 | 4 | final class IntegerTests: XCTestCase { 5 | 6 | func testUnsigned() { 7 | let min: UInt64 = .min 8 | let max: UInt64 = .max 9 | let value: UInt64 = 100 10 | let logging = TestLogging() 11 | LoggingSystem.bootstrapInternal(logging.make) 12 | 13 | var logger = Logger(label: "test") 14 | logger.logLevel = .trace 15 | 16 | logger.debug("\(min, privacy: .public)") 17 | XCTAssertEqual(logging.recorder.message, "\(min)") 18 | 19 | logger.debug("\(max, privacy: .public)") 20 | XCTAssertEqual(logging.recorder.message, "\(max)") 21 | 22 | logger.debug("\(value, format: .decimal(minDigits: 6), privacy: .public)") 23 | XCTAssertEqual(logging.recorder.message, "000100") 24 | } 25 | 26 | func testSigned() { 27 | let min: Int64 = .min 28 | let max: Int64 = .max 29 | let value: Int64 = -100 30 | let logging = TestLogging() 31 | LoggingSystem.bootstrapInternal(logging.make) 32 | 33 | var logger = Logger(label: "test") 34 | logger.logLevel = .trace 35 | 36 | logger.debug("\(min, privacy: .public)") 37 | XCTAssertEqual(logging.recorder.message, "\(min)") 38 | 39 | logger.debug("\(max, privacy: .public)") 40 | XCTAssertEqual(logging.recorder.message, "\(max)") 41 | 42 | logger.debug("\(value, format: .decimal(minDigits: 6), privacy: .public)") 43 | XCTAssertEqual(logging.recorder.message, "-00100") 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Tests/LoggingTests/Test+Floats.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Logging 3 | 4 | final class FloatTests: XCTestCase { 5 | 6 | #if canImport(CoreGraphics) 7 | func testCGFloats() { 8 | let min: CGFloat = .leastNonzeroMagnitude 9 | let max: CGFloat = .greatestFiniteMagnitude 10 | let value: CGFloat = 3.14159265359 11 | let logging = TestLogging() 12 | LoggingSystem.bootstrapInternal(logging.make) 13 | 14 | var logger = Logger(label: "test") 15 | logger.logLevel = .trace 16 | 17 | logger.debug("\(min, privacy: .public)") 18 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", CGFloat.leastNonzeroMagnitude)) 19 | 20 | logger.debug("\(max, privacy: .public)") 21 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", CGFloat.greatestFiniteMagnitude)) 22 | 23 | logger.debug("\(value, format: .fixed(precision: 2), privacy: .public)") 24 | XCTAssertEqual(logging.recorder.message, "3.14") 25 | } 26 | #endif 27 | 28 | func testFloats() { 29 | let min: Float = .leastNonzeroMagnitude 30 | let max: Float = .greatestFiniteMagnitude 31 | let value: Float = 3.14159265359 32 | let logging = TestLogging() 33 | LoggingSystem.bootstrapInternal(logging.make) 34 | 35 | var logger = Logger(label: "test") 36 | logger.logLevel = .trace 37 | 38 | logger.debug("\(min, privacy: .public)") 39 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", Float.leastNonzeroMagnitude)) 40 | 41 | logger.debug("\(max, privacy: .public)") 42 | XCTAssertEqual(logging.recorder.message, String(format: "%.06f", Float.greatestFiniteMagnitude)) 43 | 44 | logger.debug("\(value, format: .fixed(precision: 2), privacy: .public)") 45 | XCTAssertEqual(logging.recorder.message, "3.14") 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Tests/LoggingTests/Test+Private.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Logging 3 | 4 | final class PrivateTests: XCTestCase { 5 | 6 | func testPrivate() { 7 | let signed = -3.14159265359 8 | let unsigned = 3.14 9 | #if DEBUG 10 | LogPrivacy.disableRedaction = false 11 | #endif 12 | let logging = TestLogging() 13 | LoggingSystem.bootstrapInternal(logging.make) 14 | 15 | var logger = Logger(label: "test") 16 | logger.logLevel = .trace 17 | 18 | logger.debug("\("name")") 19 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 20 | 21 | logger.debug("\(signed, privacy: .private)") 22 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 23 | logger.debug("\(signed, format: .fixed(precision: 0))") 24 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 25 | logger.debug("\(signed, format: .fixed(explicitPositiveSign: true))") 26 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 27 | logger.debug("\(signed, format: .fixed(precision: 0, explicitPositiveSign: true))") 28 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 29 | 30 | logger.debug("\(signed)") 31 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 32 | logger.debug("\(signed, format: .fixed)") 33 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 34 | logger.debug("\(signed, format: .fixed(precision: 1))") 35 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 36 | logger.debug("\(signed, format: .fixed(explicitPositiveSign: true))") 37 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 38 | logger.debug("\(signed, format: .fixed(precision: 1, explicitPositiveSign: true))") 39 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 40 | 41 | logger.debug("\(true)") 42 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 43 | logger.debug("\(true, format: .answer)") 44 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 45 | logger.debug("\(true, format: .truth)") 46 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 47 | 48 | logger.debug("\(Mock())") 49 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 50 | logger.debug("\(Mock.self)") 51 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 52 | logger.debug("\(NSObject())") 53 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 54 | 55 | logger.debug("\(UInt(unsigned))") 56 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 57 | logger.debug("\(UInt(unsigned), format: .decimal(minDigits: 0))") 58 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 59 | logger.debug("\(UInt(unsigned), format: .decimal(explicitPositiveSign: true))") 60 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 61 | logger.debug("\(UInt(unsigned), format: .decimal(explicitPositiveSign: true, minDigits: 0))") 62 | XCTAssertEqual(logging.recorder.message, LogPrivacy.redacted) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Tests/LoggingTests/Test+Public.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Logging 3 | 4 | final class PublicTests: XCTestCase { 5 | 6 | func testPublic() { 7 | let signed = -3.14159265359 8 | let unsigned = 3.14159265359 9 | let logging = TestLogging() 10 | LoggingSystem.bootstrapInternal(logging.make) 11 | 12 | var logger = Logger(label: "test") 13 | logger.logLevel = .trace 14 | 15 | logger.debug("\("name", privacy: .public)") 16 | XCTAssertEqual(logging.recorder.message, "name") 17 | 18 | logger.debug("\(signed, privacy: .public)") 19 | XCTAssertEqual(logging.recorder.message, "\(String(format: "%.6f", signed))") 20 | logger.debug("\(signed, format: .fixed(precision: 0), privacy: .public)") 21 | XCTAssertEqual(logging.recorder.message, "-3") 22 | logger.debug("\(unsigned, format: .fixed(explicitPositiveSign: true), privacy: .public)") 23 | XCTAssertEqual(logging.recorder.message, "+\(String(format: "%.6f", unsigned))") 24 | logger.debug("\(unsigned, format: .fixed(precision: 11, explicitPositiveSign: true), privacy: .public)") 25 | XCTAssertEqual(logging.recorder.message, "+\(unsigned)") 26 | 27 | logger.debug("\(signed, format: .fixed, privacy: .public)") 28 | XCTAssertEqual(logging.recorder.message, "\(String(format: "%.6f", signed))") 29 | logger.debug("\(signed, format: .fixed(precision: 1), privacy: .public)") 30 | XCTAssertEqual(logging.recorder.message, "\(String(format: "%.1f", signed))") 31 | logger.debug("\(unsigned, format: .fixed(explicitPositiveSign: true), privacy: .public)") 32 | XCTAssertEqual(logging.recorder.message, "+\(String(format: "%.6f", unsigned))") 33 | logger.debug("\(unsigned, format: .fixed(precision: 1, explicitPositiveSign: true), privacy: .public)") 34 | XCTAssertEqual(logging.recorder.message, "+\(String(format: "%.1f", unsigned))") 35 | 36 | logger.debug("\(true, privacy: .public)") 37 | XCTAssertEqual(logging.recorder.message, "true") 38 | logger.debug("\(true, format: .answer, privacy: .public)") 39 | XCTAssertEqual(logging.recorder.message, "yes") 40 | logger.debug("\(true, format: .truth, privacy: .public)") 41 | XCTAssertEqual(logging.recorder.message, "true") 42 | 43 | logger.debug("\(Mock(), privacy: .public)") 44 | XCTAssertEqual(logging.recorder.message, "mock") 45 | logger.debug("\(Mock.self, privacy: .public)") 46 | XCTAssertEqual(logging.recorder.message, String(describing: Mock.self)) 47 | 48 | logger.debug("\(UInt(unsigned), privacy: .public)") 49 | XCTAssertEqual(logging.recorder.message, "\(UInt(unsigned))") 50 | logger.debug("\(UInt(unsigned), format: .decimal(minDigits: 2), privacy: .public)") 51 | XCTAssertEqual(logging.recorder.message, String(format: "%02lu", UInt(unsigned))) 52 | logger.debug("\(UInt(unsigned), format: .decimal(explicitPositiveSign: true), privacy: .public)") 53 | XCTAssertEqual(logging.recorder.message, "+\(String(format: "%lu", UInt(unsigned)))") 54 | logger.debug("\(UInt(unsigned), format: .decimal(explicitPositiveSign: true, minDigits: 0), privacy: .public)") 55 | XCTAssertEqual(logging.recorder.message, "+\(String(format: "%0lu", UInt(unsigned)))") 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/Logging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 36 | 42 | 43 | 44 | 45 | 46 | 63 | 64 | 70 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ios](https://img.shields.io/badge/iOS-13-green) 2 | ![tv](https://img.shields.io/badge/tvOS-13-green) 3 | ![watch](https://img.shields.io/badge/watchOS-6-green) 4 | ![mac](https://img.shields.io/badge/macOS-10.15-green) 5 | 6 | # Logging 7 | 8 | A version of Apple's [SwiftLog](https://github.com/apple/swift-log) that adds some improved formatting for app development and includes OSLog-ish string interpolation. 9 | 10 | __Feature List__ 11 | 12 | - [x] ~~Privacy (public, private and masked)~~ 13 | - [x] ~~Log levels~~ 14 | - [x] ~~FormattedLogHandler~~ 15 | - [x] ~~Literal string~~ 16 | - [x] ~~Numerical formats~~ 17 | - [x] ~~Boolean formats~~ 18 | - [ ] Exponential formats 19 | - [ ] Hexadecimal formats 20 | - [ ] Column formatting 21 | 22 | > I've also added many tests to ensure the string interpolation and privacy features are working as expected. However, I have not included any of SwiftLog's tests since the library is simply a _direct copy_ (see below). 23 | 24 | ## Examples 25 | 26 | ```swift 27 | // Somewhere in your code, define your log handler(s) 28 | LoggingSystem.bootstrap { label in 29 | FormattedLogHandler(label: label) 30 | } 31 | 32 | // Then in a framework or subsystem, define a logger 33 | let logger = Logger(label: "com.benkau.logging") 34 | logger.debug("Hello, world!") 35 | ``` 36 | 37 | Similar to OSLog, all interpolated values default to a `private` scope (in a non-`DEBUG`) environment, with their values redacted. 38 | 39 | ```swift 40 | logger.debug("Logged in as \(user, privacy: .private)") 41 | // Logged in as 42 | 43 | logger.debug("Fetching data from \(url, privacy: .private(.hash))") 44 | // Fetching data from 210237823 45 | ``` 46 | 47 | There are conveniences for logging numerical values: 48 | 49 | ```swift 50 | logger.debug("Pi is \(3.14159265359, format: .fixed(precision: 2))") 51 | // Pi is 3.14 52 | 53 | logger.debug("Current score: \(score, format: .decimal(minDigits: 2)") 54 | // Current score: 03 55 | ``` 56 | 57 | ## Formatting 58 | 59 | The library includes a custom `FormattedLogHandler` that you're free to use or create your own. The provided handler uses SFSymbols to improve readability and clarity in the console, as well as providing a simple closure based formatter so you can make it your own. 60 | 61 | ```swift 62 | FormattedLogHandler { options in 63 | // simply return your formatted log 64 | FormattedLogHandler(label: label) { data in 65 | "\(data.timestamp()) [\(data.label)] \(data.level.symbol) \(data.message)" 66 | } 67 | } 68 | ``` 69 | 70 | > Obviously you can always use your own `LogHandler` as you can directly with SwiftLog. 71 | 72 | ## SwiftLog 73 | 74 | The actual logging framework is a _direct copy_ of Apple's own SwiftLog. The reason I didn't include it as a dependency is because there was no way for me to _extend_ it to include string interpolation that I could see. If anyone has ideas on how to make this work, I would love to hear about them. Please open an Issue so we can discuss it as this would allow me to properly attribute and pin to a dependency version etc... 75 | 76 | ## Installation 77 | 78 | The code is packaged as a framework. You can install manually (by copying the files in the `Sources` directory) or using Swift Package Manager (**preferred**) 79 | 80 | To install using Swift Package Manager, add this to the `dependencies` section of your `Package.swift` file: 81 | 82 | `.package(url: "https://github.com/shaps80/Logging.git", .upToNextMinor(from: "1.0.0"))` 83 | -------------------------------------------------------------------------------- /Sources/Logging/Message.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Logger { 4 | public struct Message: ExpressibleByStringInterpolation, ExpressibleByStringLiteral, CustomStringConvertible, Sendable { 5 | public var interpolation: LogInterpolation 6 | 7 | public init(stringLiteral value: String) { 8 | interpolation = LogInterpolation() 9 | interpolation.appendLiteral(value) 10 | } 11 | 12 | public init(stringInterpolation: LogInterpolation) { 13 | interpolation = stringInterpolation 14 | } 15 | 16 | public var description: String { 17 | var message = "" 18 | 19 | for value in interpolation.storage { 20 | switch value { 21 | case let .literal(value): 22 | message.append(value) 23 | case let .bool(value, format, privacy): 24 | switch format { 25 | case .answer: 26 | message.append(privacy.value(for: value() ? "yes" : "no")) 27 | case .truth: 28 | message.append(privacy.value(for: value() ? "true" : "false")) 29 | } 30 | case let .convertible(value, _, privacy): 31 | message.append(privacy.value(for: value().description)) 32 | case let .double(value, format, _, privacy): 33 | switch format.format { 34 | case let .fixed(precision, explicitPositiveSign): 35 | let value = value() 36 | let string = String(format: "\(explicitPositiveSign ? "+" : "")%.0\(precision())f", value) 37 | message.append(privacy.value(for: string)) 38 | } 39 | case let .float(value, format, _, privacy): 40 | switch format.format { 41 | case let .fixed(precision, explicitPositiveSign): 42 | let value = value() 43 | let string = String(format: "\(explicitPositiveSign ? "+" : "")%.0\(precision())f", value) 44 | message.append(privacy.value(for: string)) 45 | } 46 | #if canImport(CoreGraphics) 47 | case let .cgfloat(value, format, _, privacy): 48 | switch format.format { 49 | case let .fixed(precision, explicitPositiveSign): 50 | let value = value() 51 | let string = String(format: "\(explicitPositiveSign ? "+" : "")%.0\(precision())f", value) 52 | message.append(privacy.value(for: string)) 53 | } 54 | #endif 55 | case let .signedInt(value, format, _, privacy): 56 | switch format.format { 57 | case let .decimal(minDigits, explicitPositiveSign): 58 | let value = value() 59 | let string = String(format: "\(explicitPositiveSign ? "+" : "")%0\(minDigits())ld", value) 60 | message.append(privacy.value(for: string)) 61 | } 62 | case let .unsignedInt(value, format, _, privacy): 63 | switch format.format { 64 | case let .decimal(minDigits, explicitPositiveSign): 65 | let value = String(format: "\(explicitPositiveSign ? "+" : "")%0\(minDigits())lu", value()) 66 | message.append(privacy.value(for: value)) 67 | } 68 | case let .meta(value, _, privacy): 69 | message.append(privacy.value(for: String(describing: value()))) 70 | case let .object(value, privacy): 71 | message.append(privacy.value(for: value())) 72 | case let .string(value, _, privacy): 73 | message.append(privacy.value(for: value())) 74 | } 75 | } 76 | 77 | return message 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Logging/MetadataProvider.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Logging API open source project 4 | // 5 | // Copyright (c) 2018-2022 Apple Inc. and the Swift Logging API project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Logging API project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #if canImport(Darwin) 16 | import Darwin 17 | #elseif os(Windows) 18 | import CRT 19 | #elseif canImport(Glibc) 20 | import Glibc 21 | #elseif canImport(Musl) 22 | import Musl 23 | #elseif canImport(WASILibc) 24 | import WASILibc 25 | #else 26 | #error("Unsupported runtime") 27 | #endif 28 | 29 | #if compiler(>=5.6) 30 | @preconcurrency protocol _SwiftLogSendable: Sendable {} 31 | #else 32 | protocol _SwiftLogSendable {} 33 | #endif 34 | 35 | extension Logger { 36 | /// A `MetadataProvider` is used to automatically inject runtime-generated metadata 37 | /// to all logs emitted by a logger. 38 | /// 39 | /// ### Example 40 | /// A metadata provider may be used to automatically inject metadata such as 41 | /// trace IDs: 42 | /// 43 | /// ```swift 44 | /// import Tracing // https://github.com/apple/swift-distributed-tracing 45 | /// 46 | /// let metadataProvider = MetadataProvider { 47 | /// guard let traceID = Baggage.current?.traceID else { return nil } 48 | /// return ["traceID": "\(traceID)"] 49 | /// } 50 | /// let logger = Logger(label: "example", metadataProvider: metadataProvider) 51 | /// var baggage = Baggage.topLevel 52 | /// baggage.traceID = 42 53 | /// Baggage.withValue(baggage) { 54 | /// logger.info("hello") // automatically includes ["traceID": "42"] metadata 55 | /// } 56 | /// ``` 57 | /// 58 | /// We recommend referring to [swift-distributed-tracing](https://github.com/apple/swift-distributed-tracing) 59 | /// for metadata providers which make use of its tracing and metadata propagation infrastructure. It is however 60 | /// possible to make use of metadata providers independently of tracing and instruments provided by that library, 61 | /// if necessary. 62 | public struct MetadataProvider: _SwiftLogSendable { 63 | /// Provide ``Logger.Metadata`` from current context. 64 | #if swift(>=5.5) && canImport(_Concurrency) // we could instead typealias the function type, but it was requested that we avoid this for better developer experience 65 | @usableFromInline 66 | internal let _provideMetadata: @Sendable() -> Metadata 67 | #else 68 | @usableFromInline 69 | internal let _provideMetadata: () -> Metadata 70 | #endif 71 | 72 | /// Create a new `MetadataProvider`. 73 | /// 74 | /// - Parameter provideMetadata: A closure extracting metadata from the current execution context. 75 | #if swift(>=5.5) && canImport(_Concurrency) 76 | public init(_ provideMetadata: @escaping @Sendable() -> Metadata) { 77 | self._provideMetadata = provideMetadata 78 | } 79 | 80 | #else 81 | public init(_ provideMetadata: @escaping () -> Metadata) { 82 | self._provideMetadata = provideMetadata 83 | } 84 | #endif 85 | 86 | /// Invoke the metadata provider and return the generated contextual ``Logger/Metadata``. 87 | public func get() -> Metadata { 88 | return self._provideMetadata() 89 | } 90 | } 91 | } 92 | 93 | extension Logger.MetadataProvider { 94 | /// A pseudo-`MetadataProvider` that can be used to merge metadata from multiple other `MetadataProvider`s. 95 | /// 96 | /// ### Merging conflicting keys 97 | /// 98 | /// `MetadataProvider`s are invoked left to right in the order specified in the `providers` argument. 99 | /// In case multiple providers try to add a value for the same key, the last provider "wins" and its value is being used. 100 | /// 101 | /// - Parameter providers: An array of `MetadataProvider`s to delegate to. The array must not be empty. 102 | /// - Returns: A pseudo-`MetadataProvider` merging metadata from the given `MetadataProvider`s. 103 | public static func multiplex(_ providers: [Logger.MetadataProvider]) -> Logger.MetadataProvider? { 104 | assert(!providers.isEmpty, "providers MUST NOT be empty") 105 | return Logger.MetadataProvider { 106 | providers.reduce(into: [:]) { metadata, provider in 107 | let providedMetadata = provider.get() 108 | guard !providedMetadata.isEmpty else { 109 | return 110 | } 111 | metadata.merge(providedMetadata, uniquingKeysWith: { _, rhs in rhs }) 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/Logging/FormattedLogHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A formatted log handler that allows user-configuration 4 | public struct FormattedLogHandler: LogHandler { 5 | 6 | /// All associated data for a specific log event 7 | public struct Data { 8 | /// The log level for this event 9 | public var level: Logger.Level 10 | /// The label associated with this event 11 | public var label: String 12 | /// The message for this event, use `message.description` to get a string interpretation 13 | public var message: Logger.Message 14 | /// The metadata associated with this event 15 | public var metadata: Logger.Metadata 16 | /// The source of this event 17 | public var source: String 18 | /// The file where this event was logged 19 | public var file: String 20 | /// The function where this event was logged 21 | public var function: String 22 | /// The line of code where this event was logged 23 | public var line: UInt 24 | 25 | /// Returns an timestamp using the specified format, defaults to `%Y-%m-%d %H:%M:%S` 26 | /// - Parameter format: The format to return, defaults to `%Y-%m-%d %H:%M:%S` (Example: 2021-10-10 11:22:33) 27 | public func timestamp(format: String = "%Y-%m-%d %H:%M:%S") -> String { 28 | var buffer = [Int8](repeating: 0, count: 255) 29 | var timestamp = time(nil) 30 | #if canImport(Darwin) 31 | let localTime = localtime(×tamp) 32 | strftime(&buffer, buffer.count, format, localTime) 33 | #else 34 | var localTime = tm() 35 | localtime_s(&localTime, ×tamp) 36 | strftime(&buffer, buffer.count, format, &localTime) 37 | #endif 38 | return buffer.withUnsafeBufferPointer { 39 | $0.withMemoryRebound(to: CChar.self) { 40 | String(cString: $0.baseAddress!) 41 | } 42 | } 43 | } 44 | } 45 | 46 | public typealias Formatter = (Data) -> String 47 | 48 | public static let defaultFormat: Formatter = { 49 | var message = $0.message.description 50 | if !$0.metadata.isEmpty { 51 | message += " | " + $0.metadata.formatted 52 | } 53 | 54 | #if DEBUG 55 | return "\($0.timestamp(format: "%H:%M:%S")) \($0.level.symbol) [\($0.label)] \(message)" 56 | #else 57 | let filename = URL(fileURLWithPath: $0.file) 58 | .deletingPathExtension() 59 | .lastPathComponent 60 | 61 | return "\($0.timestamp()) \($0.level.symbol) [\($0.label)] \(message) | \(filename), \($0.line)" 62 | #endif 63 | } 64 | 65 | private let label: String 66 | private let formatter: Formatter 67 | public var logLevel: Logger.Level = .info 68 | public var metadata = Logger.Metadata() 69 | 70 | public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 71 | get { metadata[metadataKey] } 72 | set { metadata[metadataKey] = newValue } 73 | } 74 | 75 | /// A log handler with the specified label and minimum log level 76 | /// - Parameters: 77 | /// - label: The associated label for this logger 78 | /// - logLevel: The minimum log level to include 79 | public init(label: String, logLevel: Logger.Level = .trace) { 80 | self.init(label: label, logLevel: logLevel, formatter: Self.defaultFormat) 81 | } 82 | 83 | /// A log handler with the specified label, minimum log level and associated formatter 84 | /// - Parameters: 85 | /// - label: The associated label for this logger 86 | /// - logLevel: The minimum log level to include 87 | /// - formatter: The formatter to use for formatting log data 88 | public init(label: String, logLevel: Logger.Level = .trace, formatter: @escaping Formatter) { 89 | self.label = label 90 | self.formatter = formatter 91 | self.logLevel = logLevel 92 | } 93 | 94 | public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt) { 95 | var meta = self.metadata 96 | 97 | if let additional = metadata { 98 | meta.merge(additional) { $1 } 99 | } 100 | 101 | let options = Data( 102 | level: level, 103 | label: label, 104 | message: message, 105 | metadata: meta, 106 | source: source, 107 | file: file, 108 | function: function, 109 | line: line 110 | ) 111 | 112 | print(formatter(options)) 113 | } 114 | 115 | } 116 | 117 | private extension Logger.Metadata { 118 | var formatted: String { 119 | lazy 120 | .map { "\($0)=\($1)" } 121 | .joined(separator: " ") 122 | } 123 | } 124 | 125 | public extension Logger.Level { 126 | var symbol: String { 127 | switch self { 128 | case .trace: return "􀁼" 129 | case .debug: return "􀍷" 130 | case .info: return "􀅴" 131 | case .notice: return "􀒴" 132 | case .warning: return "􀞟" 133 | case .error: return "􀁠" 134 | case .critical: return "􀋧" 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Logging/Interpolation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if canImport(CoreGraphics) 4 | import CoreGraphics 5 | #endif 6 | 7 | @frozen public struct LogInterpolation: StringInterpolationProtocol, Sendable { 8 | 9 | @usableFromInline enum Value: Sendable { 10 | case literal(String) 11 | case string(@Sendable () -> String, alignment: LogStringAlignment, privacy: LogPrivacy) 12 | case convertible(@Sendable () -> CustomStringConvertible, alignment: LogStringAlignment, privacy: LogPrivacy) 13 | case signedInt(@Sendable () -> Int64, format: LogIntegerFormatting, alignment: LogStringAlignment, privacy: LogPrivacy) 14 | case unsignedInt(@Sendable () -> UInt64, format: LogIntegerFormatting, alignment: LogStringAlignment, privacy: LogPrivacy) 15 | case float(@Sendable () -> Float, format: LogFloatFormatting, alignment: LogStringAlignment, privacy: LogPrivacy) 16 | #if canImport(CoreGraphics) 17 | case cgfloat(@Sendable () -> CGFloat, format: LogFloatFormatting, alignment: LogStringAlignment, privacy: LogPrivacy) 18 | #endif 19 | case double(@Sendable () -> Double, format: LogFloatFormatting, alignment: LogStringAlignment, privacy: LogPrivacy) 20 | case bool(@Sendable () -> Bool, format: LogBoolFormat, privacy: LogPrivacy) 21 | case object(@Sendable () -> NSObject, privacy: LogPrivacy) 22 | case meta(@Sendable () -> Any.Type, alignment: LogStringAlignment, privacy: LogPrivacy) 23 | } 24 | 25 | private(set) var storage: [Value] = [] 26 | public init() { } 27 | public init(literalCapacity: Int, interpolationCount: Int) { } 28 | 29 | /// Appends a literal segment to the interpolation. 30 | public mutating func appendLiteral(_ literal: String) { 31 | storage.append(.literal(literal)) 32 | } 33 | } 34 | 35 | extension LogInterpolation { 36 | /// Defines interpolation for expressions of type String. 37 | public mutating func appendInterpolation(_ argumentString: @Sendable @autoclosure @escaping () -> String, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 38 | storage.append(.string(argumentString, alignment: align, privacy: privacy)) 39 | } 40 | 41 | /// Defines interpolation for values conforming to CustomStringConvertible. The values 42 | /// are displayed using the description methods on them. 43 | public mutating func appendInterpolation(_ value: @Sendable @autoclosure @escaping () -> T, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) where T: CustomStringConvertible { 44 | storage.append(.convertible(value, alignment: align, privacy: privacy)) 45 | } 46 | } 47 | 48 | extension LogInterpolation { 49 | /// Defines interpolation for meta-types. 50 | public mutating func appendInterpolation(_ value: @Sendable @autoclosure @escaping () -> Any.Type, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 51 | storage.append(.meta(value, alignment: align, privacy: privacy)) 52 | } 53 | 54 | /// Defines interpolation for expressions of type NSObject. 55 | public mutating func appendInterpolation(_ argumentObject: @Sendable @autoclosure @escaping () -> NSObject, privacy: LogPrivacy = .private) { 56 | storage.append(.object(argumentObject, privacy: privacy)) 57 | } 58 | } 59 | 60 | extension LogInterpolation { 61 | /// Defines interpolation for expressions of type Int 62 | public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> T, format: LogIntegerFormatting = .decimal, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 63 | storage.append(.signedInt({ Int64(number()) }, format: format, alignment: align, privacy: privacy)) 64 | } 65 | 66 | /// Defines interpolation for expressions of type UInt 67 | public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> T, format: LogIntegerFormatting = .decimal, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 68 | storage.append(.unsignedInt({ UInt64(number()) }, format: format, alignment: align, privacy: privacy)) 69 | } 70 | } 71 | 72 | extension LogInterpolation { 73 | /// Defines interpolation for expressions of type Float 74 | public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> Float, format: LogFloatFormatting = .fixed, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 75 | storage.append(.float(number, format: format, alignment: align, privacy: privacy)) 76 | } 77 | 78 | #if canImport(CoreGraphics) 79 | /// Defines interpolation for expressions of type CGFloat 80 | public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> CGFloat, format: LogFloatFormatting = .fixed, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 81 | storage.append(.cgfloat(number, format: format, alignment: align, privacy: privacy)) 82 | } 83 | #endif 84 | 85 | /// Defines interpolation for expressions of type Double 86 | public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> Double, format: LogFloatFormatting = .fixed, align: LogStringAlignment = .none, privacy: LogPrivacy = .private) { 87 | storage.append(.double(number, format: format, alignment: align, privacy: privacy)) 88 | } 89 | } 90 | 91 | extension LogInterpolation { 92 | /// Defines interpolation for expressions of type Bool 93 | public mutating func appendInterpolation(_ boolean: @Sendable @autoclosure @escaping () -> Bool, format: LogBoolFormat = .truth, privacy: LogPrivacy = .private) { 94 | storage.append(.bool(boolean, format: format, privacy: privacy)) 95 | } 96 | } 97 | 98 | public enum LogBoolFormat: Sendable { 99 | /// Displays an interpolated boolean value as true or false. 100 | case truth 101 | /// Displays an interpolated boolean value as yes or no. 102 | case answer 103 | } 104 | 105 | public struct LogStringAlignment: Sendable { 106 | enum Alignment: Sendable { 107 | case none 108 | case left(columns: @Sendable () -> Int) 109 | case right(columns: @Sendable () -> Int) 110 | } 111 | 112 | let alignment: Alignment 113 | 114 | public static var none: LogStringAlignment { 115 | LogStringAlignment(alignment: .none) 116 | } 117 | 118 | public static func right(columns: @Sendable @autoclosure @escaping () -> Int) -> LogStringAlignment { 119 | LogStringAlignment(alignment: .right(columns: columns)) 120 | } 121 | 122 | public static func left(columns: @Sendable @autoclosure @escaping () -> Int) -> LogStringAlignment { 123 | LogStringAlignment(alignment: .left(columns: columns)) 124 | } 125 | } 126 | 127 | public struct LogFloatFormatting: Sendable { 128 | enum Format: Sendable { 129 | case fixed(precision: @Sendable () -> Int, explicitPositiveSign: Bool) 130 | } 131 | 132 | let format: Format 133 | 134 | public static var fixed: LogFloatFormatting { 135 | fixed(precision: 6, explicitPositiveSign: false) 136 | } 137 | 138 | public static func fixed(explicitPositiveSign: Bool = false) -> LogFloatFormatting { 139 | fixed(precision: 6, explicitPositiveSign: explicitPositiveSign) 140 | } 141 | 142 | public static func fixed(precision: @Sendable @autoclosure @escaping () -> Int, explicitPositiveSign: Bool = false) -> LogFloatFormatting { 143 | LogFloatFormatting(format: .fixed(precision: precision, explicitPositiveSign: explicitPositiveSign)) 144 | } 145 | } 146 | 147 | public struct LogIntegerFormatting: Sendable { 148 | enum Format { 149 | case decimal(minDigits: @Sendable () -> Int, explicitPositiveSign: Bool) 150 | } 151 | 152 | let format: Format 153 | 154 | public static var decimal: LogIntegerFormatting { 155 | decimal(explicitPositiveSign: false, minDigits: 0) 156 | } 157 | 158 | public static func decimal(explicitPositiveSign: Bool = false) -> LogIntegerFormatting { 159 | decimal(explicitPositiveSign: explicitPositiveSign, minDigits: 0) 160 | } 161 | 162 | public static func decimal(explicitPositiveSign: Bool = false, minDigits: @Sendable @autoclosure @escaping () -> Int) -> LogIntegerFormatting { 163 | LogIntegerFormatting(format: .decimal(minDigits: minDigits, explicitPositiveSign: explicitPositiveSign)) 164 | } 165 | } 166 | 167 | @frozen public struct LogPrivacy: Equatable, Sendable { 168 | public enum Mask: Equatable, Sendable { 169 | case hash 170 | case none 171 | } 172 | 173 | public var isPrivate: Bool 174 | private let mask: Mask? 175 | 176 | public static var `public`: LogPrivacy { 177 | LogPrivacy(isPrivate: false, mask: nil) 178 | } 179 | 180 | public static var `private`: LogPrivacy { 181 | LogPrivacy(isPrivate: true, mask: nil) 182 | } 183 | 184 | public static func `private`(mask: Mask) -> LogPrivacy { 185 | LogPrivacy(isPrivate: true, mask: mask) 186 | } 187 | 188 | #if DEBUG 189 | internal static var disableRedaction: Bool = true 190 | #endif 191 | 192 | internal static let redacted = "" 193 | internal func value(for value: Any) -> String { 194 | #if DEBUG 195 | if LogPrivacy.disableRedaction { 196 | return String(describing: value) 197 | } 198 | #endif 199 | 200 | switch self { 201 | case .public: 202 | return String(describing: value) 203 | case .private(mask: .hash): 204 | return "\(String(describing: value).hash)" 205 | default: 206 | return Self.redacted 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Sources/Logging/Locks.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Logging API open source project 4 | // 5 | // Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Logging API project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | //===----------------------------------------------------------------------===// 16 | // 17 | // This source file is part of the SwiftNIO open source project 18 | // 19 | // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors 20 | // Licensed under Apache License v2.0 21 | // 22 | // See LICENSE.txt for license information 23 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 24 | // 25 | // SPDX-License-Identifier: Apache-2.0 26 | // 27 | //===----------------------------------------------------------------------===// 28 | 29 | #if canImport(WASILibc) 30 | // No locking on WASILibc 31 | #elseif canImport(Darwin) 32 | import Darwin 33 | #elseif os(Windows) 34 | import WinSDK 35 | #elseif canImport(Glibc) 36 | import Glibc 37 | #elseif canImport(Musl) 38 | import Musl 39 | #else 40 | #error("Unsupported runtime") 41 | #endif 42 | 43 | /// A threading lock based on `libpthread` instead of `libdispatch`. 44 | /// 45 | /// This object provides a lock on top of a single `pthread_mutex_t`. This kind 46 | /// of lock is safe to use with `libpthread`-based threading models, such as the 47 | /// one used by NIO. On Windows, the lock is based on the substantially similar 48 | /// `SRWLOCK` type. 49 | internal final class Lock { 50 | #if canImport(WASILibc) 51 | // WASILibc is single threaded, provides no locks 52 | #elseif os(Windows) 53 | fileprivate let mutex: UnsafeMutablePointer = 54 | UnsafeMutablePointer.allocate(capacity: 1) 55 | #else 56 | fileprivate let mutex: UnsafeMutablePointer = 57 | UnsafeMutablePointer.allocate(capacity: 1) 58 | #endif 59 | 60 | /// Create a new lock. 61 | public init() { 62 | #if canImport(WASILibc) 63 | // WASILibc is single threaded, provides no locks 64 | #elseif os(Windows) 65 | InitializeSRWLock(self.mutex) 66 | #else 67 | var attr = pthread_mutexattr_t() 68 | pthread_mutexattr_init(&attr) 69 | pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) 70 | 71 | let err = pthread_mutex_init(self.mutex, &attr) 72 | precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") 73 | #endif 74 | } 75 | 76 | deinit { 77 | #if canImport(WASILibc) 78 | // WASILibc is single threaded, provides no locks 79 | #elseif os(Windows) 80 | // SRWLOCK does not need to be free'd 81 | self.mutex.deallocate() 82 | #else 83 | let err = pthread_mutex_destroy(self.mutex) 84 | precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") 85 | self.mutex.deallocate() 86 | #endif 87 | } 88 | 89 | /// Acquire the lock. 90 | /// 91 | /// Whenever possible, consider using `withLock` instead of this method and 92 | /// `unlock`, to simplify lock handling. 93 | public func lock() { 94 | #if canImport(WASILibc) 95 | // WASILibc is single threaded, provides no locks 96 | #elseif os(Windows) 97 | AcquireSRWLockExclusive(self.mutex) 98 | #else 99 | let err = pthread_mutex_lock(self.mutex) 100 | precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") 101 | #endif 102 | } 103 | 104 | /// Release the lock. 105 | /// 106 | /// Whenever possible, consider using `withLock` instead of this method and 107 | /// `lock`, to simplify lock handling. 108 | public func unlock() { 109 | #if canImport(WASILibc) 110 | // WASILibc is single threaded, provides no locks 111 | #elseif os(Windows) 112 | ReleaseSRWLockExclusive(self.mutex) 113 | #else 114 | let err = pthread_mutex_unlock(self.mutex) 115 | precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") 116 | #endif 117 | } 118 | } 119 | 120 | extension Lock { 121 | /// Acquire the lock for the duration of the given block. 122 | /// 123 | /// This convenience method should be preferred to `lock` and `unlock` in 124 | /// most situations, as it ensures that the lock will be released regardless 125 | /// of how `body` exits. 126 | /// 127 | /// - Parameter body: The block to execute while holding the lock. 128 | /// - Returns: The value returned by the block. 129 | @inlinable 130 | internal func withLock(_ body: () throws -> T) rethrows -> T { 131 | self.lock() 132 | defer { 133 | self.unlock() 134 | } 135 | return try body() 136 | } 137 | 138 | // specialise Void return (for performance) 139 | @inlinable 140 | internal func withLockVoid(_ body: () throws -> Void) rethrows { 141 | try self.withLock(body) 142 | } 143 | } 144 | 145 | /// A reader/writer threading lock based on `libpthread` instead of `libdispatch`. 146 | /// 147 | /// This object provides a lock on top of a single `pthread_rwlock_t`. This kind 148 | /// of lock is safe to use with `libpthread`-based threading models, such as the 149 | /// one used by NIO. On Windows, the lock is based on the substantially similar 150 | /// `SRWLOCK` type. 151 | internal final class ReadWriteLock { 152 | #if canImport(WASILibc) 153 | // WASILibc is single threaded, provides no locks 154 | #elseif os(Windows) 155 | fileprivate let rwlock: UnsafeMutablePointer = 156 | UnsafeMutablePointer.allocate(capacity: 1) 157 | fileprivate var shared: Bool = true 158 | #else 159 | fileprivate let rwlock: UnsafeMutablePointer = 160 | UnsafeMutablePointer.allocate(capacity: 1) 161 | #endif 162 | 163 | /// Create a new lock. 164 | public init() { 165 | #if canImport(WASILibc) 166 | // WASILibc is single threaded, provides no locks 167 | #elseif os(Windows) 168 | InitializeSRWLock(self.rwlock) 169 | #else 170 | let err = pthread_rwlock_init(self.rwlock, nil) 171 | precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") 172 | #endif 173 | } 174 | 175 | deinit { 176 | #if canImport(WASILibc) 177 | // WASILibc is single threaded, provides no locks 178 | #elseif os(Windows) 179 | // SRWLOCK does not need to be free'd 180 | self.rwlock.deallocate() 181 | #else 182 | let err = pthread_rwlock_destroy(self.rwlock) 183 | precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") 184 | self.rwlock.deallocate() 185 | #endif 186 | } 187 | 188 | /// Acquire a reader lock. 189 | /// 190 | /// Whenever possible, consider using `withReaderLock` instead of this 191 | /// method and `unlock`, to simplify lock handling. 192 | public func lockRead() { 193 | #if canImport(WASILibc) 194 | // WASILibc is single threaded, provides no locks 195 | #elseif os(Windows) 196 | AcquireSRWLockShared(self.rwlock) 197 | self.shared = true 198 | #else 199 | let err = pthread_rwlock_rdlock(self.rwlock) 200 | precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") 201 | #endif 202 | } 203 | 204 | /// Acquire a writer lock. 205 | /// 206 | /// Whenever possible, consider using `withWriterLock` instead of this 207 | /// method and `unlock`, to simplify lock handling. 208 | public func lockWrite() { 209 | #if canImport(WASILibc) 210 | // WASILibc is single threaded, provides no locks 211 | #elseif os(Windows) 212 | AcquireSRWLockExclusive(self.rwlock) 213 | self.shared = false 214 | #else 215 | let err = pthread_rwlock_wrlock(self.rwlock) 216 | precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") 217 | #endif 218 | } 219 | 220 | /// Release the lock. 221 | /// 222 | /// Whenever possible, consider using `withReaderLock` and `withWriterLock` 223 | /// instead of this method and `lockRead` and `lockWrite`, to simplify lock 224 | /// handling. 225 | public func unlock() { 226 | #if canImport(WASILibc) 227 | // WASILibc is single threaded, provides no locks 228 | #elseif os(Windows) 229 | if self.shared { 230 | ReleaseSRWLockShared(self.rwlock) 231 | } else { 232 | ReleaseSRWLockExclusive(self.rwlock) 233 | } 234 | #else 235 | let err = pthread_rwlock_unlock(self.rwlock) 236 | precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)") 237 | #endif 238 | } 239 | } 240 | 241 | extension ReadWriteLock { 242 | /// Acquire the reader lock for the duration of the given block. 243 | /// 244 | /// This convenience method should be preferred to `lockRead` and `unlock` 245 | /// in most situations, as it ensures that the lock will be released 246 | /// regardless of how `body` exits. 247 | /// 248 | /// - Parameter body: The block to execute while holding the reader lock. 249 | /// - Returns: The value returned by the block. 250 | @inlinable 251 | internal func withReaderLock(_ body: () throws -> T) rethrows -> T { 252 | self.lockRead() 253 | defer { 254 | self.unlock() 255 | } 256 | return try body() 257 | } 258 | 259 | /// Acquire the writer lock for the duration of the given block. 260 | /// 261 | /// This convenience method should be preferred to `lockWrite` and `unlock` 262 | /// in most situations, as it ensures that the lock will be released 263 | /// regardless of how `body` exits. 264 | /// 265 | /// - Parameter body: The block to execute while holding the writer lock. 266 | /// - Returns: The value returned by the block. 267 | @inlinable 268 | internal func withWriterLock(_ body: () throws -> T) rethrows -> T { 269 | self.lockWrite() 270 | defer { 271 | self.unlock() 272 | } 273 | return try body() 274 | } 275 | 276 | // specialise Void return (for performance) 277 | @inlinable 278 | internal func withReaderLockVoid(_ body: () throws -> Void) rethrows { 279 | try self.withReaderLock(body) 280 | } 281 | 282 | // specialise Void return (for performance) 283 | @inlinable 284 | internal func withWriterLockVoid(_ body: () throws -> Void) rethrows { 285 | try self.withWriterLock(body) 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /Sources/Logging/LogHandler.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Logging API open source project 4 | // 5 | // Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Logging API project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// A `LogHandler` is an implementation of a logging backend. 16 | /// 17 | /// This type is an implementation detail and should not normally be used, unless implementing your own logging backend. 18 | /// To use the SwiftLog API, please refer to the documentation of ``Logger``. 19 | /// 20 | /// # Implementation requirements 21 | /// 22 | /// To implement your own `LogHandler` you should respect a few requirements that are necessary so applications work 23 | /// as expected regardless of the selected `LogHandler` implementation. 24 | /// 25 | /// - The ``LogHandler`` must be a `struct`. 26 | /// - The metadata and `logLevel` properties must be implemented so that setting them on a `Logger` does not affect 27 | /// other `Logger`s. 28 | /// 29 | /// ### Treat log level & metadata as values 30 | /// 31 | /// When developing your `LogHandler`, please make sure the following test works. 32 | /// 33 | /// ```swift 34 | /// LoggingSystem.bootstrap(MyLogHandler.init) // your LogHandler might have a different bootstrapping step 35 | /// var logger1 = Logger(label: "first logger") 36 | /// logger1.logLevel = .debug 37 | /// logger1[metadataKey: "only-on"] = "first" 38 | /// 39 | /// var logger2 = logger1 40 | /// logger2.logLevel = .error // this must not override `logger1`'s log level 41 | /// logger2[metadataKey: "only-on"] = "second" // this must not override `logger1`'s metadata 42 | /// 43 | /// XCTAssertEqual(.debug, logger1.logLevel) 44 | /// XCTAssertEqual(.error, logger2.logLevel) 45 | /// XCTAssertEqual("first", logger1[metadataKey: "only-on"]) 46 | /// XCTAssertEqual("second", logger2[metadataKey: "only-on"]) 47 | /// ``` 48 | /// 49 | /// ### Special cases 50 | /// 51 | /// In certain special cases, the log level behaving like a value on `Logger` might not be what you want. For example, 52 | /// you might want to set the log level across _all_ `Logger`s to `.debug` when say a signal (eg. `SIGUSR1`) is received 53 | /// to be able to debug special failures in production. This special case is acceptable but we urge you to create a 54 | /// solution specific to your `LogHandler` implementation to achieve that. Please find an example implementation of this 55 | /// behavior below, on reception of the signal you would call 56 | /// `LogHandlerWithGlobalLogLevelOverride.overrideGlobalLogLevel = .debug`, for example. 57 | /// 58 | /// ```swift 59 | /// import class Foundation.NSLock 60 | /// 61 | /// public struct LogHandlerWithGlobalLogLevelOverride: LogHandler { 62 | /// // the static properties hold the globally overridden log level (if overridden) 63 | /// private static let overrideLock = NSLock() 64 | /// private static var overrideLogLevel: Logger.Level? = nil 65 | /// 66 | /// // this holds the log level if not overridden 67 | /// private var _logLevel: Logger.Level = .info 68 | /// 69 | /// // metadata storage 70 | /// public var metadata: Logger.Metadata = [:] 71 | /// 72 | /// public init(label: String) { 73 | /// // [...] 74 | /// } 75 | /// 76 | /// public var logLevel: Logger.Level { 77 | /// // when we get asked for the log level, we check if it was globally overridden or not 78 | /// get { 79 | /// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock() 80 | /// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() } 81 | /// return LogHandlerWithGlobalLogLevelOverride.overrideLogLevel ?? self._logLevel 82 | /// } 83 | /// // we set the log level whenever we're asked (note: this might not have an effect if globally 84 | /// // overridden) 85 | /// set { 86 | /// self._logLevel = newValue 87 | /// } 88 | /// } 89 | /// 90 | /// public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, 91 | /// source: String, file: String, function: String, line: UInt) { 92 | /// // [...] 93 | /// } 94 | /// 95 | /// public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 96 | /// get { 97 | /// return self.metadata[metadataKey] 98 | /// } 99 | /// set(newValue) { 100 | /// self.metadata[metadataKey] = newValue 101 | /// } 102 | /// } 103 | /// 104 | /// // this is the function to globally override the log level, it is not part of the `LogHandler` protocol 105 | /// public static func overrideGlobalLogLevel(_ logLevel: Logger.Level) { 106 | /// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock() 107 | /// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() } 108 | /// LogHandlerWithGlobalLogLevelOverride.overrideLogLevel = logLevel 109 | /// } 110 | /// } 111 | /// ``` 112 | /// 113 | /// Please note that the above `LogHandler` will still pass the 'log level is a value' test above it iff the global log 114 | /// level has not been overridden. And most importantly it passes the requirement listed above: A change to the log 115 | /// level on one `Logger` should not affect the log level of another `Logger` variable. 116 | public protocol LogHandler: _SwiftLogSendableLogHandler { 117 | /// The metadata provider this `LogHandler` will use when a log statement is about to be emitted. 118 | /// 119 | /// A ``Logger/MetadataProvider`` may add a constant set of metadata, 120 | /// or use task-local values to pick up contextual metadata and add it to emitted logs. 121 | var metadataProvider: Logger.MetadataProvider? { get set } 122 | 123 | /// This method is called when a `LogHandler` must emit a log message. There is no need for the `LogHandler` to 124 | /// check if the `level` is above or below the configured `logLevel` as `Logger` already performed this check and 125 | /// determined that a message should be logged. 126 | /// 127 | /// - parameters: 128 | /// - level: The log level the message was logged at. 129 | /// - message: The message to log. To obtain a `String` representation call `message.description`. 130 | /// - metadata: The metadata associated to this log message. 131 | /// - source: The source where the log message originated, for example the logging module. 132 | /// - file: The file the log message was emitted from. 133 | /// - function: The function the log line was emitted from. 134 | /// - line: The line the log message was emitted from. 135 | func log(level: Logger.Level, 136 | message: Logger.Message, 137 | metadata: Logger.Metadata?, 138 | source: String, 139 | file: String, 140 | function: String, 141 | line: UInt) 142 | 143 | /// SwiftLog 1.0 compatibility method. Please do _not_ implement, implement 144 | /// `log(level:message:metadata:source:file:function:line:)` instead. 145 | @available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)") 146 | func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt) 147 | 148 | /// Add, remove, or change the logging metadata. 149 | /// 150 | /// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must 151 | /// only affect this very `LogHandler`. 152 | /// 153 | /// - parameters: 154 | /// - metadataKey: The key for the metadata item 155 | subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set } 156 | 157 | /// Get or set the entire metadata storage as a dictionary. 158 | /// 159 | /// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must 160 | /// only affect this very `LogHandler`. 161 | var metadata: Logger.Metadata { get set } 162 | 163 | /// Get or set the configured log level. 164 | /// 165 | /// - note: `LogHandler`s must treat the log level as a value type. This means that the change in metadata must 166 | /// only affect this very `LogHandler`. It is acceptable to provide some form of global log level override 167 | /// that means a change in log level on a particular `LogHandler` might not be reflected in any 168 | /// `LogHandler`. 169 | var logLevel: Logger.Level { get set } 170 | } 171 | 172 | extension LogHandler { 173 | /// Default implementation for `metadataProvider` which defaults to `nil`. 174 | /// This default exists in order to facilitate source-compatible introduction of the `metadataProvider` protocol requirement. 175 | public var metadataProvider: Logger.MetadataProvider? { 176 | get { 177 | nil 178 | } 179 | set { 180 | #if DEBUG 181 | if LoggingSystem.warnOnceLogHandlerNotSupportedMetadataProvider(Self.self) { 182 | self.log(level: .warning, message: "Attempted to set metadataProvider on \(Self.self) that did not implement support for them. Please contact the log handler maintainer to implement metadata provider support.", metadata: nil, source: "Logging", file: #file, function: #function, line: #line) 183 | } 184 | #endif 185 | } 186 | } 187 | } 188 | 189 | extension LogHandler { 190 | @available(*, deprecated, message: "You should implement this method instead of using the default implementation") 191 | public func log(level: Logger.Level, 192 | message: Logger.Message, 193 | metadata: Logger.Metadata?, 194 | source: String, 195 | file: String, 196 | function: String, 197 | line: UInt) { 198 | self.log(level: level, message: message, metadata: metadata, file: file, function: function, line: line) 199 | } 200 | 201 | @available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)") 202 | public func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt) { 203 | self.log(level: level, 204 | message: message, 205 | metadata: metadata, 206 | source: Logger.currentModule(filePath: file), 207 | file: file, 208 | function: function, 209 | line: line) 210 | } 211 | } 212 | 213 | // MARK: - Sendable support helpers 214 | 215 | #if compiler(>=5.6) 216 | @preconcurrency public protocol _SwiftLogSendableLogHandler: Sendable {} 217 | #else 218 | public protocol _SwiftLogSendableLogHandler {} 219 | #endif 220 | -------------------------------------------------------------------------------- /Sources/Logging/Logging.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Logging API open source project 4 | // 5 | // Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Logging API project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #if canImport(Darwin) 16 | import Darwin 17 | #elseif os(Windows) 18 | import CRT 19 | #elseif canImport(Glibc) 20 | import Glibc 21 | #elseif canImport(Musl) 22 | import Musl 23 | #elseif canImport(WASILibc) 24 | import WASILibc 25 | #else 26 | #error("Unsupported runtime") 27 | #endif 28 | 29 | /// A `Logger` is the central type in `SwiftLog`. Its central function is to emit log messages using one of the methods 30 | /// corresponding to a log level. 31 | /// 32 | /// `Logger`s are value types with respect to the ``logLevel`` and the ``metadata`` (as well as the immutable `label` 33 | /// and the selected ``LogHandler``). Therefore, `Logger`s are suitable to be passed around between libraries if you want 34 | /// to preserve metadata across libraries. 35 | /// 36 | /// The most basic usage of a `Logger` is 37 | /// 38 | /// ```swift 39 | /// logger.info("Hello World!") 40 | /// ``` 41 | public struct Logger { 42 | @usableFromInline 43 | var handler: LogHandler 44 | 45 | /// An identifier of the creator of this `Logger`. 46 | public let label: String 47 | 48 | /// The metadata provider this logger was created with. 49 | public var metadataProvider: Logger.MetadataProvider? { 50 | return self.handler.metadataProvider 51 | } 52 | 53 | internal init(label: String, _ handler: LogHandler) { 54 | self.label = label 55 | self.handler = handler 56 | } 57 | } 58 | 59 | extension Logger { 60 | #if compiler(>=5.3) 61 | /// Log a message passing the log level as a parameter. 62 | /// 63 | /// If the `logLevel` passed to this method is more severe than the `Logger`'s ``logLevel``, it will be logged, 64 | /// otherwise nothing will happen. 65 | /// 66 | /// - parameters: 67 | /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`. 68 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 69 | /// - metadata: One-off metadata to attach to this log message. 70 | /// - source: The source this log messages originates from. Defaults 71 | /// to the module emitting the log message (on Swift 5.3 or 72 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 73 | /// older). 74 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 75 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 76 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 77 | /// it defaults to `#function`). 78 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 79 | /// defaults to `#line`). 80 | @inlinable 81 | public func log(level: Logger.Level, 82 | _ message: @autoclosure () -> Logger.Message, 83 | metadata: @autoclosure () -> Logger.Metadata? = nil, 84 | source: @autoclosure () -> String? = nil, 85 | file: String = #fileID, function: String = #function, line: UInt = #line) { 86 | if self.logLevel <= level { 87 | self.handler.log(level: level, 88 | message: message(), 89 | metadata: metadata(), 90 | source: source() ?? Logger.currentModule(fileID: (file)), 91 | file: file, function: function, line: line) 92 | } 93 | } 94 | 95 | #else 96 | @inlinable 97 | public func log(level: Logger.Level, 98 | _ message: @autoclosure () -> Logger.Message, 99 | metadata: @autoclosure () -> Logger.Metadata? = nil, 100 | source: @autoclosure () -> String? = nil, 101 | file: String = #file, function: String = #function, line: UInt = #line) { 102 | if self.logLevel <= level { 103 | self.handler.log(level: level, 104 | message: message(), 105 | metadata: metadata(), 106 | source: source() ?? Logger.currentModule(filePath: (file)), 107 | file: file, function: function, line: line) 108 | } 109 | } 110 | #endif 111 | 112 | /// Log a message passing the log level as a parameter. 113 | /// 114 | /// If the ``logLevel`` passed to this method is more severe than the `Logger`'s ``logLevel``, it will be logged, 115 | /// otherwise nothing will happen. 116 | /// 117 | /// - parameters: 118 | /// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`. 119 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 120 | /// - metadata: One-off metadata to attach to this log message. 121 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 122 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 123 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 124 | /// it defaults to `#function`). 125 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 126 | /// defaults to `#line`). 127 | #if compiler(>=5.3) 128 | @inlinable 129 | public func log(level: Logger.Level, 130 | _ message: @autoclosure () -> Logger.Message, 131 | metadata: @autoclosure () -> Logger.Metadata? = nil, 132 | file: String = #fileID, function: String = #function, line: UInt = #line) { 133 | self.log(level: level, message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 134 | } 135 | 136 | #else 137 | @inlinable 138 | public func log(level: Logger.Level, 139 | _ message: @autoclosure () -> Logger.Message, 140 | metadata: @autoclosure () -> Logger.Metadata? = nil, 141 | file: String = #file, function: String = #function, line: UInt = #line) { 142 | self.log(level: level, message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 143 | } 144 | #endif 145 | 146 | /// Add, change, or remove a logging metadata item. 147 | /// 148 | /// - note: Logging metadata behaves as a value that means a change to the logging metadata will only affect the 149 | /// very `Logger` it was changed on. 150 | @inlinable 151 | public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 152 | get { 153 | return self.handler[metadataKey: metadataKey] 154 | } 155 | set { 156 | self.handler[metadataKey: metadataKey] = newValue 157 | } 158 | } 159 | 160 | /// Get or set the log level configured for this `Logger`. 161 | /// 162 | /// - note: `Logger`s treat `logLevel` as a value. This means that a change in `logLevel` will only affect this 163 | /// very `Logger`. It is acceptable for logging backends to have some form of global log level override 164 | /// that affects multiple or even all loggers. This means a change in `logLevel` to one `Logger` might in 165 | /// certain cases have no effect. 166 | @inlinable 167 | public var logLevel: Logger.Level { 168 | get { 169 | return self.handler.logLevel 170 | } 171 | set { 172 | self.handler.logLevel = newValue 173 | } 174 | } 175 | } 176 | 177 | extension Logger { 178 | /// Log a message passing with the ``Logger/Level/trace`` log level. 179 | /// 180 | /// If `.trace` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 181 | /// otherwise nothing will happen. 182 | /// 183 | /// - parameters: 184 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 185 | /// - metadata: One-off metadata to attach to this log message 186 | /// - source: The source this log messages originates from. Defaults 187 | /// to the module emitting the log message (on Swift 5.3 or 188 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 189 | /// older). 190 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 191 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 192 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 193 | /// it defaults to `#function`). 194 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 195 | /// defaults to `#line`). 196 | #if compiler(>=5.3) 197 | @inlinable 198 | public func trace(_ message: @autoclosure () -> Logger.Message, 199 | metadata: @autoclosure () -> Logger.Metadata? = nil, 200 | source: @autoclosure () -> String? = nil, 201 | file: String = #fileID, function: String = #function, line: UInt = #line) { 202 | self.log(level: .trace, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 203 | } 204 | 205 | #else 206 | @inlinable 207 | public func trace(_ message: @autoclosure () -> Logger.Message, 208 | metadata: @autoclosure () -> Logger.Metadata? = nil, 209 | source: @autoclosure () -> String? = nil, 210 | file: String = #file, function: String = #function, line: UInt = #line) { 211 | self.log(level: .trace, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 212 | } 213 | #endif 214 | 215 | /// Log a message passing with the ``Logger/Level/trace`` log level. 216 | /// 217 | /// If `.trace` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 218 | /// otherwise nothing will happen. 219 | /// 220 | /// - parameters: 221 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 222 | /// - metadata: One-off metadata to attach to this log message 223 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 224 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 225 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 226 | /// it defaults to `#function`). 227 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 228 | /// defaults to `#line`). 229 | #if compiler(>=5.3) 230 | @inlinable 231 | public func trace(_ message: @autoclosure () -> Logger.Message, 232 | metadata: @autoclosure () -> Logger.Metadata? = nil, 233 | file: String = #fileID, function: String = #function, line: UInt = #line) { 234 | self.trace(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 235 | } 236 | 237 | #else 238 | @inlinable 239 | public func trace(_ message: @autoclosure () -> Logger.Message, 240 | metadata: @autoclosure () -> Logger.Metadata? = nil, 241 | file: String = #file, function: String = #function, line: UInt = #line) { 242 | self.trace(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 243 | } 244 | #endif 245 | 246 | /// Log a message passing with the ``Logger/Level/debug`` log level. 247 | /// 248 | /// If `.debug` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 249 | /// otherwise nothing will happen. 250 | /// 251 | /// - parameters: 252 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 253 | /// - metadata: One-off metadata to attach to this log message. 254 | /// - source: The source this log messages originates from. Defaults 255 | /// to the module emitting the log message (on Swift 5.3 or 256 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 257 | /// older). 258 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 259 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 260 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 261 | /// it defaults to `#function`). 262 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 263 | /// defaults to `#line`). 264 | #if compiler(>=5.3) 265 | @inlinable 266 | public func debug(_ message: @autoclosure () -> Logger.Message, 267 | metadata: @autoclosure () -> Logger.Metadata? = nil, 268 | source: @autoclosure () -> String? = nil, 269 | file: String = #fileID, function: String = #function, line: UInt = #line) { 270 | self.log(level: .debug, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 271 | } 272 | 273 | #else 274 | @inlinable 275 | public func debug(_ message: @autoclosure () -> Logger.Message, 276 | metadata: @autoclosure () -> Logger.Metadata? = nil, 277 | source: @autoclosure () -> String? = nil, 278 | file: String = #file, function: String = #function, line: UInt = #line) { 279 | self.log(level: .debug, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 280 | } 281 | #endif 282 | 283 | /// Log a message passing with the ``Logger/Level/debug`` log level. 284 | /// 285 | /// If `.debug` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 286 | /// otherwise nothing will happen. 287 | /// 288 | /// - parameters: 289 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 290 | /// - metadata: One-off metadata to attach to this log message. 291 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 292 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 293 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 294 | /// it defaults to `#function`). 295 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 296 | /// defaults to `#line`). 297 | #if compiler(>=5.3) 298 | @inlinable 299 | public func debug(_ message: @autoclosure () -> Logger.Message, 300 | metadata: @autoclosure () -> Logger.Metadata? = nil, 301 | file: String = #fileID, function: String = #function, line: UInt = #line) { 302 | self.debug(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 303 | } 304 | 305 | #else 306 | @inlinable 307 | public func debug(_ message: @autoclosure () -> Logger.Message, 308 | metadata: @autoclosure () -> Logger.Metadata? = nil, 309 | file: String = #file, function: String = #function, line: UInt = #line) { 310 | self.debug(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 311 | } 312 | #endif 313 | 314 | /// Log a message passing with the ``Logger/Level/info`` log level. 315 | /// 316 | /// If `.info` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 317 | /// otherwise nothing will happen. 318 | /// 319 | /// - parameters: 320 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 321 | /// - metadata: One-off metadata to attach to this log message. 322 | /// - source: The source this log messages originates from. Defaults 323 | /// to the module emitting the log message (on Swift 5.3 or 324 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 325 | /// older). 326 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 327 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 328 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 329 | /// it defaults to `#function`). 330 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 331 | /// defaults to `#line`). 332 | #if compiler(>=5.3) 333 | @inlinable 334 | public func info(_ message: @autoclosure () -> Logger.Message, 335 | metadata: @autoclosure () -> Logger.Metadata? = nil, 336 | source: @autoclosure () -> String? = nil, 337 | file: String = #fileID, function: String = #function, line: UInt = #line) { 338 | self.log(level: .info, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 339 | } 340 | 341 | #else 342 | @inlinable 343 | public func info(_ message: @autoclosure () -> Logger.Message, 344 | metadata: @autoclosure () -> Logger.Metadata? = nil, 345 | source: @autoclosure () -> String? = nil, 346 | file: String = #file, function: String = #function, line: UInt = #line) { 347 | self.log(level: .info, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 348 | } 349 | #endif 350 | 351 | /// Log a message passing with the ``Logger/Level/info`` log level. 352 | /// 353 | /// If `.info` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 354 | /// otherwise nothing will happen. 355 | /// 356 | /// - parameters: 357 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 358 | /// - metadata: One-off metadata to attach to this log message. 359 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 360 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 361 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 362 | /// it defaults to `#function`). 363 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 364 | /// defaults to `#line`). 365 | #if compiler(>=5.3) 366 | @inlinable 367 | public func info(_ message: @autoclosure () -> Logger.Message, 368 | metadata: @autoclosure () -> Logger.Metadata? = nil, 369 | file: String = #fileID, function: String = #function, line: UInt = #line) { 370 | self.info(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 371 | } 372 | 373 | #else 374 | @inlinable 375 | public func info(_ message: @autoclosure () -> Logger.Message, 376 | metadata: @autoclosure () -> Logger.Metadata? = nil, 377 | file: String = #file, function: String = #function, line: UInt = #line) { 378 | self.info(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 379 | } 380 | #endif 381 | 382 | /// Log a message passing with the ``Logger/Level/notice`` log level. 383 | /// 384 | /// If `.notice` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 385 | /// otherwise nothing will happen. 386 | /// 387 | /// - parameters: 388 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 389 | /// - metadata: One-off metadata to attach to this log message. 390 | /// - source: The source this log messages originates from. Defaults 391 | /// to the module emitting the log message (on Swift 5.3 or 392 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 393 | /// older). 394 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 395 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 396 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 397 | /// it defaults to `#function`). 398 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 399 | /// defaults to `#line`). 400 | #if compiler(>=5.3) 401 | @inlinable 402 | public func notice(_ message: @autoclosure () -> Logger.Message, 403 | metadata: @autoclosure () -> Logger.Metadata? = nil, 404 | source: @autoclosure () -> String? = nil, 405 | file: String = #fileID, function: String = #function, line: UInt = #line) { 406 | self.log(level: .notice, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 407 | } 408 | 409 | #else 410 | @inlinable 411 | public func notice(_ message: @autoclosure () -> Logger.Message, 412 | metadata: @autoclosure () -> Logger.Metadata? = nil, 413 | source: @autoclosure () -> String? = nil, 414 | file: String = #file, function: String = #function, line: UInt = #line) { 415 | self.log(level: .notice, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 416 | } 417 | #endif 418 | 419 | /// Log a message passing with the ``Logger/Level/notice`` log level. 420 | /// 421 | /// If `.notice` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 422 | /// otherwise nothing will happen. 423 | /// 424 | /// - parameters: 425 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 426 | /// - metadata: One-off metadata to attach to this log message. 427 | /// - source: The source this log messages originates from. Defaults 428 | /// to the module emitting the log message (on Swift 5.3 or 429 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 430 | /// older). 431 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 432 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 433 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 434 | /// it defaults to `#function`). 435 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 436 | /// defaults to `#line`). 437 | #if compiler(>=5.3) 438 | @inlinable 439 | public func notice(_ message: @autoclosure () -> Logger.Message, 440 | metadata: @autoclosure () -> Logger.Metadata? = nil, 441 | file: String = #fileID, function: String = #function, line: UInt = #line) { 442 | self.notice(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 443 | } 444 | 445 | #else 446 | @inlinable 447 | public func notice(_ message: @autoclosure () -> Logger.Message, 448 | metadata: @autoclosure () -> Logger.Metadata? = nil, 449 | file: String = #file, function: String = #function, line: UInt = #line) { 450 | self.notice(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 451 | } 452 | #endif 453 | 454 | /// Log a message passing with the ``Logger/Level/warning`` log level. 455 | /// 456 | /// If `.warning` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 457 | /// otherwise nothing will happen. 458 | /// 459 | /// - parameters: 460 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 461 | /// - metadata: One-off metadata to attach to this log message. 462 | /// - source: The source this log messages originates from. Defaults 463 | /// to the module emitting the log message (on Swift 5.3 or 464 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 465 | /// older). 466 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 467 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 468 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 469 | /// it defaults to `#function`). 470 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 471 | /// defaults to `#line`). 472 | #if compiler(>=5.3) 473 | @inlinable 474 | public func warning(_ message: @autoclosure () -> Logger.Message, 475 | metadata: @autoclosure () -> Logger.Metadata? = nil, 476 | source: @autoclosure () -> String? = nil, 477 | file: String = #fileID, function: String = #function, line: UInt = #line) { 478 | self.log(level: .warning, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 479 | } 480 | 481 | #else 482 | @inlinable 483 | public func warning(_ message: @autoclosure () -> Logger.Message, 484 | metadata: @autoclosure () -> Logger.Metadata? = nil, 485 | source: @autoclosure () -> String? = nil, 486 | file: String = #file, function: String = #function, line: UInt = #line) { 487 | self.log(level: .warning, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 488 | } 489 | #endif 490 | 491 | /// Log a message passing with the ``Logger/Level/warning`` log level. 492 | /// 493 | /// If `.warning` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 494 | /// otherwise nothing will happen. 495 | /// 496 | /// - parameters: 497 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 498 | /// - metadata: One-off metadata to attach to this log message. 499 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 500 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 501 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 502 | /// it defaults to `#function`). 503 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 504 | /// defaults to `#line`). 505 | #if compiler(>=5.3) 506 | @inlinable 507 | public func warning(_ message: @autoclosure () -> Logger.Message, 508 | metadata: @autoclosure () -> Logger.Metadata? = nil, 509 | file: String = #fileID, function: String = #function, line: UInt = #line) { 510 | self.warning(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 511 | } 512 | 513 | #else 514 | @inlinable 515 | public func warning(_ message: @autoclosure () -> Logger.Message, 516 | metadata: @autoclosure () -> Logger.Metadata? = nil, 517 | file: String = #file, function: String = #function, line: UInt = #line) { 518 | self.warning(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 519 | } 520 | #endif 521 | 522 | /// Log a message passing with the ``Logger/Level/error`` log level. 523 | /// 524 | /// If `.error` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 525 | /// otherwise nothing will happen. 526 | /// 527 | /// - parameters: 528 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 529 | /// - metadata: One-off metadata to attach to this log message. 530 | /// - source: The source this log messages originates from. Defaults 531 | /// to the module emitting the log message (on Swift 5.3 or 532 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 533 | /// older). 534 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 535 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 536 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 537 | /// it defaults to `#function`). 538 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 539 | /// defaults to `#line`). 540 | #if compiler(>=5.3) 541 | @inlinable 542 | public func error(_ message: @autoclosure () -> Logger.Message, 543 | metadata: @autoclosure () -> Logger.Metadata? = nil, 544 | source: @autoclosure () -> String? = nil, 545 | file: String = #fileID, function: String = #function, line: UInt = #line) { 546 | self.log(level: .error, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 547 | } 548 | 549 | #else 550 | @inlinable 551 | public func error(_ message: @autoclosure () -> Logger.Message, 552 | metadata: @autoclosure () -> Logger.Metadata? = nil, 553 | source: @autoclosure () -> String? = nil, 554 | file: String = #file, function: String = #function, line: UInt = #line) { 555 | self.log(level: .error, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 556 | } 557 | #endif 558 | 559 | /// Log a message passing with the ``Logger/Level/error`` log level. 560 | /// 561 | /// If `.error` is at least as severe as the `Logger`'s ``logLevel``, it will be logged, 562 | /// otherwise nothing will happen. 563 | /// 564 | /// - parameters: 565 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 566 | /// - metadata: One-off metadata to attach to this log message. 567 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 568 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 569 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 570 | /// it defaults to `#function`). 571 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 572 | /// defaults to `#line`). 573 | #if compiler(>=5.3) 574 | @inlinable 575 | public func error(_ message: @autoclosure () -> Logger.Message, 576 | metadata: @autoclosure () -> Logger.Metadata? = nil, 577 | file: String = #fileID, function: String = #function, line: UInt = #line) { 578 | self.error(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 579 | } 580 | 581 | #else 582 | @inlinable 583 | public func error(_ message: @autoclosure () -> Logger.Message, 584 | metadata: @autoclosure () -> Logger.Metadata? = nil, 585 | file: String = #file, function: String = #function, line: UInt = #line) { 586 | self.error(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 587 | } 588 | #endif 589 | 590 | /// Log a message passing with the ``Logger/Level/critical`` log level. 591 | /// 592 | /// `.critical` messages will always be logged. 593 | /// 594 | /// - parameters: 595 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 596 | /// - metadata: One-off metadata to attach to this log message. 597 | /// - source: The source this log messages originates from. Defaults 598 | /// to the module emitting the log message (on Swift 5.3 or 599 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 600 | /// older). 601 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 602 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 603 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 604 | /// it defaults to `#function`). 605 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 606 | /// defaults to `#line`). 607 | #if compiler(>=5.3) 608 | @inlinable 609 | public func critical(_ message: @autoclosure () -> Logger.Message, 610 | metadata: @autoclosure () -> Logger.Metadata? = nil, 611 | source: @autoclosure () -> String? = nil, 612 | file: String = #fileID, function: String = #function, line: UInt = #line) { 613 | self.log(level: .critical, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 614 | } 615 | 616 | #else 617 | @inlinable 618 | public func critical(_ message: @autoclosure () -> Logger.Message, 619 | metadata: @autoclosure () -> Logger.Metadata? = nil, 620 | source: @autoclosure () -> String? = nil, 621 | file: String = #file, function: String = #function, line: UInt = #line) { 622 | self.log(level: .critical, message(), metadata: metadata(), source: source(), file: file, function: function, line: line) 623 | } 624 | #endif 625 | 626 | /// Log a message passing with the ``Logger/Level/critical`` log level. 627 | /// 628 | /// `.critical` messages will always be logged. 629 | /// 630 | /// - parameters: 631 | /// - message: The message to be logged. `message` can be used with any string interpolation literal. 632 | /// - metadata: One-off metadata to attach to this log message. 633 | /// - source: The source this log messages originates from. Defaults 634 | /// to the module emitting the log message (on Swift 5.3 or 635 | /// newer and the folder name containing the log emitting file on Swift 5.2 or 636 | /// older). 637 | /// - file: The file this log message originates from (there's usually no need to pass it explicitly as it 638 | /// defaults to `#fileID` (on Swift 5.3 or newer and `#file` on Swift 5.2 or older). 639 | /// - function: The function this log message originates from (there's usually no need to pass it explicitly as 640 | /// it defaults to `#function`). 641 | /// - line: The line this log message originates from (there's usually no need to pass it explicitly as it 642 | /// defaults to `#line`). 643 | #if compiler(>=5.3) 644 | @inlinable 645 | public func critical(_ message: @autoclosure () -> Logger.Message, 646 | metadata: @autoclosure () -> Logger.Metadata? = nil, 647 | file: String = #fileID, function: String = #function, line: UInt = #line) { 648 | self.critical(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 649 | } 650 | 651 | #else 652 | @inlinable 653 | public func critical(_ message: @autoclosure () -> Logger.Message, 654 | metadata: @autoclosure () -> Logger.Metadata? = nil, 655 | file: String = #file, function: String = #function, line: UInt = #line) { 656 | self.critical(message(), metadata: metadata(), source: nil, file: file, function: function, line: line) 657 | } 658 | #endif 659 | } 660 | 661 | /// The `LoggingSystem` is a global facility where the default logging backend implementation (`LogHandler`) can be 662 | /// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend 663 | /// implementation. 664 | public enum LoggingSystem { 665 | private static let _factory = FactoryBox { label, _ in StreamLogHandler.standardOutput(label: label) } 666 | private static let _metadataProviderFactory = MetadataProviderBox(nil) 667 | 668 | #if DEBUG 669 | private static var _warnOnceBox: WarnOnceBox = WarnOnceBox() 670 | #endif 671 | 672 | /// `bootstrap` is a one-time configuration function which globally selects the desired logging backend 673 | /// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will 674 | /// lead to undefined behavior, most likely a crash. 675 | /// 676 | /// - parameters: 677 | /// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`. 678 | public static func bootstrap(_ factory: @escaping (String) -> LogHandler) { 679 | self._factory.replaceFactory({ label, _ in 680 | factory(label) 681 | }, validate: true) 682 | } 683 | 684 | /// `bootstrap` is a one-time configuration function which globally selects the desired logging backend 685 | /// implementation. 686 | /// 687 | /// - Warning: 688 | /// `bootstrap` can be called at maximum once in any given program, calling it more than once will 689 | /// lead to undefined behavior, most likely a crash. 690 | /// 691 | /// - parameters: 692 | /// - metadataProvider: The `MetadataProvider` used to inject runtime-generated metadata from the execution context. 693 | /// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`. 694 | public static func bootstrap(_ factory: @escaping (String, Logger.MetadataProvider?) -> LogHandler, 695 | metadataProvider: Logger.MetadataProvider?) { 696 | self._metadataProviderFactory.replaceMetadataProvider(metadataProvider, validate: true) 697 | self._factory.replaceFactory(factory, validate: true) 698 | } 699 | 700 | // for our testing we want to allow multiple bootstrapping 701 | internal static func bootstrapInternal(_ factory: @escaping (String) -> LogHandler) { 702 | self._metadataProviderFactory.replaceMetadataProvider(nil, validate: false) 703 | self._factory.replaceFactory({ label, _ in 704 | factory(label) 705 | }, validate: false) 706 | } 707 | 708 | // for our testing we want to allow multiple bootstrapping 709 | internal static func bootstrapInternal(_ factory: @escaping (String, Logger.MetadataProvider?) -> LogHandler, 710 | metadataProvider: Logger.MetadataProvider?) { 711 | self._metadataProviderFactory.replaceMetadataProvider(metadataProvider, validate: false) 712 | self._factory.replaceFactory(factory, validate: false) 713 | } 714 | 715 | fileprivate static var factory: (String, Logger.MetadataProvider?) -> LogHandler { 716 | return { label, metadataProvider in 717 | self._factory.underlying(label, metadataProvider) 718 | } 719 | } 720 | 721 | /// System wide ``Logger/MetadataProvider`` that was configured during the logging system's `bootstrap`. 722 | /// 723 | /// When creating a ``Logger`` using the plain ``Logger/init(label:)`` initializer, this metadata provider 724 | /// will be provided to it. 725 | /// 726 | /// When using custom log handler factories, make sure to provide the bootstrapped metadata provider to them, 727 | /// or the metadata will not be filled in automatically using the provider on log-sites. While using a custom 728 | /// factory to avoid using the bootstrapped metadata provider may sometimes be useful, usually it will lead to 729 | /// un-expected behavior, so make sure to always propagate it to your handlers. 730 | public static var metadataProvider: Logger.MetadataProvider? { 731 | return self._metadataProviderFactory.metadataProvider 732 | } 733 | 734 | #if DEBUG 735 | /// Used to warn only once about a specific ``LogHandler`` type when it does not support ``Logger/MetadataProvider``, 736 | /// but an attempt was made to set a metadata provider on such handler. In order to avoid flooding the system with 737 | /// warnings such warning is only emitted in debug mode, and even then at-most once for a handler type. 738 | internal static func warnOnceLogHandlerNotSupportedMetadataProvider(_ type: Handler.Type) -> Bool { 739 | self._warnOnceBox.warnOnceLogHandlerNotSupportedMetadataProvider(type: type) 740 | } 741 | #endif 742 | 743 | private final class FactoryBox { 744 | private let lock = ReadWriteLock() 745 | fileprivate var _underlying: (_ label: String, _ provider: Logger.MetadataProvider?) -> LogHandler 746 | private var initialized = false 747 | 748 | init(_ underlying: @escaping (String, Logger.MetadataProvider?) -> LogHandler) { 749 | self._underlying = underlying 750 | } 751 | 752 | func replaceFactory(_ factory: @escaping (String, Logger.MetadataProvider?) -> LogHandler, validate: Bool) { 753 | self.lock.withWriterLock { 754 | precondition(!validate || !self.initialized, "logging system can only be initialized once per process.") 755 | self._underlying = factory 756 | self.initialized = true 757 | } 758 | } 759 | 760 | var underlying: (String, Logger.MetadataProvider?) -> LogHandler { 761 | return self.lock.withReaderLock { 762 | return self._underlying 763 | } 764 | } 765 | } 766 | 767 | private final class MetadataProviderBox { 768 | private let lock = ReadWriteLock() 769 | 770 | internal var _underlying: Logger.MetadataProvider? 771 | private var initialized = false 772 | 773 | init(_ underlying: Logger.MetadataProvider?) { 774 | self._underlying = underlying 775 | } 776 | 777 | func replaceMetadataProvider(_ metadataProvider: Logger.MetadataProvider?, validate: Bool) { 778 | self.lock.withWriterLock { 779 | precondition(!validate || !self.initialized, "logging system can only be initialized once per process.") 780 | self._underlying = metadataProvider 781 | self.initialized = true 782 | } 783 | } 784 | 785 | var metadataProvider: Logger.MetadataProvider? { 786 | return self.lock.withReaderLock { 787 | return self._underlying 788 | } 789 | } 790 | } 791 | } 792 | 793 | extension Logger { 794 | /// `Metadata` is a typealias for `[String: Logger.MetadataValue]` the type of the metadata storage. 795 | public typealias Metadata = [String: MetadataValue] 796 | 797 | /// A logging metadata value. `Logger.MetadataValue` is string, array, and dictionary literal convertible. 798 | /// 799 | /// `MetadataValue` provides convenient conformances to `ExpressibleByStringInterpolation`, 800 | /// `ExpressibleByStringLiteral`, `ExpressibleByArrayLiteral`, and `ExpressibleByDictionaryLiteral` which means 801 | /// that when constructing `MetadataValue`s you should default to using Swift's usual literals. 802 | /// 803 | /// Examples: 804 | /// - prefer `logger.info("user logged in", metadata: ["user-id": "\(user.id)"])` over 805 | /// `..., metadata: ["user-id": .string(user.id.description)])` 806 | /// - prefer `logger.info("user selected colors", metadata: ["colors": ["\(user.topColor)", "\(user.secondColor)"]])` 807 | /// over `..., metadata: ["colors": .array([.string("\(user.topColor)"), .string("\(user.secondColor)")])` 808 | /// - prefer `logger.info("nested info", metadata: ["nested": ["fave-numbers": ["\(1)", "\(2)", "\(3)"], "foo": "bar"]])` 809 | /// over `..., metadata: ["nested": .dictionary(["fave-numbers": ...])])` 810 | public enum MetadataValue { 811 | /// A metadata value which is a `String`. 812 | /// 813 | /// Because `MetadataValue` implements `ExpressibleByStringInterpolation`, and `ExpressibleByStringLiteral`, 814 | /// you don't need to type `.string(someType.description)` you can use the string interpolation `"\(someType)"`. 815 | case string(String) 816 | 817 | /// A metadata value which is some `CustomStringConvertible`. 818 | #if compiler(>=5.7) 819 | case stringConvertible(CustomStringConvertible & Sendable) 820 | #else 821 | case stringConvertible(CustomStringConvertible) 822 | #endif 823 | /// A metadata value which is a dictionary from `String` to `Logger.MetadataValue`. 824 | /// 825 | /// Because `MetadataValue` implements `ExpressibleByDictionaryLiteral`, you don't need to type 826 | /// `.dictionary(["foo": .string("bar \(buz)")])`, you can just use the more natural `["foo": "bar \(buz)"]`. 827 | case dictionary(Metadata) 828 | 829 | /// A metadata value which is an array of `Logger.MetadataValue`s. 830 | /// 831 | /// Because `MetadataValue` implements `ExpressibleByArrayLiteral`, you don't need to type 832 | /// `.array([.string("foo"), .string("bar \(buz)")])`, you can just use the more natural `["foo", "bar \(buz)"]`. 833 | case array([Metadata.Value]) 834 | } 835 | 836 | /// The log level. 837 | /// 838 | /// Log levels are ordered by their severity, with `.trace` being the least severe and 839 | /// `.critical` being the most severe. 840 | public enum Level: String, Codable, CaseIterable { 841 | /// Appropriate for messages that contain information normally of use only when 842 | /// tracing the execution of a program. 843 | case trace 844 | 845 | /// Appropriate for messages that contain information normally of use only when 846 | /// debugging a program. 847 | case debug 848 | 849 | /// Appropriate for informational messages. 850 | case info 851 | 852 | /// Appropriate for conditions that are not error conditions, but that may require 853 | /// special handling. 854 | case notice 855 | 856 | /// Appropriate for messages that are not error conditions, but more severe than 857 | /// `.notice`. 858 | case warning 859 | 860 | /// Appropriate for error conditions. 861 | case error 862 | 863 | /// Appropriate for critical error conditions that usually require immediate 864 | /// attention. 865 | /// 866 | /// When a `critical` message is logged, the logging backend (`LogHandler`) is free to perform 867 | /// more heavy-weight operations to capture system state (such as capturing stack traces) to facilitate 868 | /// debugging. 869 | case critical 870 | } 871 | 872 | /// Construct a `Logger` given a `label` identifying the creator of the `Logger`. 873 | /// 874 | /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even 875 | /// a datatype. 876 | /// 877 | /// - parameters: 878 | /// - label: An identifier for the creator of a `Logger`. 879 | public init(label: String) { 880 | self.init(label: label, LoggingSystem.factory(label, LoggingSystem.metadataProvider)) 881 | } 882 | 883 | /// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`. 884 | /// 885 | /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even 886 | /// a datatype. 887 | /// 888 | /// This initializer provides an escape hatch in case the global default logging backend implementation (set up 889 | /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger. 890 | /// 891 | /// - parameters: 892 | /// - label: An identifier for the creator of a `Logger`. 893 | /// - factory: A closure creating non-standard `LogHandler`s. 894 | public init(label: String, factory: (String) -> LogHandler) { 895 | self = Logger(label: label, factory(label)) 896 | } 897 | 898 | /// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`. 899 | /// 900 | /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even 901 | /// a datatype. 902 | /// 903 | /// This initializer provides an escape hatch in case the global default logging backend implementation (set up 904 | /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger. 905 | /// 906 | /// - parameters: 907 | /// - label: An identifier for the creator of a `Logger`. 908 | /// - factory: A closure creating non-standard `LogHandler`s. 909 | public init(label: String, factory: (String, Logger.MetadataProvider?) -> LogHandler) { 910 | self = Logger(label: label, factory(label, LoggingSystem.metadataProvider)) 911 | } 912 | 913 | /// Construct a `Logger` given a `label` identifying the creator of the `Logger` and a non-standard ``Logger/MetadataProvider``. 914 | /// 915 | /// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even 916 | /// a datatype. 917 | /// 918 | /// This initializer provides an escape hatch in case the global default logging backend implementation (set up 919 | /// using `LoggingSystem.bootstrap` is not appropriate for this particular logger. 920 | /// 921 | /// - parameters: 922 | /// - label: An identifier for the creator of a `Logger`. 923 | /// - metadataProvider: The custom metadata provider this logger should invoke, 924 | /// instead of the system wide bootstrapped one, when a log statement is about to be emitted. 925 | public init(label: String, metadataProvider: MetadataProvider) { 926 | self = Logger(label: label, factory: { label in 927 | var handler = LoggingSystem.factory(label, metadataProvider) 928 | handler.metadataProvider = metadataProvider 929 | return handler 930 | }) 931 | } 932 | } 933 | 934 | extension Logger.Level { 935 | internal var naturalIntegralValue: Int { 936 | switch self { 937 | case .trace: 938 | return 0 939 | case .debug: 940 | return 1 941 | case .info: 942 | return 2 943 | case .notice: 944 | return 3 945 | case .warning: 946 | return 4 947 | case .error: 948 | return 5 949 | case .critical: 950 | return 6 951 | } 952 | } 953 | } 954 | 955 | extension Logger.Level: Comparable { 956 | public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool { 957 | return lhs.naturalIntegralValue < rhs.naturalIntegralValue 958 | } 959 | } 960 | 961 | // Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for 962 | // https://bugs.swift.org/browse/SR-9687 963 | // Then we could write it as follows and it would work under Swift 5 and not only 4 as it does currently: 964 | // extension Logger.Metadata.Value: Equatable { 965 | extension Logger.MetadataValue: Equatable { 966 | public static func == (lhs: Logger.Metadata.Value, rhs: Logger.Metadata.Value) -> Bool { 967 | switch (lhs, rhs) { 968 | case (.string(let lhs), .string(let rhs)): 969 | return lhs == rhs 970 | case (.stringConvertible(let lhs), .stringConvertible(let rhs)): 971 | return lhs.description == rhs.description 972 | case (.array(let lhs), .array(let rhs)): 973 | return lhs == rhs 974 | case (.dictionary(let lhs), .dictionary(let rhs)): 975 | return lhs == rhs 976 | default: 977 | return false 978 | } 979 | } 980 | } 981 | 982 | /// A pseudo-`LogHandler` that can be used to send messages to multiple other `LogHandler`s. 983 | /// 984 | /// ### Effective Logger.Level 985 | /// 986 | /// When first initialized the multiplex log handlers' log level is automatically set to the minimum of all the 987 | /// passed in log handlers. This ensures that each of the handlers will be able to log at their appropriate level 988 | /// any log events they might be interested in. 989 | /// 990 | /// Example: 991 | /// If log handler `A` is logging at `.debug` level, and log handler `B` is logging at `.info` level, the constructed 992 | /// `MultiplexLogHandler([A, B])`'s effective log level will be set to `.debug`, meaning that debug messages will be 993 | /// handled by this handler, while only logged by the underlying `A` log handler (since `B`'s log level is `.info` 994 | /// and thus it would not actually log that log message). 995 | /// 996 | /// If the log level is _set_ on a `Logger` backed by an `MultiplexLogHandler` the log level will apply to *all* 997 | /// underlying log handlers, allowing a logger to still select at what level it wants to log regardless of if the underlying 998 | /// handler is a multiplex or a normal one. If for some reason one might want to not allow changing a log level of a specific 999 | /// handler passed into the multiplex log handler, this is possible by wrapping it in a handler which ignores any log level changes. 1000 | /// 1001 | /// ### Effective Logger.Metadata 1002 | /// 1003 | /// Since a `MultiplexLogHandler` is a combination of multiple log handlers, the handling of metadata can be non-obvious. 1004 | /// For example, the underlying log handlers may have metadata of their own set before they are used to initialize the multiplex log handler. 1005 | /// 1006 | /// The multiplex log handler acts purely as proxy and does not make any changes to underlying handler metadata other than 1007 | /// proxying writes that users made on a `Logger` instance backed by this handler. 1008 | /// 1009 | /// Setting metadata is always proxied through to _all_ underlying handlers, meaning that if a modification like 1010 | /// `logger[metadataKey: "x"] = "y"` is made, all underlying log handlers that this multiplex handler was initiated with 1011 | /// will observe this change. 1012 | /// 1013 | /// Reading metadata from the multiplex log handler MAY need to pick one of conflicting values if the underlying log handlers 1014 | /// were already initiated with some metadata before passing them into the multiplex handler. The multiplex handler uses 1015 | /// the order in which the handlers were passed in during its initialization as a priority indicator - the first handler's 1016 | /// values are more important than the next handlers values, etc. 1017 | /// 1018 | /// Example: 1019 | /// If the multiplex log handler was initiated with two handlers like this: `MultiplexLogHandler([handler1, handler2])`. 1020 | /// The handlers each have some already set metadata: `handler1` has metadata values for keys `one` and `all`, and `handler2` 1021 | /// has values for keys `two` and `all`. 1022 | /// 1023 | /// A query through the multiplex log handler the key `one` naturally returns `handler1`'s value, and a query for `two` 1024 | /// naturally returns `handler2`'s value. Querying for the key `all` will return `handler1`'s value, as that handler was indicated 1025 | /// "more important" than the second handler. The same rule applies when querying for the `metadata` property of the 1026 | /// multiplex log handler - it constructs `Metadata` uniquing values. 1027 | public struct MultiplexLogHandler: LogHandler { 1028 | private var handlers: [LogHandler] 1029 | private var effectiveLogLevel: Logger.Level 1030 | /// This metadata provider runs after all metadata providers of the multiplexed handlers. 1031 | private var _metadataProvider: Logger.MetadataProvider? 1032 | 1033 | /// Create a `MultiplexLogHandler`. 1034 | /// 1035 | /// - parameters: 1036 | /// - handlers: An array of `LogHandler`s, each of which will receive the log messages sent to this `Logger`. 1037 | /// The array must not be empty. 1038 | public init(_ handlers: [LogHandler]) { 1039 | assert(!handlers.isEmpty, "MultiplexLogHandler.handlers MUST NOT be empty") 1040 | self.handlers = handlers 1041 | self.effectiveLogLevel = handlers.map { $0.logLevel }.min() ?? .trace 1042 | } 1043 | 1044 | public init(_ handlers: [LogHandler], metadataProvider: Logger.MetadataProvider?) { 1045 | assert(!handlers.isEmpty, "MultiplexLogHandler.handlers MUST NOT be empty") 1046 | self.handlers = handlers 1047 | self.effectiveLogLevel = handlers.map { $0.logLevel }.min() ?? .trace 1048 | self._metadataProvider = metadataProvider 1049 | } 1050 | 1051 | public var logLevel: Logger.Level { 1052 | get { 1053 | return self.effectiveLogLevel 1054 | } 1055 | set { 1056 | self.mutatingForEachHandler { $0.logLevel = newValue } 1057 | self.effectiveLogLevel = newValue 1058 | } 1059 | } 1060 | 1061 | public var metadataProvider: Logger.MetadataProvider? { 1062 | get { 1063 | if self.handlers.count == 1 { 1064 | if let innerHandler = self.handlers.first?.metadataProvider { 1065 | if let multiplexHandlerProvider = self._metadataProvider { 1066 | return .multiplex([innerHandler, multiplexHandlerProvider]) 1067 | } else { 1068 | return innerHandler 1069 | } 1070 | } else if let multiplexHandlerProvider = self._metadataProvider { 1071 | return multiplexHandlerProvider 1072 | } else { 1073 | return nil 1074 | } 1075 | } else { 1076 | var providers: [Logger.MetadataProvider] = [] 1077 | let additionalMetadataProviderCount = (self._metadataProvider != nil ? 1 : 0) 1078 | providers.reserveCapacity(self.handlers.count + additionalMetadataProviderCount) 1079 | for handler in self.handlers { 1080 | if let provider = handler.metadataProvider { 1081 | providers.append(provider) 1082 | } 1083 | } 1084 | if let multiplexHandlerProvider = self._metadataProvider { 1085 | providers.append(multiplexHandlerProvider) 1086 | } 1087 | guard !providers.isEmpty else { 1088 | return nil 1089 | } 1090 | return .multiplex(providers) 1091 | } 1092 | } 1093 | set { 1094 | self.mutatingForEachHandler { $0.metadataProvider = newValue } 1095 | } 1096 | } 1097 | 1098 | public func log(level: Logger.Level, 1099 | message: Logger.Message, 1100 | metadata: Logger.Metadata?, 1101 | source: String, 1102 | file: String, 1103 | function: String, 1104 | line: UInt) { 1105 | for handler in self.handlers where handler.logLevel <= level { 1106 | handler.log(level: level, message: message, metadata: metadata, source: source, file: file, function: function, line: line) 1107 | } 1108 | } 1109 | 1110 | public var metadata: Logger.Metadata { 1111 | get { 1112 | var effective: Logger.Metadata = [:] 1113 | // as a rough estimate we assume that the underlying handlers have a similar metadata count, 1114 | // and we use the first one's current count to estimate how big of a dictionary we need to allocate: 1115 | effective.reserveCapacity(self.handlers.first!.metadata.count) // !-safe, we always have at least one handler 1116 | 1117 | for handler in self.handlers { 1118 | effective.merge(handler.metadata, uniquingKeysWith: { _, handlerMetadata in handlerMetadata }) 1119 | if let provider = handler.metadataProvider { 1120 | effective.merge(provider.get(), uniquingKeysWith: { _, provided in provided }) 1121 | } 1122 | } 1123 | if let provider = self._metadataProvider { 1124 | effective.merge(provider.get(), uniquingKeysWith: { _, provided in provided }) 1125 | } 1126 | 1127 | return effective 1128 | } 1129 | set { 1130 | self.mutatingForEachHandler { $0.metadata = newValue } 1131 | } 1132 | } 1133 | 1134 | public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? { 1135 | get { 1136 | for handler in self.handlers { 1137 | if let value = handler[metadataKey: metadataKey] { 1138 | return value 1139 | } 1140 | } 1141 | return nil 1142 | } 1143 | set { 1144 | self.mutatingForEachHandler { $0[metadataKey: metadataKey] = newValue } 1145 | } 1146 | } 1147 | 1148 | private mutating func mutatingForEachHandler(_ mutator: (inout LogHandler) -> Void) { 1149 | for index in self.handlers.indices { 1150 | mutator(&self.handlers[index]) 1151 | } 1152 | } 1153 | } 1154 | 1155 | #if canImport(WASILibc) || os(Android) 1156 | internal typealias CFilePointer = OpaquePointer 1157 | #else 1158 | internal typealias CFilePointer = UnsafeMutablePointer 1159 | #endif 1160 | 1161 | /// A wrapper to facilitate `print`-ing to stderr and stdio that 1162 | /// ensures access to the underlying `FILE` is locked to prevent 1163 | /// cross-thread interleaving of output. 1164 | internal struct StdioOutputStream: TextOutputStream { 1165 | internal let file: CFilePointer 1166 | internal let flushMode: FlushMode 1167 | 1168 | internal func write(_ string: String) { 1169 | self.contiguousUTF8(string).withContiguousStorageIfAvailable { utf8Bytes in 1170 | #if os(Windows) 1171 | _lock_file(self.file) 1172 | #elseif canImport(WASILibc) 1173 | // no file locking on WASI 1174 | #else 1175 | flockfile(self.file) 1176 | #endif 1177 | defer { 1178 | #if os(Windows) 1179 | _unlock_file(self.file) 1180 | #elseif canImport(WASILibc) 1181 | // no file locking on WASI 1182 | #else 1183 | funlockfile(self.file) 1184 | #endif 1185 | } 1186 | _ = fwrite(utf8Bytes.baseAddress!, 1, utf8Bytes.count, self.file) 1187 | if case .always = self.flushMode { 1188 | self.flush() 1189 | } 1190 | }! 1191 | } 1192 | 1193 | /// Flush the underlying stream. 1194 | /// This has no effect when using the `.always` flush mode, which is the default 1195 | internal func flush() { 1196 | _ = fflush(self.file) 1197 | } 1198 | 1199 | internal func contiguousUTF8(_ string: String) -> String.UTF8View { 1200 | var contiguousString = string 1201 | #if compiler(>=5.1) 1202 | contiguousString.makeContiguousUTF8() 1203 | #else 1204 | contiguousString = string + "" 1205 | #endif 1206 | return contiguousString.utf8 1207 | } 1208 | 1209 | internal static let stderr = StdioOutputStream(file: systemStderr, flushMode: .always) 1210 | internal static let stdout = StdioOutputStream(file: systemStdout, flushMode: .always) 1211 | 1212 | /// Defines the flushing strategy for the underlying stream. 1213 | internal enum FlushMode { 1214 | case undefined 1215 | case always 1216 | } 1217 | } 1218 | 1219 | // Prevent name clashes 1220 | #if canImport(Darwin) 1221 | let systemStderr = Darwin.stderr 1222 | let systemStdout = Darwin.stdout 1223 | #elseif os(Windows) 1224 | let systemStderr = CRT.stderr 1225 | let systemStdout = CRT.stdout 1226 | #elseif canImport(Glibc) 1227 | let systemStderr = Glibc.stderr! 1228 | let systemStdout = Glibc.stdout! 1229 | #elseif canImport(Musl) 1230 | let systemStderr = Musl.stderr! 1231 | let systemStdout = Musl.stdout! 1232 | #elseif canImport(WASILibc) 1233 | let systemStderr = WASILibc.stderr! 1234 | let systemStdout = WASILibc.stdout! 1235 | #else 1236 | #error("Unsupported runtime") 1237 | #endif 1238 | 1239 | /// `StreamLogHandler` is a simple implementation of `LogHandler` for directing 1240 | /// `Logger` output to either `stderr` or `stdout` via the factory methods. 1241 | /// 1242 | /// Metadata is merged in the following order: 1243 | /// 1. Metadata set on the log handler itself is used as the base metadata. 1244 | /// 2. The handler's ``metadataProvider`` is invoked, overriding any existing keys. 1245 | /// 3. The per-log-statement metadata is merged, overriding any previously set keys. 1246 | public struct StreamLogHandler: LogHandler { 1247 | #if compiler(>=5.6) 1248 | internal typealias _SendableTextOutputStream = TextOutputStream & Sendable 1249 | #else 1250 | internal typealias _SendableTextOutputStream = TextOutputStream 1251 | #endif 1252 | 1253 | /// Factory that makes a `StreamLogHandler` to directs its output to `stdout` 1254 | public static func standardOutput(label: String) -> StreamLogHandler { 1255 | return StreamLogHandler(label: label, stream: StdioOutputStream.stdout, metadataProvider: LoggingSystem.metadataProvider) 1256 | } 1257 | 1258 | /// Factory that makes a `StreamLogHandler` that directs its output to `stdout` 1259 | public static func standardOutput(label: String, metadataProvider: Logger.MetadataProvider?) -> StreamLogHandler { 1260 | return StreamLogHandler(label: label, stream: StdioOutputStream.stdout, metadataProvider: metadataProvider) 1261 | } 1262 | 1263 | /// Factory that makes a `StreamLogHandler` that directs its output to `stderr` 1264 | public static func standardError(label: String) -> StreamLogHandler { 1265 | return StreamLogHandler(label: label, stream: StdioOutputStream.stderr, metadataProvider: LoggingSystem.metadataProvider) 1266 | } 1267 | 1268 | /// Factory that makes a `StreamLogHandler` that direct its output to `stderr` 1269 | public static func standardError(label: String, metadataProvider: Logger.MetadataProvider?) -> StreamLogHandler { 1270 | return StreamLogHandler(label: label, stream: StdioOutputStream.stderr, metadataProvider: metadataProvider) 1271 | } 1272 | 1273 | private let stream: _SendableTextOutputStream 1274 | private let label: String 1275 | 1276 | public var logLevel: Logger.Level = .info 1277 | 1278 | public var metadataProvider: Logger.MetadataProvider? 1279 | 1280 | private var prettyMetadata: String? 1281 | public var metadata = Logger.Metadata() { 1282 | didSet { 1283 | self.prettyMetadata = self.prettify(self.metadata) 1284 | } 1285 | } 1286 | 1287 | public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 1288 | get { 1289 | return self.metadata[metadataKey] 1290 | } 1291 | set { 1292 | self.metadata[metadataKey] = newValue 1293 | } 1294 | } 1295 | 1296 | // internal for testing only 1297 | internal init(label: String, stream: _SendableTextOutputStream) { 1298 | self.init(label: label, stream: stream, metadataProvider: LoggingSystem.metadataProvider) 1299 | } 1300 | 1301 | // internal for testing only 1302 | internal init(label: String, stream: _SendableTextOutputStream, metadataProvider: Logger.MetadataProvider?) { 1303 | self.label = label 1304 | self.stream = stream 1305 | self.metadataProvider = metadataProvider 1306 | } 1307 | 1308 | public func log(level: Logger.Level, 1309 | message: Logger.Message, 1310 | metadata explicitMetadata: Logger.Metadata?, 1311 | source: String, 1312 | file: String, 1313 | function: String, 1314 | line: UInt) { 1315 | let effectiveMetadata = StreamLogHandler.prepareMetadata(base: self.metadata, provider: self.metadataProvider, explicit: explicitMetadata) 1316 | 1317 | let prettyMetadata: String? 1318 | if let effectiveMetadata = effectiveMetadata { 1319 | prettyMetadata = self.prettify(effectiveMetadata) 1320 | } else { 1321 | prettyMetadata = self.prettyMetadata 1322 | } 1323 | 1324 | var stream = self.stream 1325 | stream.write("\(self.timestamp()) \(level) \(self.label) :\(prettyMetadata.map { " \($0)" } ?? "") [\(source)] \(message)\n") 1326 | } 1327 | 1328 | internal static func prepareMetadata(base: Logger.Metadata, provider: Logger.MetadataProvider?, explicit: Logger.Metadata?) -> Logger.Metadata? { 1329 | var metadata = base 1330 | 1331 | let provided = provider?.get() ?? [:] 1332 | 1333 | guard !provided.isEmpty || !((explicit ?? [:]).isEmpty) else { 1334 | // all per-log-statement values are empty 1335 | return nil 1336 | } 1337 | 1338 | if !provided.isEmpty { 1339 | metadata.merge(provided, uniquingKeysWith: { _, provided in provided }) 1340 | } 1341 | 1342 | if let explicit = explicit, !explicit.isEmpty { 1343 | metadata.merge(explicit, uniquingKeysWith: { _, explicit in explicit }) 1344 | } 1345 | 1346 | return metadata 1347 | } 1348 | 1349 | private func prettify(_ metadata: Logger.Metadata) -> String? { 1350 | if metadata.isEmpty { 1351 | return nil 1352 | } else { 1353 | return metadata.lazy.sorted(by: { $0.key < $1.key }).map { "\($0)=\($1)" }.joined(separator: " ") 1354 | } 1355 | } 1356 | 1357 | private func timestamp() -> String { 1358 | var buffer = [Int8](repeating: 0, count: 255) 1359 | #if os(Windows) 1360 | var timestamp = __time64_t() 1361 | _ = _time64(×tamp) 1362 | 1363 | var localTime = tm() 1364 | _ = _localtime64_s(&localTime, ×tamp) 1365 | 1366 | _ = strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", &localTime) 1367 | #else 1368 | var timestamp = time(nil) 1369 | guard let localTime = localtime(×tamp) else { 1370 | return "" 1371 | } 1372 | strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime) 1373 | #endif 1374 | return buffer.withUnsafeBufferPointer { 1375 | $0.withMemoryRebound(to: CChar.self) { 1376 | String(cString: $0.baseAddress!) 1377 | } 1378 | } 1379 | } 1380 | } 1381 | 1382 | /// No operation LogHandler, used when no logging is required 1383 | public struct SwiftLogNoOpLogHandler: LogHandler { 1384 | public init() {} 1385 | 1386 | public init(_: String) {} 1387 | 1388 | @inlinable public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) {} 1389 | 1390 | @inlinable public subscript(metadataKey _: String) -> Logger.Metadata.Value? { 1391 | get { 1392 | return nil 1393 | } 1394 | set {} 1395 | } 1396 | 1397 | @inlinable public var metadata: Logger.Metadata { 1398 | get { 1399 | return [:] 1400 | } 1401 | set {} 1402 | } 1403 | 1404 | @inlinable public var logLevel: Logger.Level { 1405 | get { 1406 | return .critical 1407 | } 1408 | set {} 1409 | } 1410 | } 1411 | 1412 | extension Logger { 1413 | @inlinable 1414 | internal static func currentModule(filePath: String = #file) -> String { 1415 | let utf8All = filePath.utf8 1416 | return filePath.utf8.lastIndex(of: UInt8(ascii: "/")).flatMap { lastSlash -> Substring? in 1417 | utf8All[.. Substring in 1418 | filePath[utf8All.index(after: secondLastSlash) ..< lastSlash] 1419 | } 1420 | }.map { 1421 | String($0) 1422 | } ?? "n/a" 1423 | } 1424 | 1425 | #if compiler(>=5.3) 1426 | @inlinable 1427 | internal static func currentModule(fileID: String = #fileID) -> String { 1428 | let utf8All = fileID.utf8 1429 | if let slashIndex = utf8All.firstIndex(of: UInt8(ascii: "/")) { 1430 | return String(fileID[..(type: Handler.Type) -> Bool { 1499 | self.lock.withLock { 1500 | let id = ObjectIdentifier(type) 1501 | if warnOnceLogHandlerNotSupportedMetadataProviderPerType[id] ?? false { 1502 | return false // don't warn, it was already warned about 1503 | } else { 1504 | warnOnceLogHandlerNotSupportedMetadataProviderPerType[id] = true 1505 | return true // warn about this handler type, it is the first time we encountered it 1506 | } 1507 | } 1508 | } 1509 | } 1510 | #endif 1511 | 1512 | // MARK: - Sendable support helpers 1513 | 1514 | #if compiler(>=5.7.0) 1515 | extension Logger.MetadataValue: Sendable {} // on 5.7 `stringConvertible`'s value marked as Sendable; but if a value not conforming to Sendable is passed there, a warning is emitted. We are okay with warnings, but on 5.6 for the same situation an error is emitted (!) 1516 | #elseif compiler(>=5.6) 1517 | extension Logger.MetadataValue: @unchecked Sendable {} // sadly, On 5.6 a missing Sendable conformance causes an 'error' (specifically this is about `stringConvertible`'s value) 1518 | #endif 1519 | 1520 | #if compiler(>=5.6) 1521 | extension Logger: Sendable {} 1522 | extension Logger.Level: Sendable {} 1523 | #endif 1524 | --------------------------------------------------------------------------------