Initial commit
This commit is contained in:
202
Pods/Logging/LICENSE.txt
generated
Normal file
202
Pods/Logging/LICENSE.txt
generated
Normal 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
276
Pods/Logging/README.md
generated
Normal 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
257
Pods/Logging/Sources/Logging/Locks.swift
generated
Normal 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)
|
||||
}
|
||||
}
|
||||
188
Pods/Logging/Sources/Logging/LogHandler.swift
generated
Normal file
188
Pods/Logging/Sources/Logging/LogHandler.swift
generated
Normal 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
826
Pods/Logging/Sources/Logging/Logging.swift
generated
Normal 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(×tamp)
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user