Initial commit
This commit is contained in:
21
Pods/SwiftyBeaver/LICENSE
generated
Normal file
21
Pods/SwiftyBeaver/LICENSE
generated
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Sebastian Kreutzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
27
Pods/SwiftyBeaver/PrivacyInfo.xcprivacy
generated
Normal file
27
Pods/SwiftyBeaver/PrivacyInfo.xcprivacy
generated
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyCollectedDataType</key>
|
||||
<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
|
||||
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||
<array>
|
||||
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
249
Pods/SwiftyBeaver/README.md
generated
Normal file
249
Pods/SwiftyBeaver/README.md
generated
Normal file
@ -0,0 +1,249 @@
|
||||
<p align="center"><b>Colorful</b>, flexible, <b>lightweight</b> logging for Swift 3, Swift 4 & <b>Swift 5</b>.<br/>Great for <b>development & release</b> with support for Console, file & cloud destinations for server-side Swift.</p>
|
||||
|
||||
<p align="center"><a href="https://swift.org" target="_blank"><img src="https://img.shields.io/badge/Language-Swift%203,%204%20&%205-orange.svg" alt="Language Swift 2, 3, 4 & 5"></a> <a href="https://circleci.com/gh/SwiftyBeaver/SwiftyBeaver" target="_blank"><img src="https://circleci.com/gh/SwiftyBeaver/SwiftyBeaver/tree/master.svg?style=shield" alt="CircleCI"/></a><br/><p>
|
||||
|
||||
---
|
||||
|
||||
<br/>
|
||||
|
||||
### During Development: Colored Logging to Xcode Console via OSLog API or Print
|
||||
|
||||
<img width="924" alt="image" src="https://github.com/SwiftyBeaver/SwiftyBeaver/assets/15070906/418a6a70-ced4-4000-91c3-8dc8fc235b7c">
|
||||
|
||||
|
||||
#### In Xcode 15
|
||||
```Swift
|
||||
|
||||
// use Apple's fancy OSLog API:
|
||||
let console = ConsoleDestination()
|
||||
console.logPrintWay = .logger(subsystem: "Main", category: "UI")
|
||||
|
||||
// or use good ol' "print" (which is the default):
|
||||
let console = ConsoleDestination()
|
||||
console.logPrintWay = .print
|
||||
```
|
||||
#### In Xcode 8
|
||||
[Learn more](http://docs.swiftybeaver.com/article/9-log-to-xcode-console) about colored logging to Xcode 8 Console with Swift 3, 4 & 5. For Swift 2.3 [use this Gist](https://gist.github.com/skreutzberger/7c396573796473ed1be2c6d15cafed34). **No need to hack Xcode 8 anymore** to get color. You can even customize the log level word (ATTENTION instead of ERROR maybe?), the general amount of displayed data and if you want to use the 💜s or replace them with something else 😉
|
||||
|
||||
<br/>
|
||||
|
||||
### During Development: Colored Logging to File
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/564725/18608325/b7ecd4c2-7ce6-11e6-829b-7f8f9fe6ef2f.png" width="738">
|
||||
|
||||
[Learn more](http://docs.swiftybeaver.com/article/10-log-to-file) about logging to file which is great for Terminal.app fans or to store logs on disk.
|
||||
|
||||
<br/>
|
||||
|
||||
### Google Cloud & More
|
||||
|
||||
You can fully customize your log format, turn it into JSON, or create your own destinations. For example, our [Google Cloud Destination](https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/Sources/GoogleCloudDestination.swift) is just another customized logging format that adds the powerful functionality of automatic server-side Swift logging when hosted on Google Cloud Platform.
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Installation
|
||||
|
||||
- For **Swift 4 & 5** install the latest SwiftyBeaver version
|
||||
- For **Swift 3** install SwiftyBeaver 1.8.4
|
||||
- For **Swift 2** install SwiftyBeaver 0.7.0
|
||||
|
||||
<br/>
|
||||
|
||||
### Carthage
|
||||
|
||||
You can use [Carthage](https://github.com/Carthage/Carthage) to install SwiftyBeaver by adding that to your Cartfile:
|
||||
|
||||
Swift 4 & 5:
|
||||
|
||||
```Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver"
|
||||
```
|
||||
|
||||
Swift 3:
|
||||
|
||||
```Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver" ~> 1.8.4
|
||||
```
|
||||
|
||||
Swift 2:
|
||||
|
||||
```Swift
|
||||
github "SwiftyBeaver/SwiftyBeaver" ~> 0.7
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
For [Swift Package Manager](https://swift.org/package-manager/) add the following package to your Package.swift file. Just Swift 4 & 5 are supported:
|
||||
|
||||
```Swift
|
||||
.package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git", .upToNextMajor(from: "2.0.0")),
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
### CocoaPods
|
||||
|
||||
To use [CocoaPods](https://cocoapods.org) just add this to your Podfile:
|
||||
|
||||
Swift 4 & 5:
|
||||
|
||||
```Swift
|
||||
pod 'SwiftyBeaver'
|
||||
```
|
||||
|
||||
Swift 3:
|
||||
|
||||
```Ruby
|
||||
target 'MyProject' do
|
||||
use_frameworks!
|
||||
|
||||
# Pods for MyProject
|
||||
pod 'SwiftyBeaver', '~> 1.8.4'
|
||||
end
|
||||
```
|
||||
|
||||
Swift 2:
|
||||
|
||||
```Ruby
|
||||
target 'MyProject' do
|
||||
use_frameworks!
|
||||
|
||||
# Pods for MyProject
|
||||
pod 'SwiftyBeaver', '~> 0.7'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.build_configurations.each do |config|
|
||||
# Configure Pod targets for Xcode 8 with Swift 2.3
|
||||
config.build_settings['SWIFT_VERSION'] = '2.3'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Usage
|
||||
|
||||
Add that near the top of your `AppDelegate.swift` to be able to use SwiftyBeaver in your whole project.
|
||||
|
||||
```Swift
|
||||
import SwiftyBeaver
|
||||
let log = SwiftyBeaver.self
|
||||
|
||||
```
|
||||
|
||||
At the beginning of your `AppDelegate:didFinishLaunchingWithOptions()` add the SwiftyBeaver log destinations (console, file, etc.), optionally adjust the [log format](http://docs.swiftybeaver.com/article/20-custom-format) and then you can already do the following log level calls globally:
|
||||
|
||||
```Swift
|
||||
// add log destinations. at least one is needed!
|
||||
let console = ConsoleDestination() // log to Xcode Console
|
||||
let file = FileDestination() // log to default swiftybeaver.log file
|
||||
|
||||
// use custom format and set console output to short time, log level & message
|
||||
console.format = "$DHH:mm:ss$d $L $M"
|
||||
// or use this for JSON output: console.format = "$J"
|
||||
|
||||
// In Xcode 15, specifying the logging method as .logger to display color, subsystem, and category information in the console.(Relies on the OSLog API)
|
||||
console.logPrintWay = .logger(subsystem: "Main", category: "UI")
|
||||
// If you prefer not to use the OSLog API, you can use print instead.
|
||||
// console.logPrintWay = .print
|
||||
|
||||
// add the destinations to SwiftyBeaver
|
||||
log.addDestination(console)
|
||||
log.addDestination(file)
|
||||
|
||||
// Now let’s log!
|
||||
log.verbose("not so important") // prio 1, VERBOSE in silver
|
||||
log.debug("something to debug") // prio 2, DEBUG in green
|
||||
log.info("a nice information") // prio 3, INFO in blue
|
||||
log.warning("oh no, that won’t be good") // prio 4, WARNING in yellow
|
||||
log.error("ouch, an error did occur!") // prio 5, ERROR in red
|
||||
|
||||
// log anything!
|
||||
log.verbose(123)
|
||||
log.info(-123.45678)
|
||||
log.warning(Date())
|
||||
log.error(["I", "like", "logs!"])
|
||||
log.error(["name": "Mr Beaver", "address": "7 Beaver Lodge"])
|
||||
|
||||
// optionally add context to a log message
|
||||
console.format = "$L: $M $X"
|
||||
log.debug("age", context: 123) // "DEBUG: age 123"
|
||||
log.info("my data", context: [1, "a", 2]) // "INFO: my data [1, \"a\", 2]"
|
||||
|
||||
```
|
||||
|
||||
Alternatively, if you are using SwiftUI, consider using the following setup:
|
||||
|
||||
```swift
|
||||
import SwiftyBeaver
|
||||
let logger = SwiftyBeaver.self
|
||||
|
||||
@main
|
||||
struct yourApp: App {
|
||||
|
||||
init() {
|
||||
let console = ConsoleDestination()
|
||||
logger.addDestination(console)
|
||||
// etc...
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Server-side Swift
|
||||
|
||||
We ❤️ server-side Swift 4 & 5 and SwiftyBeaver support it **out-of-the-box**! Try for yourself and run SwiftyBeaver inside a Ubuntu Docker container. Just install Docker and then go to your project folder on macOS or Ubuntu and type:
|
||||
|
||||
```shell
|
||||
# create docker image, build SwiftyBeaver and run unit tests
|
||||
docker run --rm -it -v $PWD:/app swiftybeaver /bin/bash -c "cd /app ; swift build ; swift test"
|
||||
|
||||
# optionally log into container to run Swift CLI and do more stuff
|
||||
docker run --rm -it --privileged=true -v $PWD:/app swiftybeaver
|
||||
```
|
||||
|
||||
Best: for the popular server-side Swift web framework [Vapor](https://github.com/vapor/vapor) you can use **[our Vapor logging provider](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor)** which makes server logging awesome again 🙌
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## Documentation
|
||||
|
||||
**Getting Started:**
|
||||
|
||||
- [Features](http://docs.swiftybeaver.com/article/7-introduction)
|
||||
- [Installation](http://docs.swiftybeaver.com/article/5-installation)
|
||||
- [Basic Setup](http://docs.swiftybeaver.com/article/6-basic-setup)
|
||||
|
||||
**Logging Destinations:**
|
||||
|
||||
- [Colored Logging to Xcode Console](http://docs.swiftybeaver.com/article/9-log-to-xcode-console)
|
||||
- [Colored Logging to File](http://docs.swiftybeaver.com/article/10-log-to-file)
|
||||
|
||||
**Advanced Topics:**
|
||||
|
||||
- [Custom Format & Context](http://docs.swiftybeaver.com/article/20-custom-format)
|
||||
- [Filters](http://docs.swiftybeaver.com/article/21-filters)
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
## License
|
||||
|
||||
SwiftyBeaver Framework is released under the [MIT License](https://github.com/SwiftyBeaver/SwiftyBeaver/blob/master/LICENSE).
|
||||
165
Pods/SwiftyBeaver/Sources/Base64.swift
generated
Normal file
165
Pods/SwiftyBeaver/Sources/Base64.swift
generated
Normal file
@ -0,0 +1,165 @@
|
||||
//
|
||||
// Base64.swift
|
||||
// SwiftyBeaver (macOS)
|
||||
//
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
#if os(Linux)
|
||||
import Foundation
|
||||
|
||||
struct InvalidBase64: Error {}
|
||||
|
||||
struct Base64 {
|
||||
static func decode(_ string: String) throws -> [UInt8] {
|
||||
return try decode([UInt8](string.utf8))
|
||||
}
|
||||
|
||||
/// Decodes a Base64 encoded String into Data
|
||||
///
|
||||
/// - throws: If the string isn't base64 encoded
|
||||
static func decode(_ string: [UInt8]) throws -> [UInt8] {
|
||||
let lookupTable: [UInt8] = [
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 62, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||
64, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63,
|
||||
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||
]
|
||||
|
||||
let remainder = string.count % 4
|
||||
let length = (string.count - remainder) / 4
|
||||
|
||||
var decoded = [UInt8]()
|
||||
decoded.reserveCapacity(length)
|
||||
|
||||
var index = 0
|
||||
var i0: UInt8 = 0
|
||||
var i1: UInt8 = 0
|
||||
var i2: UInt8 = 0
|
||||
var i3: UInt8 = 0
|
||||
|
||||
while index &+ 4 < string.count {
|
||||
i0 = lookupTable[numericCast(string[index])]
|
||||
i1 = lookupTable[numericCast(string[index &+ 1])]
|
||||
i2 = lookupTable[numericCast(string[index &+ 2])]
|
||||
i3 = lookupTable[numericCast(string[index &+ 3])]
|
||||
|
||||
if i0 > 63 || i1 > 63 || i2 > 63 || i3 > 63 {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
decoded.append(i0 << 2 | i1 >> 4)
|
||||
decoded.append(i1 << 4 | i2 >> 2)
|
||||
decoded.append(i2 << 6 | i3)
|
||||
index += 4
|
||||
}
|
||||
|
||||
if string.count &- index > 1 {
|
||||
i0 = lookupTable[numericCast(string[index])]
|
||||
i1 = lookupTable[numericCast(string[index &+ 1])]
|
||||
|
||||
if i1 > 63 {
|
||||
guard string[index] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
if i2 > 63 {
|
||||
guard string[index &+ 2] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i0 << 2 | i1 >> 4)
|
||||
|
||||
if string.count &- index > 2 {
|
||||
i2 = lookupTable[numericCast(string[index &+ 2])]
|
||||
|
||||
if i2 > 63 {
|
||||
guard string[index &+ 2] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i1 << 4 | i2 >> 2)
|
||||
|
||||
if string.count &- index > 3 {
|
||||
i3 = lookupTable[numericCast(string[index &+ 3])]
|
||||
|
||||
if i3 > 63 {
|
||||
guard string[index &+ 3] == 61 else {
|
||||
throw InvalidBase64()
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
decoded.append(i2 << 6 | i3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
static func encode(_ data: [UInt8]) -> String {
|
||||
let base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
var encoded: String = ""
|
||||
|
||||
func appendCharacterFromBase(_ character: Int) {
|
||||
encoded.append(base64[base64.index(base64.startIndex, offsetBy: character)])
|
||||
}
|
||||
|
||||
func byte(_ index: Int) -> Int {
|
||||
return Int(data[index])
|
||||
}
|
||||
|
||||
let decodedBytes = data.map { Int($0) }
|
||||
|
||||
var i = 0
|
||||
|
||||
while i < decodedBytes.count - 2 {
|
||||
appendCharacterFromBase((byte(i) >> 2) & 0x3F)
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4) | ((byte(i + 1) & 0xF0) >> 4))
|
||||
appendCharacterFromBase(((byte(i + 1) & 0xF) << 2) | ((byte(i + 2) & 0xC0) >> 6))
|
||||
appendCharacterFromBase(byte(i + 2) & 0x3F)
|
||||
i += 3
|
||||
}
|
||||
|
||||
if i < decodedBytes.count {
|
||||
appendCharacterFromBase((byte(i) >> 2) & 0x3F)
|
||||
|
||||
if i == decodedBytes.count - 1 {
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4))
|
||||
encoded.append("=")
|
||||
} else {
|
||||
appendCharacterFromBase(((byte(i) & 0x3) << 4) | ((byte(i + 1) & 0xF0) >> 4))
|
||||
appendCharacterFromBase(((byte(i + 1) & 0xF) << 2))
|
||||
}
|
||||
|
||||
encoded.append("=")
|
||||
}
|
||||
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
529
Pods/SwiftyBeaver/Sources/BaseDestination.swift
generated
Normal file
529
Pods/SwiftyBeaver/Sources/BaseDestination.swift
generated
Normal file
@ -0,0 +1,529 @@
|
||||
//
|
||||
// BaseDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger (Twitter @skreutzb) on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Dispatch
|
||||
|
||||
// store operating system / platform
|
||||
#if os(iOS)
|
||||
let OS = "iOS"
|
||||
#elseif os(OSX)
|
||||
let OS = "OSX"
|
||||
#elseif os(watchOS)
|
||||
let OS = "watchOS"
|
||||
#elseif os(tvOS)
|
||||
let OS = "tvOS"
|
||||
#elseif os(Linux)
|
||||
let OS = "Linux"
|
||||
#elseif os(FreeBSD)
|
||||
let OS = "FreeBSD"
|
||||
#elseif os(Windows)
|
||||
let OS = "Windows"
|
||||
#elseif os(Android)
|
||||
let OS = "Android"
|
||||
#else
|
||||
let OS = "Unknown"
|
||||
#endif
|
||||
|
||||
/// destination which all others inherit from. do not directly use
|
||||
open class BaseDestination: Hashable, Equatable {
|
||||
|
||||
/// output format pattern, see documentation for syntax
|
||||
open var format = "$DHH:mm:ss.SSS$d $C$L$c $N.$F:$l - $M"
|
||||
|
||||
/// runs in own serial background thread for better performance
|
||||
open var asynchronously = true
|
||||
|
||||
/// do not log any message which has a lower level than this one
|
||||
open var minLevel = SwiftyBeaver.Level.verbose
|
||||
|
||||
/// set custom log level words for each level
|
||||
open var levelString = LevelString()
|
||||
|
||||
/// set custom log level colors for each level
|
||||
open var levelColor = LevelColor()
|
||||
|
||||
/// set custom calendar for dateFormatter
|
||||
open var calendar = Calendar.current
|
||||
|
||||
public struct LevelString {
|
||||
public var verbose = "VERBOSE"
|
||||
public var debug = "DEBUG"
|
||||
public var info = "INFO"
|
||||
public var warning = "WARNING"
|
||||
public var error = "ERROR"
|
||||
public var critical = "CRITICAL"
|
||||
public var fault = "FAULT"
|
||||
}
|
||||
|
||||
// For a colored log level word in a logged line
|
||||
// empty on default
|
||||
public struct LevelColor {
|
||||
public var verbose = "" // silver
|
||||
public var debug = "" // green
|
||||
public var info = "" // blue
|
||||
public var warning = "" // yellow
|
||||
public var error = "" // red
|
||||
public var critical = "" // red
|
||||
public var fault = "" // red
|
||||
}
|
||||
|
||||
var reset = ""
|
||||
var escape = ""
|
||||
|
||||
var filters = [FilterType]()
|
||||
let formatter = DateFormatter()
|
||||
let startDate = Date()
|
||||
|
||||
// each destination class must have an own hashValue Int
|
||||
#if swift(>=4.2)
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(defaultHashValue)
|
||||
}
|
||||
#else
|
||||
lazy public var hashValue: Int = self.defaultHashValue
|
||||
#endif
|
||||
|
||||
open var defaultHashValue: Int {return 0}
|
||||
|
||||
// each destination instance must have an own serial queue to ensure serial output
|
||||
// GCD gives it a prioritization between User Initiated and Utility
|
||||
var queue: DispatchQueue? //dispatch_queue_t?
|
||||
var debugPrint = false // set to true to debug the internal filter logic of the class
|
||||
|
||||
public init() {
|
||||
let uuid = NSUUID().uuidString
|
||||
let queueLabel = "swiftybeaver-queue-" + uuid
|
||||
queue = DispatchQueue(label: queueLabel, target: queue)
|
||||
}
|
||||
|
||||
/// send / store the formatted log message to the destination
|
||||
/// returns the formatted log message for processing by inheriting method
|
||||
/// and for unit tests (nil if error)
|
||||
open func send(_ level: SwiftyBeaver.Level, msg: String, thread: String, file: String,
|
||||
function: String, line: Int, context: Any? = nil) -> String? {
|
||||
|
||||
if format.hasPrefix("$J") {
|
||||
return messageToJSON(level, msg: msg, thread: thread,
|
||||
file: file, function: function, line: line, context: context)
|
||||
|
||||
} else {
|
||||
return formatMessage(format, level: level, msg: msg, thread: thread,
|
||||
file: file, function: function, line: line, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
public func execute(synchronously: Bool, block: @escaping () -> Void) {
|
||||
guard let queue = queue else {
|
||||
fatalError("Queue not set")
|
||||
}
|
||||
if synchronously {
|
||||
queue.sync(execute: block)
|
||||
} else {
|
||||
queue.async(execute: block)
|
||||
}
|
||||
}
|
||||
|
||||
public func executeSynchronously<T>(block: @escaping () throws -> T) rethrows -> T {
|
||||
guard let queue = queue else {
|
||||
fatalError("Queue not set")
|
||||
}
|
||||
return try queue.sync(execute: block)
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// MARK: Format
|
||||
////////////////////////////////
|
||||
|
||||
/// returns (padding length value, offset in string after padding info)
|
||||
private func parsePadding(_ text: String) -> (Int, Int) {
|
||||
// look for digits followed by a alpha character
|
||||
var s: String!
|
||||
var sign: Int = 1
|
||||
if text.firstChar == "-" {
|
||||
sign = -1
|
||||
s = String(text.suffix(from: text.index(text.startIndex, offsetBy: 1)))
|
||||
} else {
|
||||
s = text
|
||||
}
|
||||
let numStr = String(s.prefix { $0 >= "0" && $0 <= "9" })
|
||||
if let num = Int(numStr) {
|
||||
return (sign * num, (sign == -1 ? 1 : 0) + numStr.count)
|
||||
} else {
|
||||
return (0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func paddedString(_ text: String, _ toLength: Int, truncating: Bool = false) -> String {
|
||||
if toLength > 0 {
|
||||
// Pad to the left of the string
|
||||
if text.count > toLength {
|
||||
// Hm... better to use suffix or prefix?
|
||||
return truncating ? String(text.suffix(toLength)) : text
|
||||
} else {
|
||||
return "".padding(toLength: toLength - text.count, withPad: " ", startingAt: 0) + text
|
||||
}
|
||||
} else if toLength < 0 {
|
||||
// Pad to the right of the string
|
||||
let maxLength = truncating ? -toLength : max(-toLength, text.count)
|
||||
return text.padding(toLength: maxLength, withPad: " ", startingAt: 0)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the log message based on the format pattern
|
||||
func formatMessage(_ format: String, level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String {
|
||||
|
||||
var text = ""
|
||||
// Prepend a $I for 'ignore' or else the first character is interpreted as a format character
|
||||
// even if the format string did not start with a $.
|
||||
let phrases: [String] = ("$I" + format).components(separatedBy: "$")
|
||||
|
||||
for phrase in phrases where !phrase.isEmpty {
|
||||
let (padding, offset) = parsePadding(phrase)
|
||||
let formatCharIndex = phrase.index(phrase.startIndex, offsetBy: offset)
|
||||
let formatChar = phrase[formatCharIndex]
|
||||
let rangeAfterFormatChar = phrase.index(formatCharIndex, offsetBy: 1)..<phrase.endIndex
|
||||
let remainingPhrase = phrase[rangeAfterFormatChar]
|
||||
|
||||
switch formatChar {
|
||||
case "I": // ignore
|
||||
text += remainingPhrase
|
||||
case "L":
|
||||
text += paddedString(levelWord(level), padding) + remainingPhrase
|
||||
case "M":
|
||||
text += paddedString(msg, padding) + remainingPhrase
|
||||
case "T":
|
||||
text += paddedString(thread, padding) + remainingPhrase
|
||||
case "N":
|
||||
// name of file without suffix
|
||||
text += paddedString(fileNameWithoutSuffix(file), padding) + remainingPhrase
|
||||
case "n":
|
||||
// name of file with suffix
|
||||
text += paddedString(fileNameOfFile(file), padding) + remainingPhrase
|
||||
case "F":
|
||||
text += paddedString(function, padding) + remainingPhrase
|
||||
case "l":
|
||||
text += paddedString(String(line), padding) + remainingPhrase
|
||||
case "D":
|
||||
// start of datetime format
|
||||
#if swift(>=3.2)
|
||||
text += paddedString(formatDate(String(remainingPhrase)), padding)
|
||||
#else
|
||||
text += paddedString(formatDate(remainingPhrase), padding)
|
||||
#endif
|
||||
case "d":
|
||||
text += remainingPhrase
|
||||
case "U":
|
||||
text += paddedString(uptime(), padding) + remainingPhrase
|
||||
case "Z":
|
||||
// start of datetime format in UTC timezone
|
||||
#if swift(>=3.2)
|
||||
text += paddedString(formatDate(String(remainingPhrase), timeZone: "UTC"), padding)
|
||||
#else
|
||||
text += paddedString(formatDate(remainingPhrase, timeZone: "UTC"), padding)
|
||||
#endif
|
||||
case "z":
|
||||
text += remainingPhrase
|
||||
case "C":
|
||||
// color code ("" on default)
|
||||
text += escape + colorForLevel(level) + remainingPhrase
|
||||
case "c":
|
||||
text += reset + remainingPhrase
|
||||
case "X":
|
||||
// add the context
|
||||
if let cx = context {
|
||||
text += paddedString(String(describing: cx).trimmingCharacters(in: .whitespacesAndNewlines), padding) + remainingPhrase
|
||||
} else {
|
||||
text += paddedString("", padding) + remainingPhrase
|
||||
}
|
||||
default:
|
||||
text += phrase
|
||||
}
|
||||
}
|
||||
// right trim only
|
||||
return text.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
|
||||
}
|
||||
|
||||
/// returns the log payload as optional JSON string
|
||||
func messageToJSON(_ level: SwiftyBeaver.Level, msg: String,
|
||||
thread: String, file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
var dict: [String: Any] = [
|
||||
"timestamp": Date().timeIntervalSince1970,
|
||||
"level": level.rawValue,
|
||||
"message": msg,
|
||||
"thread": thread,
|
||||
"file": file,
|
||||
"function": function,
|
||||
"line": line
|
||||
]
|
||||
if let cx = context {
|
||||
dict["context"] = cx
|
||||
}
|
||||
return jsonStringFromDict(dict)
|
||||
}
|
||||
|
||||
/// returns the string of a level
|
||||
func levelWord(_ level: SwiftyBeaver.Level) -> String {
|
||||
|
||||
var str = ""
|
||||
|
||||
switch level {
|
||||
case .verbose:
|
||||
str = levelString.verbose
|
||||
|
||||
case .debug:
|
||||
str = levelString.debug
|
||||
|
||||
case .info:
|
||||
str = levelString.info
|
||||
|
||||
case .warning:
|
||||
str = levelString.warning
|
||||
|
||||
case .error:
|
||||
str = levelString.error
|
||||
|
||||
case .critical:
|
||||
str = levelString.critical
|
||||
|
||||
case .fault:
|
||||
str = levelString.fault
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/// returns color string for level
|
||||
func colorForLevel(_ level: SwiftyBeaver.Level) -> String {
|
||||
var color = ""
|
||||
|
||||
switch level {
|
||||
case .verbose:
|
||||
color = levelColor.verbose
|
||||
|
||||
case .debug:
|
||||
color = levelColor.debug
|
||||
|
||||
case .info:
|
||||
color = levelColor.info
|
||||
|
||||
case .warning:
|
||||
color = levelColor.warning
|
||||
|
||||
case .error:
|
||||
color = levelColor.error
|
||||
|
||||
case .critical:
|
||||
color = levelColor.critical
|
||||
|
||||
case .fault:
|
||||
color = levelColor.fault
|
||||
}
|
||||
return color
|
||||
}
|
||||
|
||||
/// returns the filename of a path
|
||||
func fileNameOfFile(_ file: String) -> String {
|
||||
let fileParts = file.components(separatedBy: "/")
|
||||
if let lastPart = fileParts.last {
|
||||
return lastPart
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// returns the filename without suffix (= file ending) of a path
|
||||
func fileNameWithoutSuffix(_ file: String) -> String {
|
||||
let fileName = fileNameOfFile(file)
|
||||
|
||||
if !fileName.isEmpty {
|
||||
let fileNameParts = fileName.components(separatedBy: ".")
|
||||
if let firstPart = fileNameParts.first {
|
||||
return firstPart
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// returns a formatted date string
|
||||
/// optionally in a given abbreviated timezone like "UTC"
|
||||
func formatDate(_ dateFormat: String, timeZone: String = "") -> String {
|
||||
if !timeZone.isEmpty {
|
||||
formatter.timeZone = TimeZone(abbreviation: timeZone)
|
||||
}
|
||||
formatter.calendar = calendar
|
||||
formatter.dateFormat = dateFormat
|
||||
//let dateStr = formatter.string(from: NSDate() as Date)
|
||||
let dateStr = formatter.string(from: Date())
|
||||
return dateStr
|
||||
}
|
||||
|
||||
/// returns a uptime string
|
||||
func uptime() -> String {
|
||||
let interval = Date().timeIntervalSince(startDate)
|
||||
|
||||
let hours = Int(interval) / 3600
|
||||
let minutes = Int(interval / 60) - Int(hours * 60)
|
||||
let seconds = Int(interval) - (Int(interval / 60) * 60)
|
||||
let milliseconds = Int(interval.truncatingRemainder(dividingBy: 1) * 1000)
|
||||
|
||||
return String(format: "%0.2d:%0.2d:%0.2d.%03d", arguments: [hours, minutes, seconds, milliseconds])
|
||||
}
|
||||
|
||||
/// returns the json-encoded string value
|
||||
/// after it was encoded by jsonStringFromDict
|
||||
func jsonStringValue(_ jsonString: String?, key: String) -> String {
|
||||
guard let str = jsonString else {
|
||||
return ""
|
||||
}
|
||||
|
||||
// remove the leading {"key":" from the json string and the final }
|
||||
let offset = key.length + 5
|
||||
let endIndex = str.index(str.startIndex,
|
||||
offsetBy: str.length - 2)
|
||||
let range = str.index(str.startIndex, offsetBy: offset)..<endIndex
|
||||
#if swift(>=3.2)
|
||||
return String(str[range])
|
||||
#else
|
||||
return str[range]
|
||||
#endif
|
||||
}
|
||||
|
||||
/// turns dict into JSON-encoded string
|
||||
func jsonStringFromDict(_ dict: [String: Any]) -> String? {
|
||||
var jsonString: String?
|
||||
|
||||
// try to create JSON string
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
|
||||
jsonString = String(data: jsonData, encoding: .utf8)
|
||||
} catch {
|
||||
print("SwiftyBeaver could not create JSON from dict.")
|
||||
}
|
||||
return jsonString
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// MARK: Filters
|
||||
////////////////////////////////
|
||||
|
||||
/// Add a filter that determines whether or not a particular message will be logged to this destination
|
||||
public func addFilter(_ filter: FilterType) {
|
||||
filters.append(filter)
|
||||
}
|
||||
|
||||
/// Remove a filter from the list of filters
|
||||
public func removeFilter(_ filter: FilterType) {
|
||||
#if swift(>=5)
|
||||
let index = filters.firstIndex {
|
||||
return ObjectIdentifier($0) == ObjectIdentifier(filter)
|
||||
}
|
||||
#else
|
||||
let index = filters.index {
|
||||
return ObjectIdentifier($0) == ObjectIdentifier(filter)
|
||||
}
|
||||
#endif
|
||||
|
||||
guard let filterIndex = index else {
|
||||
return
|
||||
}
|
||||
|
||||
filters.remove(at: filterIndex)
|
||||
}
|
||||
|
||||
/// Answer whether the destination has any message filters
|
||||
/// returns boolean and is used to decide whether to resolve
|
||||
/// the message before invoking shouldLevelBeLogged
|
||||
func hasMessageFilters() -> Bool {
|
||||
return !getFiltersTargeting(Filter.TargetType.Message(.Equals([], true)),
|
||||
fromFilters: self.filters).isEmpty
|
||||
}
|
||||
|
||||
/// checks if level is at least minLevel or if a minLevel filter for that path does exist
|
||||
/// returns boolean and can be used to decide if a message should be logged or not
|
||||
func shouldLevelBeLogged(_ level: SwiftyBeaver.Level, path: String,
|
||||
function: String, message: String? = nil) -> Bool {
|
||||
|
||||
if filters.isEmpty {
|
||||
if level.rawValue >= minLevel.rawValue {
|
||||
if debugPrint {
|
||||
print("filters are empty and level >= minLevel")
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if debugPrint {
|
||||
print("filters are empty and level < minLevel")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let filterCheckResult = FilterValidator.validate(input: .init(filters: self.filters, level: level, path: path, function: function, message: message))
|
||||
|
||||
// Exclusion filters match if they do NOT meet the filter condition (see Filter.apply(_:) method)
|
||||
switch filterCheckResult[.excluded] {
|
||||
case .some(.someFiltersMatch):
|
||||
// Exclusion filters are present and at least one of them matches the log entry
|
||||
if debugPrint {
|
||||
print("filters are not empty and message was excluded")
|
||||
}
|
||||
return false
|
||||
case .some(.allFiltersMatch), .some(.noFiltersMatchingType), .none: break
|
||||
}
|
||||
|
||||
// If required filters exist, we should validate or invalidate the log if all of them pass or not
|
||||
switch filterCheckResult[.required] {
|
||||
case .some(.allFiltersMatch): return true
|
||||
case .some(.someFiltersMatch): return false
|
||||
case .some(.noFiltersMatchingType), .none: break
|
||||
}
|
||||
|
||||
let checkLogLevel: () -> Bool = {
|
||||
// Check if the log message's level matches or exceeds the minLevel of the destination
|
||||
return level.rawValue >= self.minLevel.rawValue
|
||||
}
|
||||
|
||||
// Non-required filters should only be applied if the log entry matches the filter condition (e.g. path)
|
||||
switch filterCheckResult[.nonRequired] {
|
||||
case .some(.allFiltersMatch): return true
|
||||
case .some(.noFiltersMatchingType), .none: return checkLogLevel()
|
||||
case .some(.someFiltersMatch(let partialMatchData)):
|
||||
if partialMatchData.fullMatchCount > 0 {
|
||||
// The log entry matches at least one filter condition and the destination's log level
|
||||
return true
|
||||
} else if partialMatchData.conditionMatchCount > 0 {
|
||||
// The log entry matches at least one filter condition, but does not match or exceed the destination's log level
|
||||
return false
|
||||
} else {
|
||||
// There is no filter with a matching filter condition. Check the destination's log level
|
||||
return checkLogLevel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFiltersTargeting(_ target: Filter.TargetType, fromFilters: [FilterType]) -> [FilterType] {
|
||||
return fromFilters.filter { filter in
|
||||
return filter.getTarget() == target
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Triggered by main flush() method on each destination. Runs in background thread.
|
||||
Use for destinations that buffer log items, implement this function to flush those
|
||||
buffers to their final destination (web server...)
|
||||
*/
|
||||
func flush() {
|
||||
// no implementation in base destination needed
|
||||
}
|
||||
}
|
||||
|
||||
public func == (lhs: BaseDestination, rhs: BaseDestination) -> Bool {
|
||||
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
|
||||
}
|
||||
130
Pods/SwiftyBeaver/Sources/ConsoleDestination.swift
generated
Normal file
130
Pods/SwiftyBeaver/Sources/ConsoleDestination.swift
generated
Normal file
@ -0,0 +1,130 @@
|
||||
//
|
||||
// ConsoleDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if canImport(OSLog)
|
||||
import OSLog
|
||||
#endif
|
||||
|
||||
open class ConsoleDestination: BaseDestination {
|
||||
public enum LogPrintWay {
|
||||
case logger(subsystem: String, category: String)
|
||||
case nslog
|
||||
case print
|
||||
}
|
||||
|
||||
/// Use this to change the logging method to the console. By default, it is set to .print. You can switch to .logger(subsystem:category:) to utilize the OSLog API.
|
||||
public var logPrintWay: LogPrintWay = .print
|
||||
/// use NSLog instead of print, default is false
|
||||
public var useNSLog = false {
|
||||
didSet {
|
||||
if useNSLog {
|
||||
logPrintWay = .nslog
|
||||
}
|
||||
}
|
||||
}
|
||||
/// uses colors compatible to Terminal instead of Xcode, default is false
|
||||
public var useTerminalColors: Bool = false {
|
||||
didSet {
|
||||
if useTerminalColors {
|
||||
// use Terminal colors
|
||||
reset = "\u{001b}[0m"
|
||||
escape = "\u{001b}[38;5;"
|
||||
levelColor.verbose = "251m" // silver
|
||||
levelColor.debug = "35m" // green
|
||||
levelColor.info = "38m" // blue
|
||||
levelColor.warning = "178m" // yellow
|
||||
levelColor.error = "197m" // red
|
||||
levelColor.critical = "197m" // red
|
||||
levelColor.fault = "197m" // red
|
||||
} else {
|
||||
// use colored Emojis for better visual distinction
|
||||
// of log level for Xcode 8
|
||||
levelColor.verbose = "💜 " // purple
|
||||
levelColor.debug = "💚 " // green
|
||||
levelColor.info = "💙 " // blue
|
||||
levelColor.warning = "💛 " // yellow
|
||||
levelColor.error = "❤️ " // red
|
||||
levelColor.critical = "❤️ " // red
|
||||
levelColor.fault = "❤️ " // red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var defaultHashValue: Int { return 1 }
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
levelColor.verbose = "💜 " // purple
|
||||
levelColor.debug = "💚 " // green
|
||||
levelColor.info = "💙 " // blue
|
||||
levelColor.warning = "💛 " // yellow
|
||||
levelColor.error = "❤️ " // red
|
||||
levelColor.critical = "❤️ " // red
|
||||
levelColor.fault = "❤️ " // red
|
||||
}
|
||||
|
||||
// print to Xcode Console. uses full base class functionality
|
||||
override open func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
let formattedString = super.send(level, msg: msg, thread: thread, file: file, function: function, line: line, context: context)
|
||||
|
||||
if let message = formattedString {
|
||||
#if os(Linux)
|
||||
print(message)
|
||||
#else
|
||||
switch logPrintWay {
|
||||
case let .logger(subsystem, category):
|
||||
_logger(message: message, level: level, subsystem: subsystem, category: category)
|
||||
case .nslog:
|
||||
_nslog(message: message)
|
||||
case .print:
|
||||
_print(message: message)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return formattedString
|
||||
}
|
||||
|
||||
private func _logger(message: String, level: SwiftyBeaver.Level, subsystem: String, category: String) {
|
||||
#if canImport(OSLog)
|
||||
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
|
||||
let logger = Logger(subsystem: subsystem, category: category)
|
||||
switch level {
|
||||
case .verbose:
|
||||
logger.trace("\(message)")
|
||||
case .debug:
|
||||
logger.debug("\(message)")
|
||||
case .info:
|
||||
logger.info("\(message)")
|
||||
case .warning:
|
||||
logger.warning("\(message)")
|
||||
case .error:
|
||||
logger.error("\(message)")
|
||||
case .critical:
|
||||
logger.critical("\(message)")
|
||||
case .fault:
|
||||
logger.fault("\(message)")
|
||||
}
|
||||
} else {
|
||||
_print(message: message)
|
||||
}
|
||||
#else
|
||||
_print(message: message)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func _nslog(message: String) {
|
||||
NSLog("%@", message)
|
||||
}
|
||||
|
||||
private func _print(message: String) {
|
||||
print(message)
|
||||
}
|
||||
}
|
||||
35
Pods/SwiftyBeaver/Sources/Extensions.swift
generated
Normal file
35
Pods/SwiftyBeaver/Sources/Extensions.swift
generated
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Extensions.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 13.12.17.
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
/// cross-Swift compatible characters count
|
||||
var length: Int {
|
||||
return self.count
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible first character
|
||||
var firstChar: Character? {
|
||||
return self.first
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible last character
|
||||
var lastChar: Character? {
|
||||
return self.last
|
||||
}
|
||||
|
||||
/// cross-Swift-compatible index
|
||||
func find(_ char: Character) -> Index? {
|
||||
#if swift(>=5)
|
||||
return self.firstIndex(of: char)
|
||||
#else
|
||||
return self.index(of: char)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
240
Pods/SwiftyBeaver/Sources/FileDestination.swift
generated
Normal file
240
Pods/SwiftyBeaver/Sources/FileDestination.swift
generated
Normal file
@ -0,0 +1,240 @@
|
||||
//
|
||||
// FileDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger on 05.12.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class FileDestination: BaseDestination {
|
||||
|
||||
public var logFileURL: URL?
|
||||
public var syncAfterEachWrite: Bool = false
|
||||
public var colored: Bool = false {
|
||||
didSet {
|
||||
if colored {
|
||||
// bash font color, first value is intensity, second is color
|
||||
// see http://bit.ly/1Otu3Zr & for syntax http://bit.ly/1Tp6Fw9
|
||||
// uses the 256-color table from http://bit.ly/1W1qJuH
|
||||
reset = "\u{001b}[0m"
|
||||
escape = "\u{001b}[38;5;"
|
||||
levelColor.verbose = "251m" // silver
|
||||
levelColor.debug = "35m" // green
|
||||
levelColor.info = "38m" // blue
|
||||
levelColor.warning = "178m" // yellow
|
||||
levelColor.error = "197m" // red
|
||||
} else {
|
||||
reset = ""
|
||||
escape = ""
|
||||
levelColor.verbose = ""
|
||||
levelColor.debug = ""
|
||||
levelColor.info = ""
|
||||
levelColor.warning = ""
|
||||
levelColor.error = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LOGFILE ROTATION
|
||||
// ho many bytes should a logfile have until it is rotated?
|
||||
// default is 5 MB. Just is used if logFileAmount > 1
|
||||
public var logFileMaxSize = (5 * 1024 * 1024)
|
||||
// Number of log files used in rotation, default is 1 which deactivates file rotation
|
||||
public var logFileAmount = 1
|
||||
|
||||
override public var defaultHashValue: Int {return 2}
|
||||
let fileManager = FileManager.default
|
||||
|
||||
|
||||
public init(logFileURL: URL? = nil) {
|
||||
if let logFileURL = logFileURL {
|
||||
self.logFileURL = logFileURL
|
||||
super.init()
|
||||
return
|
||||
}
|
||||
|
||||
// platform-dependent logfile directory default
|
||||
var baseURL: URL?
|
||||
#if os(OSX)
|
||||
if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
// try to use ~/Library/Caches/APP NAME instead of ~/Library/Caches
|
||||
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String {
|
||||
do {
|
||||
if let appURL = baseURL?.appendingPathComponent(appName, isDirectory: true) {
|
||||
try fileManager.createDirectory(at: appURL,
|
||||
withIntermediateDirectories: true, attributes: nil)
|
||||
baseURL = appURL
|
||||
}
|
||||
} catch {
|
||||
print("Warning! Could not create folder /Library/Caches/\(appName)")
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if os(Linux)
|
||||
baseURL = URL(fileURLWithPath: "/var/cache")
|
||||
#else
|
||||
// iOS, watchOS, etc. are using the caches directory
|
||||
if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||
baseURL = url
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if let baseURL = baseURL {
|
||||
self.logFileURL = baseURL.appendingPathComponent("swiftybeaver.log", isDirectory: false)
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
// append to file. uses full base class functionality
|
||||
override open func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
let formattedString = super.send(level, msg: msg, thread: thread, file: file, function: function, line: line, context: context)
|
||||
|
||||
if let str = formattedString {
|
||||
_ = validateSaveFile(str: str)
|
||||
}
|
||||
return formattedString
|
||||
}
|
||||
|
||||
// check if filesize is bigger than wanted and if yes then rotate them
|
||||
func validateSaveFile(str: String) -> Bool {
|
||||
if self.logFileAmount > 1 {
|
||||
guard let url = logFileURL else { return false }
|
||||
let filePath = url.path
|
||||
if FileManager.default.fileExists(atPath: filePath) == true {
|
||||
do {
|
||||
// Get file size
|
||||
let attr = try FileManager.default.attributesOfItem(atPath: filePath)
|
||||
let fileSize = attr[FileAttributeKey.size] as! UInt64
|
||||
// Do file rotation
|
||||
if fileSize > logFileMaxSize {
|
||||
rotateFile(url)
|
||||
}
|
||||
} catch {
|
||||
print("validateSaveFile error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
return saveToFile(str: str)
|
||||
}
|
||||
|
||||
private func rotateFile(_ fileUrl: URL) {
|
||||
let filePath = fileUrl.path
|
||||
let lastIndex = (logFileAmount-1)
|
||||
let firstIndex = 1
|
||||
do {
|
||||
for index in stride(from: lastIndex, through: firstIndex, by: -1) {
|
||||
let oldFile = makeRotatedFileUrl(fileUrl, index: index).path
|
||||
|
||||
if FileManager.default.fileExists(atPath: oldFile) {
|
||||
if index == lastIndex {
|
||||
// Delete the last file
|
||||
try FileManager.default.removeItem(atPath: oldFile)
|
||||
} else {
|
||||
// Move the current file to next index
|
||||
let newFile = makeRotatedFileUrl(fileUrl, index: index + 1).path
|
||||
try FileManager.default.moveItem(atPath: oldFile, toPath: newFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, move the current file
|
||||
let newFile = makeRotatedFileUrl(fileUrl, index: firstIndex).path
|
||||
try FileManager.default.moveItem(atPath: filePath, toPath: newFile)
|
||||
} catch {
|
||||
print("rotateFile error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeRotatedFileUrl(_ fileUrl: URL, index: Int) -> URL {
|
||||
// The index is appended to the file name, to preserve the original extension.
|
||||
fileUrl.deletingPathExtension()
|
||||
.appendingPathExtension("\(index).\(fileUrl.pathExtension)")
|
||||
}
|
||||
|
||||
/// appends a string as line to a file.
|
||||
/// returns boolean about success
|
||||
func saveToFile(str: String) -> Bool {
|
||||
guard let url = logFileURL else { return false }
|
||||
|
||||
let line = str + "\n"
|
||||
guard let data = line.data(using: String.Encoding.utf8) else { return false }
|
||||
|
||||
return write(data: data, to: url)
|
||||
}
|
||||
|
||||
private func write(data: Data, to url: URL) -> Bool {
|
||||
|
||||
#if os(Linux)
|
||||
return true
|
||||
#else
|
||||
var success = false
|
||||
let coordinator = NSFileCoordinator(filePresenter: nil)
|
||||
var error: NSError?
|
||||
coordinator.coordinate(writingItemAt: url, error: &error) { url in
|
||||
do {
|
||||
if fileManager.fileExists(atPath: url.path) == false {
|
||||
|
||||
let directoryURL = url.deletingLastPathComponent()
|
||||
if fileManager.fileExists(atPath: directoryURL.path) == false {
|
||||
try fileManager.createDirectory(
|
||||
at: directoryURL,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
}
|
||||
fileManager.createFile(atPath: url.path, contents: nil)
|
||||
|
||||
#if os(iOS) || os(watchOS)
|
||||
if #available(iOS 10.0, watchOS 3.0, *) {
|
||||
var attributes = try fileManager.attributesOfItem(atPath: url.path)
|
||||
attributes[FileAttributeKey.protectionKey] = FileProtectionType.none
|
||||
try fileManager.setAttributes(attributes, ofItemAtPath: url.path)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
let fileHandle = try FileHandle(forWritingTo: url)
|
||||
fileHandle.seekToEndOfFile()
|
||||
if #available(iOS 13.4, watchOS 6.2, tvOS 13.4, macOS 10.15.4, *) {
|
||||
try fileHandle.write(contentsOf: data)
|
||||
} else {
|
||||
fileHandle.write(data)
|
||||
}
|
||||
if syncAfterEachWrite {
|
||||
fileHandle.synchronizeFile()
|
||||
}
|
||||
fileHandle.closeFile()
|
||||
success = true
|
||||
} catch {
|
||||
print("SwiftyBeaver File Destination could not write to file \(url).")
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
print("Failed writing file with error: \(String(describing: error))")
|
||||
return false
|
||||
}
|
||||
|
||||
return success
|
||||
#endif
|
||||
}
|
||||
|
||||
/// deletes log file.
|
||||
/// returns true if file was removed or does not exist, false otherwise
|
||||
public func deleteLogFile() -> Bool {
|
||||
guard let url = logFileURL, fileManager.fileExists(atPath: url.path) == true else { return true }
|
||||
do {
|
||||
try fileManager.removeItem(at: url)
|
||||
return true
|
||||
} catch {
|
||||
print("SwiftyBeaver File Destination could not remove file \(url).")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
283
Pods/SwiftyBeaver/Sources/Filter.swift
generated
Normal file
283
Pods/SwiftyBeaver/Sources/Filter.swift
generated
Normal file
@ -0,0 +1,283 @@
|
||||
//
|
||||
// Filter.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Jeff Roberts on 5/31/16.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// FilterType is a protocol that describes something that determines
|
||||
/// whether or not a message gets logged. A filter answers a Bool when it
|
||||
/// is applied to a value. If the filter passes, it shall return true,
|
||||
/// false otherwise.
|
||||
///
|
||||
/// A filter must contain a target, which identifies what it filters against
|
||||
/// A filter can be required meaning that all required filters against a specific
|
||||
/// target must pass in order for the message to be logged.
|
||||
public protocol FilterType : AnyObject {
|
||||
func apply(_ value: String?) -> Bool
|
||||
func getTarget() -> Filter.TargetType
|
||||
func isRequired() -> Bool
|
||||
func isExcluded() -> Bool
|
||||
func reachedMinLevel(_ level: SwiftyBeaver.Level) -> Bool
|
||||
}
|
||||
|
||||
/// Filters is syntactic sugar used to easily construct filters
|
||||
public class Filters {
|
||||
public static let Path = PathFilterFactory.self
|
||||
public static let Function = FunctionFilterFactory.self
|
||||
public static let Message = MessageFilterFactory.self
|
||||
}
|
||||
|
||||
/// Filter is an abstract base class for other filters
|
||||
public class Filter {
|
||||
public enum TargetType {
|
||||
case Path(Filter.ComparisonType)
|
||||
case Function(Filter.ComparisonType)
|
||||
case Message(Filter.ComparisonType)
|
||||
}
|
||||
|
||||
public enum ComparisonType {
|
||||
case StartsWith([String], Bool)
|
||||
case Contains([String], Bool)
|
||||
case Excludes([String], Bool)
|
||||
case EndsWith([String], Bool)
|
||||
case Equals([String], Bool)
|
||||
case Custom((String) -> Bool)
|
||||
}
|
||||
|
||||
let targetType: Filter.TargetType
|
||||
let required: Bool
|
||||
let minLevel: SwiftyBeaver.Level
|
||||
|
||||
public init(_ target: Filter.TargetType, required: Bool, minLevel: SwiftyBeaver.Level) {
|
||||
self.targetType = target
|
||||
self.required = required
|
||||
self.minLevel = minLevel
|
||||
}
|
||||
|
||||
public func getTarget() -> Filter.TargetType {
|
||||
return self.targetType
|
||||
}
|
||||
|
||||
public func isRequired() -> Bool {
|
||||
return self.required
|
||||
}
|
||||
|
||||
public func isExcluded() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/// returns true of set minLevel is >= as given level
|
||||
public func reachedMinLevel(_ level: SwiftyBeaver.Level) -> Bool {
|
||||
//print("checking if given level \(level) >= \(minLevel)")
|
||||
return level.rawValue >= minLevel.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
/// CompareFilter is a FilterType that can filter based upon whether a target
|
||||
/// starts with, contains or ends with a specific string. CompareFilters can be
|
||||
/// case sensitive.
|
||||
public class CompareFilter: Filter, FilterType {
|
||||
|
||||
private var filterComparisonType: Filter.ComparisonType?
|
||||
|
||||
override public init(_ target: Filter.TargetType, required: Bool, minLevel: SwiftyBeaver.Level) {
|
||||
super.init(target, required: required, minLevel: minLevel)
|
||||
|
||||
let comparisonType: Filter.ComparisonType?
|
||||
switch self.getTarget() {
|
||||
case let .Function(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
case let .Path(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
case let .Message(comparison):
|
||||
comparisonType = comparison
|
||||
|
||||
/*default:
|
||||
comparisonType = nil*/
|
||||
}
|
||||
self.filterComparisonType = comparisonType
|
||||
}
|
||||
|
||||
public func apply(_ value: String?) -> Bool {
|
||||
guard let value = value else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let filterComparisonType = self.filterComparisonType else {
|
||||
return false
|
||||
}
|
||||
|
||||
let matches: Bool
|
||||
switch filterComparisonType {
|
||||
case let .Contains(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.contains(string) :
|
||||
value.lowercased().contains(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .Excludes(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? !value.contains(string) :
|
||||
!value.lowercased().contains(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .StartsWith(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.hasPrefix(string) :
|
||||
value.lowercased().hasPrefix(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .EndsWith(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value.hasSuffix(string) :
|
||||
value.lowercased().hasSuffix(string.lowercased())
|
||||
}.isEmpty
|
||||
|
||||
case let .Equals(strings, caseSensitive):
|
||||
matches = !strings.filter { string in
|
||||
return caseSensitive ? value == string :
|
||||
value.lowercased() == string.lowercased()
|
||||
}.isEmpty
|
||||
case let .Custom(predicate):
|
||||
matches = predicate(value)
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
override public func isExcluded() -> Bool {
|
||||
guard let filterComparisonType = self.filterComparisonType else { return false }
|
||||
|
||||
switch filterComparisonType {
|
||||
case .Excludes:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a function comparison filter
|
||||
public class FunctionFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Function(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Function(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a message comparison filter
|
||||
public class MessageFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Message(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Message(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// Syntactic sugar for creating a path comparison filter
|
||||
public class PathFilterFactory {
|
||||
public static func startsWith(_ prefixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.StartsWith(prefixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func contains(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Contains(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func excludes(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Excludes(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func endsWith(_ suffixes: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.EndsWith(suffixes, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func equals(_ strings: String..., caseSensitive: Bool = false,
|
||||
required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose) -> FilterType {
|
||||
return CompareFilter(.Path(.Equals(strings, caseSensitive)), required: required, minLevel: minLevel)
|
||||
}
|
||||
|
||||
public static func custom(required: Bool = false, minLevel: SwiftyBeaver.Level = .verbose, filterPredicate: @escaping (String) -> Bool) -> FilterType {
|
||||
return CompareFilter(.Path(.Custom(filterPredicate)), required: required, minLevel: minLevel)
|
||||
}
|
||||
}
|
||||
|
||||
extension Filter.TargetType: Equatable {
|
||||
}
|
||||
|
||||
// The == does not compare associated values for each enum. Instead == evaluates to true
|
||||
// if both enums are the same "types", ignoring the associated values of each enum
|
||||
public func == (lhs: Filter.TargetType, rhs: Filter.TargetType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
|
||||
case (.Path, .Path):
|
||||
return true
|
||||
|
||||
case (.Function, .Function):
|
||||
return true
|
||||
|
||||
case (.Message, .Message):
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
129
Pods/SwiftyBeaver/Sources/FilterValidator.swift
generated
Normal file
129
Pods/SwiftyBeaver/Sources/FilterValidator.swift
generated
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// FilterValidator.swift
|
||||
// SwiftyBeaver (iOS)
|
||||
//
|
||||
// Created by Felix Lisczyk on 07.07.19.
|
||||
// Copyright © 2019 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// FilterValidator is a utility class used by BaseDestination.
|
||||
/// It encapsulates the filtering logic for excluded, required
|
||||
/// and non-required filters.
|
||||
///
|
||||
/// FilterValidator evaluates a set of filters for a single log
|
||||
/// entry. It determines if these filters apply to the log entry
|
||||
/// based on their condition (path, function, message) and their
|
||||
/// minimum log level.
|
||||
|
||||
struct FilterValidator {
|
||||
|
||||
// These are the different filter types that the user can set
|
||||
enum ValidationType: CaseIterable {
|
||||
case excluded
|
||||
case required
|
||||
case nonRequired
|
||||
|
||||
func apply(to filters: [FilterType]) -> [FilterType] {
|
||||
switch self {
|
||||
case .excluded:
|
||||
return filters.filter { $0.isExcluded() }
|
||||
case .required:
|
||||
return filters.filter { $0.isRequired() && !$0.isExcluded() }
|
||||
case .nonRequired:
|
||||
return filters.filter { !$0.isRequired() && !$0.isExcluded() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper object for input parameters
|
||||
struct Input {
|
||||
let filters: [FilterType]
|
||||
let level: SwiftyBeaver.Level
|
||||
let path: String
|
||||
let function: String
|
||||
let message: String?
|
||||
}
|
||||
|
||||
// Result wrapper object
|
||||
enum Result {
|
||||
case allFiltersMatch // All filters fully match the log entry (condition + minimum log level)
|
||||
case someFiltersMatch(PartialMatchData) // Only some filters fully match the log entry (condition + minimum log level)
|
||||
case noFiltersMatchingType // There are no filters set for a particular type (excluded, required, nonRequired)
|
||||
|
||||
struct PartialMatchData {
|
||||
let fullMatchCount: Int // Number of filters that match both the condition and the minimum log level of the log entry
|
||||
let conditionMatchCount: Int // Number of filters that match ONLY the condition of the log entry (path, function, message)
|
||||
let logLevelMatchCount: Int // Number of filters that match ONLY the minimum log level of the log entry
|
||||
}
|
||||
}
|
||||
|
||||
static func validate(input: Input, for types: [ValidationType] = ValidationType.allCases) -> [ValidationType: Result] {
|
||||
var results = [ValidationType: Result]()
|
||||
for type in types {
|
||||
let filtersToValidate = type.apply(to: input.filters)
|
||||
|
||||
if filtersToValidate.isEmpty {
|
||||
// There are no filters set for this particular type
|
||||
results[type] = .noFiltersMatchingType
|
||||
} else {
|
||||
var fullMatchCount: Int = 0
|
||||
var conditionMatchCount: Int = 0
|
||||
var logLevelMatchCount: Int = 0
|
||||
|
||||
for filter in filtersToValidate {
|
||||
let filterMatchesCondition = self.filterMatchesCondition(filter, level: input.level, path: input.path, function: input.function, message: input.message)
|
||||
let filterMatchesMinLogLevel = self.filterMatchesMinLogLevel(filter, level: input.level)
|
||||
|
||||
switch (filterMatchesCondition, filterMatchesMinLogLevel) {
|
||||
// Filter matches both the condition and the minimum log level
|
||||
case (true, true): fullMatchCount += 1
|
||||
// Filter matches only the condition (path, function, message)
|
||||
case (true, false): conditionMatchCount += 1
|
||||
// Filter matches only the minimum log level
|
||||
case (false, true): logLevelMatchCount += 1
|
||||
// Filter does not match the condition nor the minimum log level
|
||||
case (false, false): break
|
||||
}
|
||||
}
|
||||
|
||||
if filtersToValidate.count == fullMatchCount {
|
||||
// All filters fully match the log entry
|
||||
results[type] = .allFiltersMatch
|
||||
} else {
|
||||
// Only some filters match the log entry
|
||||
results[type] = .someFiltersMatch(.init(fullMatchCount: fullMatchCount, conditionMatchCount: conditionMatchCount, logLevelMatchCount: logLevelMatchCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private static func filterMatchesCondition(_ filter: FilterType, level: SwiftyBeaver.Level,
|
||||
path: String, function: String, message: String?) -> Bool {
|
||||
let passes: Bool
|
||||
|
||||
switch filter.getTarget() {
|
||||
case .Path(_):
|
||||
passes = filter.apply(path)
|
||||
|
||||
case .Function(_):
|
||||
passes = filter.apply(function)
|
||||
|
||||
case .Message(_):
|
||||
guard let message = message else {
|
||||
return false
|
||||
}
|
||||
|
||||
passes = filter.apply(message)
|
||||
}
|
||||
|
||||
return passes
|
||||
}
|
||||
|
||||
private static func filterMatchesMinLogLevel(_ filter: FilterType, level: SwiftyBeaver.Level) -> Bool {
|
||||
return filter.reachedMinLevel(level)
|
||||
}
|
||||
}
|
||||
99
Pods/SwiftyBeaver/Sources/GoogleCloudDestination.swift
generated
Normal file
99
Pods/SwiftyBeaver/Sources/GoogleCloudDestination.swift
generated
Normal file
@ -0,0 +1,99 @@
|
||||
//
|
||||
// GoogleCloudDestination.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Copyright © 2017 Sebastian Kreutzberger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class GoogleCloudDestination: BaseDestination {
|
||||
|
||||
private let serviceName: String
|
||||
|
||||
public init(serviceName: String) {
|
||||
self.serviceName = serviceName
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public var asynchronously: Bool {
|
||||
get {
|
||||
return false
|
||||
}
|
||||
set {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override public func send(_ level: SwiftyBeaver.Level, msg: String, thread: String,
|
||||
file: String, function: String, line: Int, context: Any? = nil) -> String? {
|
||||
|
||||
let reportLocation: [String: Any] = ["filePath": file, "lineNumber": line, "functionName": function]
|
||||
var gcpContext: [String: Any] = ["reportLocation": reportLocation]
|
||||
if let context = context as? [String: Any] {
|
||||
if let httpRequestContext = context["httpRequest"] as? [String: Any] {
|
||||
gcpContext["httpRequest"] = httpRequestContext
|
||||
}
|
||||
|
||||
if let user = context["user"] as? String {
|
||||
gcpContext["user"] = user
|
||||
}
|
||||
}
|
||||
|
||||
let gcpJSON: [String: Any] = [
|
||||
"serviceContext": [
|
||||
"service": serviceName
|
||||
],
|
||||
"message": msg,
|
||||
"severity": level.severity,
|
||||
"context": gcpContext
|
||||
]
|
||||
|
||||
let finalLogString: String
|
||||
|
||||
do {
|
||||
finalLogString = try jsonString(obj: gcpJSON)
|
||||
} catch {
|
||||
let uncrashableLogString = "{\"context\":{\"reportLocation\":{\"filePath\": \"\(file)\"" +
|
||||
",\"functionName\":\"\(function)\"" +
|
||||
",\"lineNumber\":\(line)},\"severity\"" +
|
||||
":\"CRITICAL\",\"message\":\"Error encoding " +
|
||||
"JSON log entry. You may be losing log messages!\"}"
|
||||
finalLogString = uncrashableLogString.description
|
||||
}
|
||||
print(finalLogString)
|
||||
return finalLogString
|
||||
}
|
||||
|
||||
private func jsonString(obj: Dictionary<String, Any>) throws -> String {
|
||||
let json = try JSONSerialization.data(withJSONObject: obj, options: [])
|
||||
guard let string = String(data: json, encoding: .utf8) else {
|
||||
throw GCPError.serialization
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
|
||||
extension SwiftyBeaver.Level {
|
||||
|
||||
/// Verbose is reported as Debug to GCP.
|
||||
/// Recommend you don't bother using it.
|
||||
var severity: String {
|
||||
switch self {
|
||||
// There is only one level below "Debug": "Default", which becomes "Any" and is considered as a potential error as well
|
||||
case .verbose: return "DEBUG"
|
||||
case .debug: return "DEBUG"
|
||||
case .info: return "INFO"
|
||||
case .warning: return "WARNING"
|
||||
case .error: return "ERROR"
|
||||
case .critical: return "CRITICAL"
|
||||
case .fault: return "FAULT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum GCPError: Error {
|
||||
case serialization
|
||||
}
|
||||
246
Pods/SwiftyBeaver/Sources/SwiftyBeaver.swift
generated
Normal file
246
Pods/SwiftyBeaver/Sources/SwiftyBeaver.swift
generated
Normal file
@ -0,0 +1,246 @@
|
||||
//
|
||||
// SwiftyBeaver.swift
|
||||
// SwiftyBeaver
|
||||
//
|
||||
// Created by Sebastian Kreutzberger (Twitter @skreutzb) on 28.11.15.
|
||||
// Copyright © 2015 Sebastian Kreutzberger
|
||||
// Some rights reserved: http://opensource.org/licenses/MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class SwiftyBeaver {
|
||||
|
||||
/// version string of framework
|
||||
public static let version = "2.1.1" // UPDATE ON RELEASE!
|
||||
/// build number of framework
|
||||
public static let build = 2110 // version 1.6.2 -> 1620, UPDATE ON RELEASE!
|
||||
|
||||
public enum Level: Int {
|
||||
case verbose = 0
|
||||
case debug = 1
|
||||
case info = 2
|
||||
case warning = 3
|
||||
case error = 4
|
||||
case critical = 5
|
||||
case fault = 6
|
||||
}
|
||||
|
||||
// a set of active destinations
|
||||
public private(set) static var destinations = Set<BaseDestination>()
|
||||
|
||||
/// A private queue for synchronizing access to `destinations`.
|
||||
/// Read accesses are done concurrently.
|
||||
/// Write accesses are done with a barrier, ensuring only 1 operation is ran at that time.
|
||||
private static let queue = DispatchQueue(label: "destination queue", attributes: .concurrent)
|
||||
|
||||
// MARK: Destination Handling
|
||||
|
||||
/// returns boolean about success
|
||||
@discardableResult
|
||||
open class func addDestination(_ destination: BaseDestination) -> Bool {
|
||||
return queue.sync(flags: DispatchWorkItemFlags.barrier) {
|
||||
if destinations.contains(destination) {
|
||||
return false
|
||||
}
|
||||
destinations.insert(destination)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// returns boolean about success
|
||||
@discardableResult
|
||||
open class func removeDestination(_ destination: BaseDestination) -> Bool {
|
||||
return queue.sync(flags: DispatchWorkItemFlags.barrier) {
|
||||
if destinations.contains(destination) == false {
|
||||
return false
|
||||
}
|
||||
destinations.remove(destination)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// if you need to start fresh
|
||||
open class func removeAllDestinations() {
|
||||
queue.sync(flags: DispatchWorkItemFlags.barrier) {
|
||||
destinations.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the amount of destinations
|
||||
open class func countDestinations() -> Int {
|
||||
return queue.sync { destinations.count }
|
||||
}
|
||||
|
||||
/// returns the current thread name
|
||||
open class func threadName() -> String {
|
||||
|
||||
#if os(Linux)
|
||||
// on 9/30/2016 not yet implemented in server-side Swift:
|
||||
// > import Foundation
|
||||
// > Thread.isMainThread
|
||||
return ""
|
||||
#else
|
||||
if Thread.isMainThread {
|
||||
return ""
|
||||
} else {
|
||||
let name = __dispatch_queue_get_label(nil)
|
||||
return String(cString: name, encoding: .utf8) ?? Thread.current.description
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: Levels
|
||||
|
||||
/// log something generally unimportant (lowest priority)
|
||||
open class func verbose(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .verbose, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .verbose, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which help during debugging (low priority)
|
||||
open class func debug(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .debug, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .debug, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which you are really interested but which is not an issue or error (normal priority)
|
||||
open class func info(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .info, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .info, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which may cause big trouble soon (high priority)
|
||||
open class func warning(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .warning, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .warning, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which will keep you awake at night (highest priority)
|
||||
open class func error(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .error, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .error, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which will keep you awake at night (highest priority)
|
||||
open class func critical(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .critical, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .critical, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// log something which will keep you awake at night (highest priority)
|
||||
open class func fault(_ message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
custom(level: .fault, message: message(), file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
custom(level: .fault, message: message, file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// custom logging to manually adjust values, should just be used by other frameworks
|
||||
open class func custom(level: SwiftyBeaver.Level, message: @autoclosure () -> Any,
|
||||
file: String = #file, function: String = #function, line: Int = #line, context: Any? = nil) {
|
||||
#if swift(>=5)
|
||||
dispatch_send(level: level, message: message(), thread: threadName(),
|
||||
file: file, function: function, line: line, context: context)
|
||||
#else
|
||||
dispatch_send(level: level, message: message, thread: threadName(),
|
||||
file: file, function: function, line: line, context: context)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// internal helper which dispatches send to dedicated queue if minLevel is ok
|
||||
class func dispatch_send(level: SwiftyBeaver.Level, message: @autoclosure () -> Any,
|
||||
thread: String, file: String, function: String, line: Int, context: Any?) {
|
||||
var resolvedMessage: String?
|
||||
let destinations = queue.sync { self.destinations }
|
||||
for dest in destinations {
|
||||
|
||||
guard let queue = dest.queue else {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvedMessage = resolvedMessage == nil && dest.hasMessageFilters() ? "\(message())" : resolvedMessage
|
||||
if dest.shouldLevelBeLogged(level, path: file, function: function, message: resolvedMessage) {
|
||||
// try to convert msg object to String and put it on queue
|
||||
let msgStr = resolvedMessage == nil ? "\(message())" : resolvedMessage!
|
||||
let f = stripParams(function: function)
|
||||
|
||||
if dest.asynchronously {
|
||||
queue.async {
|
||||
_ = dest.send(level, msg: msgStr, thread: thread, file: file, function: f, line: line, context: context)
|
||||
}
|
||||
} else {
|
||||
queue.sync {
|
||||
_ = dest.send(level, msg: msgStr, thread: thread, file: file, function: f, line: line, context: context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// flush all destinations to make sure all logging messages have been written out
|
||||
/// returns after all messages flushed or timeout seconds
|
||||
/// returns: true if all messages flushed, false if timeout or error occurred
|
||||
public class func flush(secondTimeout: Int64) -> Bool {
|
||||
let grp = DispatchGroup()
|
||||
let destinations = queue.sync { self.destinations }
|
||||
for dest in destinations {
|
||||
guard let queue = dest.queue else {
|
||||
continue
|
||||
}
|
||||
grp.enter()
|
||||
if dest.asynchronously {
|
||||
queue.async {
|
||||
dest.flush()
|
||||
grp.leave()
|
||||
}
|
||||
} else {
|
||||
queue.sync {
|
||||
dest.flush()
|
||||
grp.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
return grp.wait(timeout: .now() + .seconds(Int(secondTimeout))) == .success
|
||||
}
|
||||
|
||||
/// removes the parameters from a function because it looks weird with a single param
|
||||
class func stripParams(function: String) -> String {
|
||||
var f = function
|
||||
if let indexOfBrace = f.find("(") {
|
||||
#if swift(>=4.0)
|
||||
f = String(f[..<indexOfBrace])
|
||||
#else
|
||||
f = f.substring(to: indexOfBrace)
|
||||
#endif
|
||||
}
|
||||
f += "()"
|
||||
return f
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user