├── .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 | 
2 | 
3 | 
4 | 
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 |
--------------------------------------------------------------------------------