Initial commit

This commit is contained in:
oscarz
2024-08-12 10:49:20 +08:00
parent 3002510aaf
commit 00fd0adf89
331 changed files with 53210 additions and 130 deletions

202
Pods/Logging/LICENSE.txt generated Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

276
Pods/Logging/README.md generated Normal file
View File

@ -0,0 +1,276 @@
# SwiftLog
A Logging API package for Swift. Version `1.0.0` requires Swift 5.0 but there is a version `0.x.y` series available for Swift 4 to ease your transition towards Swift 5. If you intend to use or support SwiftLog for Swift 4, please check the [paragraph](#help-i-need-swift-4) at the end of the document.
First things first: This is the beginning of a community-driven open-source project actively seeking contributions, be it code, documentation, or ideas. Apart from contributing to `SwiftLog` itself, there's another huge gap at the moment: `SwiftLog` is an _API package_ which tries to establish a common API the ecosystem can use. To make logging really work for real-world workloads, we need `SwiftLog`-compatible _logging backends_ which then either persist the log messages in files, render them in nicer colors on the terminal, or send them over to Splunk or ELK.
What `SwiftLog` provides today can be found in the [API docs][api-docs].
## Getting started
If you have a server-side Swift application, or maybe a cross-platform (for example Linux & macOS) app/library, and you would like to log, we think targeting this logging API package is a great idea. Below you'll find all you need to know to get started.
#### Adding the dependency
`SwiftLog` is designed for Swift 5, the `1.0.0` release requires Swift 5 (however we will soon tag a `0.x` version that will work with Swift 4 for the transition period). To depend on the logging API package, you need to declare your dependency in your `Package.swift`:
```swift
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
```
and to your application/library target, add `"Logging"` to your `dependencies`, e.g. like this:
```swift
.target(name: "BestExampleApp", dependencies: ["Logging"]),
```
#### Let's log
```swift
// 1) let's import the logging API package
import Logging
// 2) we need to create a logger, the label works similarly to a DispatchQueue label
let logger = Logger(label: "com.example.BestExampleApp.main")
// 3) we're now ready to use it
logger.info("Hello World!")
```
#### Output
```
2019-03-13T15:46:38+0000 info: Hello World!
```
#### Default `Logger` behavior
`SwiftLog` provides for very basic console logging out-of-the-box by way of `StreamLogHandler`. It is possible to switch the default output to `stderr` like so:
```swift
LoggingSystem.bootstrap(StreamLogHandler.standardError)
```
`StreamLogHandler` is primarily a convenience only and does not provide any substantial customization. Library maintainers who aim to build their own logging backends for integration and consumption should implement the `LogHandler` protocol directly as laid out in [the "On the implementation of a logging backend" section](#on-the-implementation-of-a-logging-backend-a-loghandler).
For further information, please check the [API documentation][api-docs].
<a name="backends"></a>
#### Selecting a logging backend implementation (applications only)
As the API has just launched, not many implementations exist yet. If you are interested in implementing one see the "Implementation considerations" section below explaining how to do so. List of existing SwiftLog API compatible libraries:
| Repository | Handler Description|
| ----------- | ----------- |
| [IBM-Swift/HeliumLogger](https://github.com/IBM-Swift/HeliumLogger) |a logging backend widely used in the Kitura ecosystem |
| [ianpartridge/swift-log-**syslog**](https://github.com/ianpartridge/swift-log-syslog) | a [syslog](https://en.wikipedia.org/wiki/Syslog) backend|
| [Adorkable/swift-log-**format-and-pipe**](https://github.com/Adorkable/swift-log-format-and-pipe) | a backend that allows customization of the output format and the resulting destination |
| [chrisaljoudi/swift-log-**oslog**](https://github.com/chrisaljoudi/swift-log-oslog) | an OSLog [Unified Logging](https://developer.apple.com/documentation/os/logging) backend for use on Apple platforms. **Important Note:** we recommend using os_log directly as decribed [here](https://developer.apple.com/documentation/os/logging). Using os_log through swift-log using this backend will be less efficient and will also prevent specifying the privacy of the message. The backend always uses `%{public}@` as the format string and eagerly converts all string interpolations to strings. This has two drawbacks: 1. the static components of the string interpolation would be eagerly copied by the unified logging system, which will result in loss of performance. 2. It makes all messages public, which changes the default privacy policy of os_log, and doesn't allow specifying fine-grained privacy of sections of the message. In a separate on-going work, Swift APIs for os_log are being improved and made to align closely with swift-log APIs. References: [Unifying Logging Levels](https://forums.swift.org/t/custom-string-interpolation-and-compile-time-interpretation-applied-to-logging/18799), [Making os_log accept string interpolations using compile-time interpretation](https://forums.swift.org/t/logging-levels-for-swifts-server-side-logging-apis-and-new-os-log-apis/20365). |
| [Brainfinance/StackdriverLogging](https://github.com/Brainfinance/StackdriverLogging) | a structured JSON logging backend for use on Google Cloud Platform with the [Stackdriver logging agent](https://cloud.google.com/logging/docs/agent) |
| [vapor/console-kit](https://github.com/vapor/console-kit/) | print log messages to a terminal with stylized ([ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code)) output |
| [neallester/swift-log-testing](https://github.com/neallester/swift-log-testing) | provides access to log messages for use in assertions (within test targets) |
| [wlisac/swift-log-slack](https://github.com/wlisac/swift-log-slack) | a logging backend that sends critical log messages to Slack |
| [NSHipster/swift-log-github-actions](https://github.com/NSHipster/swift-log-github-actions) | a logging backend that translates logging messages into [workflow commands for GitHub Actions](https://help.github.com/en/actions/reference/workflow-commands-for-github-actions). |
| [stevapple/swift-log-telegram](https://github.com/stevapple/swift-log-telegram) | a logging backend that sends log messages to any Telegram chat (Inspired by and forked from [wlisac/swift-log-slack](https://github.com/wlisac/swift-log-slack)) |
| [jagreenwood/swift-log-datadog](https://github.com/jagreenwood/swift-log-datadog) | a logging backend which sends log messages to the [Datadog](https://www.datadoghq.com/log-management/) log management service |
| Your library? | [Get in touch!](https://forums.swift.org/c/server) |
## What is an API package?
Glad you asked. We believe that for the Swift on Server ecosystem, it's crucial to have a logging API that can be adopted by anybody so a multitude of libraries from different parties can all log to a shared destination. More concretely this means that we believe all the log messages from all libraries end up in the same file, database, Elastic Stack/Splunk instance, or whatever you may choose.
In the real-world however, there are so many opinions over how exactly a logging system should behave, what a log message should be formatted like, and where/how it should be persisted. We think it's not feasible to wait for one logging package to support everything that a specific deployment needs whilst still being easy enough to use and remain performant. That's why we decided to cut the problem in half:
1. a logging API
2. a logging backend implementation
This package only provides the logging API itself and therefore `SwiftLog` is a 'logging API package'. `SwiftLog` (using `LoggingSystem.bootstrap`) can be configured to choose any compatible logging backend implementation. This way packages can adopt the API and the _application_ can choose any compatible logging backend implementation without requiring any changes from any of the libraries.
Just for completeness sake: This API package does actually include an overly simplistic and non-configurable logging backend implementation which simply writes all log messages to `stdout`. The reason to include this overly simplistic logging backend implementation is to improve the first-time usage experience. Let's assume you start a project and try out `SwiftLog` for the first time, it's just a whole lot better to see something you logged appear on `stdout` in a simplistic format rather than nothing happening at all. For any real-world application, we advise configuring another logging backend implementation that logs in the style you like.
## The core concepts
### Loggers
`Logger`s are used to emit log messages and therefore the most important type in `SwiftLog`, so their use should be as simple as possible. Most commonly, they are used to emit log messages in a certain log level. For example:
```swift
// logging an informational message
logger.info("Hello World!")
// ouch, something went wrong
logger.error("Houston, we have a problem: \(problem)")
```
### Log levels
The following log levels are supported:
- `trace`
- `debug`
- `info`
- `notice`
- `warning`
- `error`
- `critical`
The log level of a given logger can be changed, but the change will only affect the specific logger you changed it on. You could say the `Logger` is a _value type_ regarding the log level.
### Logging metadata
Logging metadata is metadata that can be attached to loggers to add information that is crucial when debugging a problem. In servers, the usual example is attaching a request UUID to a logger that will then be present on all log messages logged with that logger. Example:
```swift
var logger = logger
logger[metadataKey: "request-uuid"] = "\(UUID())"
logger.info("hello world")
```
will print
```
2019-03-13T18:30:02+0000 info: request-uuid=F8633013-3DD8-481C-9256-B296E43443ED hello world
```
with the default logging backend implementation that ships with `SwiftLog`. Needless to say, the format is fully defined by the logging backend you choose.
## On the implementation of a logging backend (a `LogHandler`)
Note: If you don't want to implement a custom logging backend, everything in this section is probably not very relevant, so please feel free to skip.
To become a compatible logging backend that all `SwiftLog` consumers can use, you need to do two things: 1) Implement a type (usually a `struct`) that implements `LogHandler`, a protocol provided by `SwiftLog` and 2) instruct `SwiftLog` to use your logging backend implementation.
A `LogHandler` or logging backend implementation is anything that conforms to the following protocol
```swift
public protocol LogHandler {
func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt)
subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set }
var metadata: Logger.Metadata { get set }
var logLevel: Logger.Level { get set }
}
```
Instructing `SwiftLog` to use your logging backend as the one the whole application (including all libraries) should use is very simple:
LoggingSystem.bootstrap(MyLogHandler.init)
### Implementation considerations
`LogHandler`s control most parts of the logging system:
#### Under control of a `LogHandler`
##### Configuration
`LogHandler`s control the two crucial pieces of `Logger` configuration, namely:
- log level (`logger.logLevel` property)
- logging metadata (`logger[metadataKey:]` and `logger.metadata`)
For the system to work, however, it is important that `LogHandler` treat the configuration as _value types_. This means that `LogHandler`s should be `struct`s and a change in log level or logging metadata should only affect the very `LogHandler` it was changed on.
However, in special cases, it is acceptable that a `LogHandler` provides some global log level override that may affect all `LogHandler`s created.
##### Emitting
- emitting the log message itself
### Not under control of `LogHandler`s
`LogHandler`s do not control if a message should be logged or not. `Logger` will only invoke the `log` function of a `LogHandler` if `Logger` determines that a log message should be emitted given the configured log level.
## Source vs Label
A `Logger` carries an (immutable) `label` and each log message carries a `source` parameter (since SwiftLog 1.3.0). The `Logger`'s label
identifies the creator of the `Logger`. If you are using structured logging by preserving metadata across multiple modules, the `Logger`'s
`label` is not a good way to identify where a log message originated from as it identifies the creator of a `Logger` which is often passed
around between libraries to preserve metadata and the like.
If you want to filter all log messages originating from a certain subsystem, filter by `source` which defaults to the module that is emitting the
log message.
## SwiftLog for Swift 4
<a name="help-i-need-swift-4"></a>
First of, SwiftLog 1.0.x and SwiftLog 0.0.x are mostly compatible so don't be afraid. In fact, SwiftLog 0.0.0 is the same source code as SwiftLog 1.0.0 with a few changes made to make it Swift 4 compatible.
### How can I use SwiftLog 0 library or application?
If you have a application or a library that needs to be compatible with both Swift 4 and 5, then we recommend using the following in your `Package.swift`:
```swift
.package(url: "https://github.com/apple/swift-log.git", Version("0.0.0") ..< Version("2.0.0")),
```
This will instruct SwiftPM to allow any SwiftLog 0 and any SwiftLog 1 version. This is an unusual form because usually packages don't support multiple major versions of a package. Because SwiftLog 0 and 1 are mostly compatible however, this should not be a real issue and will enable everybody to get the best. If compiled with a Swift 4 compiler, this will be a SwiftLog 0 version but if compiled with a Swift 5 compiler everybody will get the best experience and performance delivered by SwiftLog 1.
In most cases, there is only one thing you need to remember: Always use _string literals_ and _string interpolations_ in the log methods and don't rely on the fact that SwiftLog 0 also allows `String`.
Good:
```swift
logger.info("hello world")
```
Bad:
```swift
let message = "hello world"
logger.info(message)
```
If you have a `String` that you received from elsewhere, please use
```swift
logger.info("\(stringIAlreadyHave)")
```
For more details, have a look in the next section.
### What are the differences between SwiftLog 1 and 0?
- SwiftLog 0 does not use `@inlinable`.
- Apart from accepting `Logger.Message` for the message, SwiftLog 0 has a `String` overload.
- In SwiftLog 0, `Logger.Message` is not `ExpressibleByStringLiteral` or `ExpressibleByStringInterpolation`.
- In SwiftLog 0, `Logger.MetadataValue` is not `ExpressibleByStringLiteral` or `ExpressibleByStringInterpolation`.
#### Why these differences?
##### @inlinable
Swift 4.0 & 4.1 don't support `@inlinable`, so SwiftLog 0 can't use them.
##### Logger.Message
Because all Swift 4 versions don't have a (non-deprecated) mechanism for a type to be `ExpressibleByStringInterpolation` we couldn't make `Logger.Message` expressible by string literals. Unfortunately, the most basic form of our logging API is `logger.info("Hello \(world)")`. For this to work however, `"Hello \(world)"` needs to be accepted and because we can't make `Logger.Message` `ExpressibleByStringInterpolation` we added an overload for all the logging methods to also accept `String`. In most cases, you won't even notice that with SwiftLog 0 you're creating a `String` (which is then transformed to a `Logger.Message`) and with SwiftLog 1 you're creating a `Logger.Message` directly. That is because both `String` and `Logger.Message` will accept all forms of string literals and string interpolations.
Unfortunately, there is code that will make this seemingly small difference visible. If you write
```swift
let message = "Hello world"
logger.info(message)
```
then this will only work in SwiftLog 0 and not in SwiftLog 1. Why? Because SwiftLog 1 will want a `Logger.Message` but `let message = "Hello world"` will make `message` to be of type `String` and in SwiftLog 1, the logging methods don't accept `String`s.
So if you intend to be compatible with SwiftLog 0 and 1 at the same time, please make sure to always use a _string literal_ or a _string interpolation_ inside of the logging methods.
In the case that you already have a `String` handy that you want to log, don't worry at all, just use
```swift
let message = "Hello world"
logger.info("\(message)")
```
and again, you will be okay with SwiftLog 0 and 1.
## Design
This logging API was designed with the contributors to the Swift on Server community and approved by the [SSWG (Swift Server Work Group)](https://swift.org/server/) to the 'sandbox level' of the SSWG's [incubation process](https://github.com/swift-server/sswg/blob/master/process/incubation.md).
- [pitch](https://forums.swift.org/t/logging/16027), [discussion](https://forums.swift.org/t/discussion-server-logging-api/18834), [feedback](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375)
- [log levels](https://forums.swift.org/t/logging-levels-for-swifts-server-side-logging-apis-and-new-os-log-apis/20365)
[api-docs]: https://apple.github.io/swift-log/docs/current/Logging/Structs/Logger.html

257
Pods/Logging/Sources/Logging/Locks.swift generated Normal file
View File

@ -0,0 +1,257 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Windows)
import WinSDK
#else
import Glibc
#endif
/// A threading lock based on `libpthread` instead of `libdispatch`.
///
/// This object provides a lock on top of a single `pthread_mutex_t`. This kind
/// of lock is safe to use with `libpthread`-based threading models, such as the
/// one used by NIO. On Windows, the lock is based on the substantially similar
/// `SRWLOCK` type.
internal final class Lock {
#if os(Windows)
fileprivate let mutex: UnsafeMutablePointer<SRWLOCK> =
UnsafeMutablePointer.allocate(capacity: 1)
#else
fileprivate let mutex: UnsafeMutablePointer<pthread_mutex_t> =
UnsafeMutablePointer.allocate(capacity: 1)
#endif
/// Create a new lock.
public init() {
#if os(Windows)
InitializeSRWLock(self.mutex)
#else
var attr = pthread_mutexattr_t()
pthread_mutexattr_init(&attr)
pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK))
let err = pthread_mutex_init(self.mutex, &attr)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
deinit {
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
let err = pthread_mutex_destroy(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
self.mutex.deallocate()
}
/// Acquire the lock.
///
/// Whenever possible, consider using `withLock` instead of this method and
/// `unlock`, to simplify lock handling.
public func lock() {
#if os(Windows)
AcquireSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_lock(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
/// Release the lock.
///
/// Whenever possible, consider using `withLock` instead of this method and
/// `lock`, to simplify lock handling.
public func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(self.mutex)
#else
let err = pthread_mutex_unlock(self.mutex)
precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)")
#endif
}
}
extension Lock {
/// Acquire the lock for the duration of the given block.
///
/// This convenience method should be preferred to `lock` and `unlock` in
/// most situations, as it ensures that the lock will be released regardless
/// of how `body` exits.
///
/// - Parameter body: The block to execute while holding the lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withLock<T>(_ body: () throws -> T) rethrows -> T {
self.lock()
defer {
self.unlock()
}
return try body()
}
// specialise Void return (for performance)
@inlinable
internal func withLockVoid(_ body: () throws -> Void) rethrows {
try self.withLock(body)
}
}
/// A reader/writer threading lock based on `libpthread` instead of `libdispatch`.
///
/// This object provides a lock on top of a single `pthread_rwlock_t`. This kind
/// of lock is safe to use with `libpthread`-based threading models, such as the
/// one used by NIO. On Windows, the lock is based on the substantially similar
/// `SRWLOCK` type.
internal final class ReadWriteLock {
#if os(Windows)
fileprivate let rwlock: UnsafeMutablePointer<SRWLOCK> =
UnsafeMutablePointer.allocate(capacity: 1)
fileprivate var shared: Bool = true
#else
fileprivate let rwlock: UnsafeMutablePointer<pthread_rwlock_t> =
UnsafeMutablePointer.allocate(capacity: 1)
#endif
/// Create a new lock.
public init() {
#if os(Windows)
InitializeSRWLock(self.rwlock)
#else
let err = pthread_rwlock_init(self.rwlock, nil)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
deinit {
#if os(Windows)
// SRWLOCK does not need to be free'd
#else
let err = pthread_rwlock_destroy(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
self.rwlock.deallocate()
}
/// Acquire a reader lock.
///
/// Whenever possible, consider using `withReaderLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockRead() {
#if os(Windows)
AcquireSRWLockShared(self.rwlock)
self.shared = true
#else
let err = pthread_rwlock_rdlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
/// Acquire a writer lock.
///
/// Whenever possible, consider using `withWriterLock` instead of this
/// method and `unlock`, to simplify lock handling.
public func lockWrite() {
#if os(Windows)
AcquireSRWLockExclusive(self.rwlock)
self.shared = false
#else
let err = pthread_rwlock_wrlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
/// Release the lock.
///
/// Whenever possible, consider using `withReaderLock` and `withWriterLock`
/// instead of this method and `lockRead` and `lockWrite`, to simplify lock
/// handling.
public func unlock() {
#if os(Windows)
if self.shared {
ReleaseSRWLockShared(self.rwlock)
} else {
ReleaseSRWLockExclusive(self.rwlock)
}
#else
let err = pthread_rwlock_unlock(self.rwlock)
precondition(err == 0, "\(#function) failed in pthread_rwlock with error \(err)")
#endif
}
}
extension ReadWriteLock {
/// Acquire the reader lock for the duration of the given block.
///
/// This convenience method should be preferred to `lockRead` and `unlock`
/// in most situations, as it ensures that the lock will be released
/// regardless of how `body` exits.
///
/// - Parameter body: The block to execute while holding the reader lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withReaderLock<T>(_ body: () throws -> T) rethrows -> T {
self.lockRead()
defer {
self.unlock()
}
return try body()
}
/// Acquire the writer lock for the duration of the given block.
///
/// This convenience method should be preferred to `lockWrite` and `unlock`
/// in most situations, as it ensures that the lock will be released
/// regardless of how `body` exits.
///
/// - Parameter body: The block to execute while holding the writer lock.
/// - Returns: The value returned by the block.
@inlinable
internal func withWriterLock<T>(_ body: () throws -> T) rethrows -> T {
self.lockWrite()
defer {
self.unlock()
}
return try body()
}
// specialise Void return (for performance)
@inlinable
internal func withReaderLockVoid(_ body: () throws -> Void) rethrows {
try self.withReaderLock(body)
}
// specialise Void return (for performance)
@inlinable
internal func withWriterLockVoid(_ body: () throws -> Void) rethrows {
try self.withWriterLock(body)
}
}

View File

@ -0,0 +1,188 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A `LogHandler` is an implementation of a logging backend.
///
/// This type is an implementation detail and should not normally be used, unless implementing your own logging backend.
/// To use the SwiftLog API, please refer to the documentation of `Logger`.
///
/// # Implementation requirements
///
/// To implement your own `LogHandler` you should respect a few requirements that are necessary so applications work
/// as expected regardless of the selected `LogHandler` implementation.
///
/// - The `LogHandler` must be a `struct`.
/// - The metadata and `logLevel` properties must be implemented so that setting them on a `Logger` does not affect
/// other `Logger`s.
///
/// ### Treat log level & metadata as values
///
/// When developing your `LogHandler`, please make sure the following test works.
///
/// ```swift
/// LoggingSystem.bootstrap(MyLogHandler.init) // your LogHandler might have a different bootstrapping step
/// var logger1 = Logger(label: "first logger")
/// logger1.logLevel = .debug
/// logger1[metadataKey: "only-on"] = "first"
///
/// var logger2 = logger1
/// logger2.logLevel = .error // this must not override `logger1`'s log level
/// logger2[metadataKey: "only-on"] = "second" // this must not override `logger1`'s metadata
///
/// XCTAssertEqual(.debug, logger1.logLevel)
/// XCTAssertEqual(.error, logger2.logLevel)
/// XCTAssertEqual("first", logger1[metadataKey: "only-on"])
/// XCTAssertEqual("second", logger2[metadataKey: "only-on"])
/// ```
///
/// ### Special cases
///
/// In certain special cases, the log level behaving like a value on `Logger` might not be what you want. For example,
/// you might want to set the log level across _all_ `Logger`s to `.debug` when say a signal (eg. `SIGUSR1`) is received
/// to be able to debug special failures in production. This special case is acceptable but we urge you to create a
/// solution specific to your `LogHandler` implementation to achieve that. Please find an example implementation of this
/// behavior below, on reception of the signal you would call
/// `LogHandlerWithGlobalLogLevelOverride.overrideGlobalLogLevel = .debug`, for example.
///
/// ```swift
/// import class Foundation.NSLock
///
/// public struct LogHandlerWithGlobalLogLevelOverride: LogHandler {
/// // the static properties hold the globally overridden log level (if overridden)
/// private static let overrideLock = NSLock()
/// private static var overrideLogLevel: Logger.Level? = nil
///
/// // this holds the log level if not overridden
/// private var _logLevel: Logger.Level = .info
///
/// // metadata storage
/// public var metadata: Logger.Metadata = [:]
///
/// public init(label: String) {
/// // [...]
/// }
///
/// public var logLevel: Logger.Level {
/// // when we get asked for the log level, we check if it was globally overridden or not
/// get {
/// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock()
/// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() }
/// return LogHandlerWithGlobalLogLevelOverride.overrideLogLevel ?? self._logLevel
/// }
/// // we set the log level whenever we're asked (note: this might not have an effect if globally
/// // overridden)
/// set {
/// self._logLevel = newValue
/// }
/// }
///
/// public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?,
/// source: String, file: String, function: String, line: UInt) {
/// // [...]
/// }
///
/// public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
/// get {
/// return self.metadata[metadataKey]
/// }
/// set(newValue) {
/// self.metadata[metadataKey] = newValue
/// }
/// }
///
/// // this is the function to globally override the log level, it is not part of the `LogHandler` protocol
/// public static func overrideGlobalLogLevel(_ logLevel: Logger.Level) {
/// LogHandlerWithGlobalLogLevelOverride.overrideLock.lock()
/// defer { LogHandlerWithGlobalLogLevelOverride.overrideLock.unlock() }
/// LogHandlerWithGlobalLogLevelOverride.overrideLogLevel = logLevel
/// }
/// }
/// ```
///
/// Please note that the above `LogHandler` will still pass the 'log level is a value' test above it iff the global log
/// level has not been overridden. And most importantly it passes the requirement listed above: A change to the log
/// level on one `Logger` should not affect the log level of another `Logger` variable.
public protocol LogHandler {
/// This method is called when a `LogHandler` must emit a log message. There is no need for the `LogHandler` to
/// check if the `level` is above or below the configured `logLevel` as `Logger` already performed this check and
/// determined that a message should be logged.
///
/// - parameters:
/// - level: The log level the message was logged at.
/// - message: The message to log. To obtain a `String` representation call `message.description`.
/// - metadata: The metadata associated to this log message.
/// - source: The source where the log message originated, for example the logging module.
/// - file: The file the log message was emitted from.
/// - function: The function the log line was emitted from.
/// - line: The line the log message was emitted from.
func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt)
/// SwiftLog 1.0 compatibility method. Please do _not_ implement, implement
/// `log(level:message:metadata:source:file:function:line:)` instead.
@available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)")
func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt)
/// Add, remove, or change the logging metadata.
///
/// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`.
///
/// - parameters:
/// - metadataKey: The key for the metadata item
subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set }
/// Get or set the entire metadata storage as a dictionary.
///
/// - note: `LogHandler`s must treat logging metadata as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`.
var metadata: Logger.Metadata { get set }
/// Get or set the configured log level.
///
/// - note: `LogHandler`s must treat the log level as a value type. This means that the change in metadata must
/// only affect this very `LogHandler`. It is acceptable to provide some form of global log level override
/// that means a change in log level on a particular `LogHandler` might not be reflected in any
/// `LogHandler`.
var logLevel: Logger.Level { get set }
}
extension LogHandler {
@available(*, deprecated, message: "You should implement this method instead of using the default implementation")
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
self.log(level: level, message: message, metadata: metadata, file: file, function: function, line: line)
}
@available(*, deprecated, renamed: "log(level:message:metadata:source:file:function:line:)")
public func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, file: String, function: String, line: UInt) {
self.log(level: level,
message: message,
metadata: metadata,
source: Logger.currentModule(filePath: file),
file: file,
function: function,
line: line)
}
}

826
Pods/Logging/Sources/Logging/Logging.swift generated Normal file
View File

@ -0,0 +1,826 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Windows)
import MSVCRT
#else
import Glibc
#endif
/// A `Logger` is the central type in `SwiftLog`. Its central function is to emit log messages using one of the methods
/// corresponding to a log level.
///
/// `Logger`s are value types with respect to the `logLevel` and the `metadata` (as well as the immutable `label`
/// and the selected `LogHandler`). Therefore, `Logger`s are suitable to be passed around between libraries if you want
/// to preserve metadata across libraries.
///
/// The most basic usage of a `Logger` is
///
/// logger.info("Hello World!")
///
public struct Logger {
@usableFromInline
var handler: LogHandler
/// An identifier of the creator of this `Logger`.
public let label: String
internal init(label: String, _ handler: LogHandler) {
self.label = label
self.handler = handler
}
}
extension Logger {
/// Log a message passing the log level as a parameter.
///
/// If the `logLevel` passed to this method is more severe than the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func log(level: Logger.Level,
_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
if self.logLevel <= level {
self.handler.log(level: level,
message: message(),
metadata: metadata(),
source: source() ?? Logger.currentModule(filePath: (file)),
file: file, function: function, line: line)
}
}
/// Add, change, or remove a logging metadata item.
///
/// - note: Logging metadata behaves as a value that means a change to the logging metadata will only affect the
/// very `Logger` it was changed on.
@inlinable
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
return self.handler[metadataKey: metadataKey]
}
set {
self.handler[metadataKey: metadataKey] = newValue
}
}
/// Get or set the log level configured for this `Logger`.
///
/// - note: `Logger`s treat `logLevel` as a value. This means that a change in `logLevel` will only affect this
/// very `Logger`. It it acceptable for logging backends to have some form of global log level override
/// that affects multiple or even all loggers. This means a change in `logLevel` to one `Logger` might in
/// certain cases have no effect.
@inlinable
public var logLevel: Logger.Level {
get {
return self.handler.logLevel
}
set {
self.handler.logLevel = newValue
}
}
}
extension Logger {
/// Log a message passing with the `Logger.Level.trace` log level.
///
/// If `.trace` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func trace(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .trace, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.debug` log level.
///
/// If `.debug` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func debug(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .debug, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.info` log level.
///
/// If `.info` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func info(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .info, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.notice` log level.
///
/// If `.notice` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func notice(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .notice, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.warning` log level.
///
/// If `.warning` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func warning(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .warning, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.error` log level.
///
/// If `.error` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func error(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .error, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
/// Log a message passing with the `Logger.Level.critical` log level.
///
/// `.critical` messages will always be logged.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message.
/// - source: The source this log messages originates to. Currently, it defaults to the folder containing the
/// file that is emitting the log message, which usually is the module.
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func critical(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.log(level: .critical, message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
}
}
/// The `LoggingSystem` is a global facility where the default logging backend implementation (`LogHandler`) can be
/// configured. `LoggingSystem` is set up just once in a given program to set up the desired logging backend
/// implementation.
public enum LoggingSystem {
fileprivate static let lock = ReadWriteLock()
fileprivate static var factory: (String) -> LogHandler = StreamLogHandler.standardOutput
fileprivate static var initialized = false
/// `bootstrap` is a one-time configuration function which globally selects the desired logging backend
/// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
/// lead to undefined behavior, most likely a crash.
///
/// - parameters:
/// - factory: A closure that given a `Logger` identifier, produces an instance of the `LogHandler`.
public static func bootstrap(_ factory: @escaping (String) -> LogHandler) {
self.lock.withWriterLock {
precondition(!self.initialized, "logging system can only be initialized once per process.")
self.factory = factory
self.initialized = true
}
}
// for our testing we want to allow multiple bootstraping
internal static func bootstrapInternal(_ factory: @escaping (String) -> LogHandler) {
self.lock.withWriterLock {
self.factory = factory
}
}
}
extension Logger {
/// `Metadata` is a typealias for `[String: Logger.MetadataValue]` the type of the metadata storage.
public typealias Metadata = [String: MetadataValue]
/// A logging metadata value. `Logger.MetadataValue` is string, array, and dictionary literal convertible.
///
/// `MetadataValue` provides convenient conformances to `ExpressibleByStringInterpolation`,
/// `ExpressibleByStringLiteral`, `ExpressibleByArrayLiteral`, and `ExpressibleByDictionaryLiteral` which means
/// that when constructing `MetadataValue`s you should default to using Swift's usual literals.
///
/// Examples:
/// - prefer `logger.info("user logged in", metadata: ["user-id": "\(user.id)"])` over
/// `..., metadata: ["user-id": .string(user.id.description)])`
/// - prefer `logger.info("user selected colors", metadata: ["colors": ["\(user.topColor)", "\(user.secondColor)"]])`
/// over `..., metadata: ["colors": .array([.string("\(user.topColor)"), .string("\(user.secondColor)")])`
/// - prefer `logger.info("nested info", metadata: ["nested": ["fave-numbers": ["\(1)", "\(2)", "\(3)"], "foo": "bar"]])`
/// over `..., metadata: ["nested": .dictionary(["fave-numbers": ...])])`
public enum MetadataValue {
/// A metadata value which is a `String`.
///
/// Because `MetadataValue` implements `ExpressibleByStringInterpolation`, and `ExpressibleByStringLiteral`,
/// you don't need to type `.string(someType.description)` you can use the string interpolation `"\(someType)"`.
case string(String)
/// A metadata value which is some `CustomStringConvertible`.
case stringConvertible(CustomStringConvertible)
/// A metadata value which is a dictionary from `String` to `Logger.MetadataValue`.
///
/// Because `MetadataValue` implements `ExpressibleByDictionaryLiteral`, you don't need to type
/// `.dictionary(["foo": .string("bar \(buz)")])`, you can just use the more natural `["foo": "bar \(buz)"]`.
case dictionary(Metadata)
/// A metadata value which is an array of `Logger.MetadataValue`s.
///
/// Because `MetadataValue` implements `ExpressibleByArrayLiteral`, you don't need to type
/// `.array([.string("foo"), .string("bar \(buz)")])`, you can just use the more natural `["foo", "bar \(buz)"]`.
case array([Metadata.Value])
}
/// The log level.
///
/// Log levels are ordered by their severity, with `.trace` being the least severe and
/// `.critical` being the most severe.
public enum Level: String, Codable, CaseIterable {
/// Appropriate for messages that contain information normally of use only when
/// tracing the execution of a program.
case trace
/// Appropriate for messages that contain information normally of use only when
/// debugging a program.
case debug
/// Appropriate for informational messages.
case info
/// Appropriate for conditions that are not error conditions, but that may require
/// special handling.
case notice
/// Appropriate for messages that are not error conditions, but more severe than
/// `.notice`.
case warning
/// Appropriate for error conditions.
case error
/// Appropriate for critical error conditions that usually require immediate
/// attention.
///
/// When a `critical` message is logged, the logging backend (`LogHandler`) is free to perform
/// more heavy-weight operations to capture system state (such as capturing stack traces) to facilitate
/// debugging.
case critical
}
/// Construct a `Logger` given a `label` identifying the creator of the `Logger`.
///
/// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
/// a datatype.
///
/// - parameters:
/// - label: An identifier for the creator of a `Logger`.
public init(label: String) {
self = LoggingSystem.lock.withReaderLock { Logger(label: label, LoggingSystem.factory(label)) }
}
/// Construct a `Logger` given a `label` identifying the creator of the `Logger` or a non-standard `LogHandler`.
///
/// The `label` should identify the creator of the `Logger`. This can be an application, a sub-system, or even
/// a datatype.
///
/// This initializer provides an escape hatch in case the global default logging backend implementation (set up
/// using `LoggingSystem.bootstrap` is not appropriate for this particular logger.
///
/// - parameters:
/// - label: An identifier for the creator of a `Logger`.
/// - factory: A closure creating non-standard `LogHandler`s.
public init(label: String, factory: (String) -> LogHandler) {
self = Logger(label: label, factory(label))
}
}
extension Logger.Level {
internal var naturalIntegralValue: Int {
switch self {
case .trace:
return 0
case .debug:
return 1
case .info:
return 2
case .notice:
return 3
case .warning:
return 4
case .error:
return 5
case .critical:
return 6
}
}
}
extension Logger.Level: Comparable {
public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
return lhs.naturalIntegralValue < rhs.naturalIntegralValue
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9687
// Then we could write it as follows and it would work under Swift 5 and not only 4 as it does currently:
// extension Logger.Metadata.Value: Equatable {
extension Logger.MetadataValue: Equatable {
public static func == (lhs: Logger.Metadata.Value, rhs: Logger.Metadata.Value) -> Bool {
switch (lhs, rhs) {
case (.string(let lhs), .string(let rhs)):
return lhs == rhs
case (.stringConvertible(let lhs), .stringConvertible(let rhs)):
return lhs.description == rhs.description
case (.array(let lhs), .array(let rhs)):
return lhs == rhs
case (.dictionary(let lhs), .dictionary(let rhs)):
return lhs == rhs
default:
return false
}
}
}
extension Logger {
/// `Logger.Message` represents a log message's text. It is usually created using string literals.
///
/// Example creating a `Logger.Message`:
///
/// let world: String = "world"
/// let myLogMessage: Logger.Message = "Hello \(world)"
///
/// Most commonly, `Logger.Message`s appear simply as the parameter to a logging method such as:
///
/// logger.info("Hello \(world)")
///
public struct Message: ExpressibleByStringLiteral, Equatable, CustomStringConvertible, ExpressibleByStringInterpolation {
public typealias StringLiteralType = String
private var value: String
public init(stringLiteral value: String) {
self.value = value
}
public var description: String {
return self.value
}
}
}
/// A pseudo-`LogHandler` that can be used to send messages to multiple other `LogHandler`s.
///
/// ### Effective Logger.Level
///
/// When first initialized the multiplex log handlers' log level is automatically set to the minimum of all the
/// passed in log handlers. This ensures that each of the handlers will be able to log at their appropriate level
/// any log events they might be interested in.
///
/// Example:
/// If log handler `A` is logging at `.debug` level, and log handler `B` is logging at `.info` level, the constructed
/// `MultiplexLogHandler([A, B])`'s effective log level will be set to `.debug`, meaning that debug messages will be
/// handled by this handler, while only logged by the underlying `A` log handler (since `B`'s log level is `.info`
/// and thus it would not actually log that log message).
///
/// If the log level is _set_ on a `Logger` backed by an `MultiplexLogHandler` the log level will apply to *all*
/// underlying log handlers, allowing a logger to still select at what level it wants to log regardless of if the underlying
/// 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
/// handler passed into the multiplex log handler, this is possible by wrapping it in a handler which ignores any log level changes.
///
/// ### Effective Logger.Metadata
///
/// Since a `MultiplexLogHandler` is a combination of multiple log handlers, the handling of metadata can be non-obvious.
/// For example, the underlying log handlers may have metadata of their own set before they are used to initialize the multiplex log handler.
///
/// The multiplex log handler acts purely as proxy and does not make any changes to underlying handler metadata other than
/// proxying writes that users made on a `Logger` instance backed by this handler.
///
/// Setting metadata is always proxied through to _all_ underlying handlers, meaning that if a modification like
/// `logger[metadataKey: "x"] = "y"` is made, all underlying log handlers that this multiplex handler was initiated with
/// will observe this change.
///
/// Reading metadata from the multiplex log handler MAY need to pick one of conflicting values if the underlying log handlers
/// were already initiated with some metadata before passing them into the multiplex handler. The multiplex handler uses
/// the order in which the handlers were passed in during its initialization as a priority indicator - the first handler's
/// values are more important than the next handlers values, etc.
///
/// Example:
/// If the multiplex log handler was initiated with two handlers like this: `MultiplexLogHandler([handler1, handler2])`.
/// The handlers each have some already set metadata: `handler1` has metadata values for keys `one` and `all`, and `handler2`
/// has values for keys `two` and `all`.
///
/// A query through the multiplex log handler the key `one` naturally returns `handler1`'s value, and a query for `two`
/// naturally returns `handler2`'s value. Querying for the key `all` will return `handler1`'s value, as that handler was indicated
/// "more important" than the second handler. The same rule applies when querying for the `metadata` property of the
/// multiplex log handler - it constructs `Metadata` uniquing values.
public struct MultiplexLogHandler: LogHandler {
private var handlers: [LogHandler]
private var effectiveLogLevel: Logger.Level
/// Create a `MultiplexLogHandler`.
///
/// - parameters:
/// - handlers: An array of `LogHandler`s, each of which will receive the log messages sent to this `Logger`.
/// The array must not be empty.
public init(_ handlers: [LogHandler]) {
assert(!handlers.isEmpty, "MultiplexLogHandler.handlers MUST NOT be empty")
self.handlers = handlers
self.effectiveLogLevel = handlers.map { $0.logLevel }.min() ?? .trace
}
public var logLevel: Logger.Level {
get {
return self.effectiveLogLevel
}
set {
self.mutatingForEachHandler { $0.logLevel = newValue }
self.effectiveLogLevel = newValue
}
}
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
for handler in self.handlers where handler.logLevel <= level {
handler.log(level: level, message: message, metadata: metadata, source: source, file: file, function: function, line: line)
}
}
public var metadata: Logger.Metadata {
get {
var effectiveMetadata: Logger.Metadata = [:]
// as a rough estimate we assume that the underlying handlers have a similar metadata count,
// and we use the first one's current count to estimate how big of a dictionary we need to allocate:
effectiveMetadata.reserveCapacity(self.handlers.first!.metadata.count) // !-safe, we always have at least one handler
return self.handlers.reduce(into: effectiveMetadata) { effectiveMetadata, handler in
effectiveMetadata.merge(handler.metadata, uniquingKeysWith: { l, _ in l })
}
}
set {
self.mutatingForEachHandler { $0.metadata = newValue }
}
}
public subscript(metadataKey metadataKey: Logger.Metadata.Key) -> Logger.Metadata.Value? {
get {
for handler in self.handlers {
if let value = handler[metadataKey: metadataKey] {
return value
}
}
return nil
}
set {
self.mutatingForEachHandler { $0[metadataKey: metadataKey] = newValue }
}
}
private mutating func mutatingForEachHandler(_ mutator: (inout LogHandler) -> Void) {
for index in self.handlers.indices {
mutator(&self.handlers[index])
}
}
}
/// A wrapper to facilitate `print`-ing to stderr and stdio that
/// ensures access to the underlying `FILE` is locked to prevent
/// cross-thread interleaving of output.
internal struct StdioOutputStream: TextOutputStream {
internal let file: UnsafeMutablePointer<FILE>
internal let flushMode: FlushMode
internal func write(_ string: String) {
string.withCString { ptr in
#if os(Windows)
_lock_file(self.file)
#else
flockfile(self.file)
#endif
defer {
#if os(Windows)
_unlock_file(self.file)
#else
funlockfile(self.file)
#endif
}
_ = fputs(ptr, self.file)
if case .always = self.flushMode {
self.flush()
}
}
}
/// Flush the underlying stream.
/// This has no effect when using the `.always` flush mode, which is the default
internal func flush() {
_ = fflush(self.file)
}
internal static let stderr = StdioOutputStream(file: systemStderr, flushMode: .always)
internal static let stdout = StdioOutputStream(file: systemStdout, flushMode: .always)
/// Defines the flushing strategy for the underlying stream.
internal enum FlushMode {
case undefined
case always
}
}
// Prevent name clashes
#if os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
let systemStderr = Darwin.stderr
let systemStdout = Darwin.stdout
#elseif os(Windows)
let systemStderr = MSVCRT.stderr
let systemStdout = MSVCRT.stdout
#else
let systemStderr = Glibc.stderr!
let systemStdout = Glibc.stdout!
#endif
/// `StreamLogHandler` is a simple implementation of `LogHandler` for directing
/// `Logger` output to either `stderr` or `stdout` via the factory methods.
public struct StreamLogHandler: LogHandler {
/// Factory that makes a `StreamLogHandler` to directs its output to `stdout`
public static func standardOutput(label: String) -> StreamLogHandler {
return StreamLogHandler(label: label, stream: StdioOutputStream.stdout)
}
/// Factory that makes a `StreamLogHandler` to directs its output to `stderr`
public static func standardError(label: String) -> StreamLogHandler {
return StreamLogHandler(label: label, stream: StdioOutputStream.stderr)
}
private let stream: TextOutputStream
private let label: String
public var logLevel: Logger.Level = .info
private var prettyMetadata: String?
public var metadata = Logger.Metadata() {
didSet {
self.prettyMetadata = self.prettify(self.metadata)
}
}
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
return self.metadata[metadataKey]
}
set {
self.metadata[metadataKey] = newValue
}
}
// internal for testing only
internal init(label: String, stream: TextOutputStream) {
self.label = label
self.stream = stream
}
public func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
let prettyMetadata = metadata?.isEmpty ?? true
? self.prettyMetadata
: self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new }))
var stream = self.stream
stream.write("\(self.timestamp()) \(level) \(self.label) :\(prettyMetadata.map { " \($0)" } ?? "") \(message)\n")
}
private func prettify(_ metadata: Logger.Metadata) -> String? {
return !metadata.isEmpty ? metadata.map { "\($0)=\($1)" }.joined(separator: " ") : nil
}
private func timestamp() -> String {
var buffer = [Int8](repeating: 0, count: 255)
var timestamp = time(nil)
let localTime = localtime(&timestamp)
strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime)
return buffer.withUnsafeBufferPointer {
$0.withMemoryRebound(to: CChar.self) {
String(cString: $0.baseAddress!)
}
}
}
}
/// No operation LogHandler, used when no logging is required
public struct SwiftLogNoOpLogHandler: LogHandler {
public init() {}
@inlinable public func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) {}
@inlinable public subscript(metadataKey _: String) -> Logger.Metadata.Value? {
get {
return nil
}
set {}
}
@inlinable public var metadata: Logger.Metadata {
get {
return [:]
}
set {}
}
@inlinable public var logLevel: Logger.Level {
get {
return .critical
}
set {}
}
}
extension Logger {
@inlinable
internal static func currentModule(filePath: String = #file) -> String {
let utf8All = filePath.utf8
return filePath.utf8.lastIndex(of: UInt8(ascii: "/")).flatMap { lastSlash -> Substring? in
utf8All[..<lastSlash].lastIndex(of: UInt8(ascii: "/")).map { secondLastSlash -> Substring in
filePath[utf8All.index(after: secondLastSlash) ..< lastSlash]
}
}.map {
String($0)
} ?? "n/a"
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
self = .string(value)
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: CustomStringConvertible {
public var description: String {
switch self {
case .dictionary(let dict):
return dict.mapValues { $0.description }.description
case .array(let list):
return list.map { $0.description }.description
case .string(let str):
return str
case .stringConvertible(let repr):
return repr.description
}
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9687
extension Logger.MetadataValue: ExpressibleByStringInterpolation {}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByDictionaryLiteral {
public typealias Key = String
public typealias Value = Logger.Metadata.Value
public init(dictionaryLiteral elements: (String, Logger.Metadata.Value)...) {
self = .dictionary(.init(uniqueKeysWithValues: elements))
}
}
// Extension has to be done on explicit type rather than Logger.Metadata.Value as workaround for
// https://bugs.swift.org/browse/SR-9686
extension Logger.MetadataValue: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Logger.Metadata.Value
public init(arrayLiteral elements: Logger.Metadata.Value...) {
self = .array(elements)
}
}