├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── PerfectLogger │ ├── FileLogger.swift │ ├── LogOptions.swift │ ├── LogPriority.swift │ └── RemoteLogger.swift └── Tests ├── LinuxMain.swift └── PerfectLoggerTests └── PerfectLoggerTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | .build_lin/ 40 | *.pins 41 | *.resolved 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 64 | 65 | fastlane/report.xml 66 | fastlane/screenshots 67 | 68 | Packages/ 69 | .packages_lin/ 70 | *.xcodeproj 71 | buildlinux 72 | Package.swift.orig 73 | Package.resolved 74 | test.set.txt 75 | .DS_Store 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | // Generated automatically by Perfect Assistant Application 3 | // Date: 2017-09-29 18:02:27 +0000 4 | import PackageDescription 5 | let package = Package( 6 | name: "PerfectLogger", 7 | products: [ 8 | .library(name: "PerfectLogger", targets: ["PerfectLogger"]) 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/PerfectlySoft/Perfect-CURL.git", from: "3.0.0"), 12 | ], 13 | targets: [ 14 | .target(name: "PerfectLogger", dependencies: ["PerfectCURL"]) 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect Logging (File & Remote) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 3.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | Using the `PerfectLogger` module, events can be logged to a specfied file, in addition to the console. 43 | 44 | Support is also included in this module for remote logging events to the [Perfect Log Server](https://github.com/PerfectServers/Perfect-LogServer). 45 | 46 | ## Using in your project 47 | 48 | Add the dependancy to your project's Package.swift file: 49 | 50 | ``` swift 51 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 52 | ``` 53 | 54 | Now add the `import` directive to the file you wish to use the logging in: 55 | 56 | ``` swift 57 | import PerfectLogger 58 | ``` 59 | 60 | To log events to the local console as well as a file: 61 | 62 | ``` swift 63 | LogFile.debug("debug message", logFile: "test.txt") 64 | LogFile.info("info message", logFile: "test.txt") 65 | LogFile.warning("warning message", logFile: "test.txt") 66 | LogFile.error("error message", logFile: "test.txt") 67 | LogFile.critical("critical message", logFile: "test.txt") 68 | LogFile.terminal("terminal message", logFile: "test.txt") 69 | ``` 70 | 71 | To log to the default file, omit the file name parameter. 72 | 73 | ## Linking events with "eventid" 74 | 75 | Each log event returns an event id string. If an eventid string is supplied to the directive then it will use the supplied eventid in the log file instead - this makes it easy to link together related events. 76 | 77 | ``` swift 78 | let eid = LogFile.warning("test 1") 79 | LogFile.critical("test 2", eventid: eid) 80 | ``` 81 | 82 | returns: 83 | 84 | ``` 85 | [WARNING] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 1 86 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] test 2 87 | ``` 88 | 89 | The returned eventid is marked `@discardableResult` therefore can be safely ignored if not required for re-use. 90 | 91 | 92 | ## Customization 93 | 94 | ### Setting a custom Logfile location 95 | 96 | The default logfile location is `./log.log`. To set a custom logfile location, set the `LogFile.location` variable: 97 | 98 | ``` swift 99 | LogFile.location = "/var/log/myLog.log" 100 | ``` 101 | 102 | Messages can now be logged directly to the file as set by using: 103 | 104 | ``` swift 105 | LogFile.debug("debug message") 106 | LogFile.info("info message") 107 | LogFile.warning("warning message") 108 | LogFile.error("error message") 109 | LogFile.critical("critical message") 110 | LogFile.terminal("terminal message") 111 | ``` 112 | 113 | ### LogFile threshold 114 | 115 | For debug purposes, you want to see as much info as available. However, on production servers you probably desire a 116 | smaller logfile and filter out all redundant info. 117 | 118 | To do so, you may set the LogFile's `threshold` property to the minumum priority you want to actually being logged into the file. 119 | 120 | e.g.: 121 | 122 | ```swift 123 | LogFile.threshold = .warning 124 | LogFile.debug("This won't be logged into the file") 125 | LogFile.info("This won't be logged into the file") 126 | LogFile.warning("This will be logged into the file") 127 | LogFile.error("This will be logged into the file") 128 | LogFile.critical("This will be logged into the file") 129 | ``` 130 | 131 | The default value of this property is `.debug` to preserve backward compatibility and this property will not affect the Console/Remote logger. 132 | 133 | ### LogFile options 134 | 135 | Depending on your needs, you may not be interested in an event id, timestamp or priority. 136 | 137 | Using the LogFile's `options` property you can customize which of those fields will actually be added as a prefix to the log message. 138 | 139 | e.g.: 140 | 141 | ```swift 142 | // Default behaviour (equal to `[.priority, .eventId, .timestamp]`) 143 | LogFile.options = .default 144 | LogFile.debug("This is my log message") 145 | // Will log: "[DEBUG] [CEC5B5DB-931F-4C5A-A794-17D060BABC80] [2019-05-04 15:16:11 GMT+02:00] This is my log message" 146 | 147 | LogFile.options = .none 148 | LogFile.debug("This is my log message") 149 | // Will log: "This is my log message" 150 | 151 | LogFile.options = [.priority, .timestamp] 152 | LogFile.debug("This is my log message") 153 | // Will log: "[DEBUG] [2019-05-04 15:16:11 GMT+02:00] This is my log message" 154 | 155 | LogFile.options = [.priority] 156 | LogFile.debug("This is my log message") 157 | // Will log: "[DEBUG] This is my log message" 158 | ``` 159 | 160 | 161 | ## Sample output 162 | 163 | ``` 164 | [DEBUG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] a debug message 165 | [INFO] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] an informational message 166 | [WARNING] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] a warning message 167 | [ERROR] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] an error message 168 | [CRITICAL] [62f940aa-f204-43ed-9934-166896eda21c] [2016-11-16 15:18:02 GMT-05:00] a critical message 169 | [EMERG] [ec6a9ca5-00b1-4656-9e4c-ddecae8dde02] [2016-11-16 15:18:02 GMT-05:00] an emergency message 170 | ``` 171 | 172 | ## Remote Logging 173 | 174 | The "Perfect-Logging" dependency includes support for remote logging to this log server. 175 | 176 | To include the dependency in your project, add the following to your project's Package.swift file: 177 | 178 | ``` swift 179 | .Package(url: "https://github.com/PerfectlySoft/Perfect-Logger.git", majorVersion: 3), 180 | ``` 181 | 182 | Now add the import directive to the file you wish to use the logging in: 183 | 184 | ``` swift 185 | import PerfectLogger 186 | ``` 187 | 188 | #### Configuration 189 | Three configuration parameters are required: 190 | 191 | ``` swift 192 | // Your token 193 | RemoteLogger.token = "" 194 | 195 | // App ID (Optional) 196 | RemoteLogger.appid = "" 197 | 198 | // URL to access the log server. 199 | // Note, this is not the full API path, just the host and port. 200 | RemoteLogger.logServer = "http://localhost:8181" 201 | 202 | ``` 203 | 204 | 205 | #### To log events to the log server: 206 | 207 | ``` swift 208 | var obj = [String: Any]() 209 | obj["one"] = "donkey" 210 | RemoteLogger.critical(obj) 211 | ``` 212 | 213 | #### Linking events with "eventid" 214 | 215 | Each log event returns an event id string. If an eventid string is supplied to the directive then it will use the supplied eventid in the log directive instead - this makes it easy to link together related events. 216 | 217 | ``` swift 218 | let eid = RemoteLogger.critical(obj) 219 | RemoteLogger.info(obj, eventid: eid) 220 | ``` 221 | 222 | The returned eventid is marked @discardableResult therefore can be safely ignored if not required for re-use. 223 | 224 | ## Further Information 225 | For more information on the Perfect project, please visit [perfect.org](http://perfect.org). 226 | -------------------------------------------------------------------------------- /Sources/PerfectLogger/FileLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Perfect_Logger.swift 3 | // PerfectLogger 4 | // 5 | // Created by Jonathan Guthrie on 16/11/2016. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | 21 | #if os(Linux) 22 | import SwiftGlibc 23 | import LinuxBridge 24 | #else 25 | import Darwin 26 | #endif 27 | 28 | import PerfectLib 29 | import Foundation 30 | 31 | struct FileLogger { 32 | var threshold: LogPriority = .debug 33 | var options: LogOptions = .default 34 | 35 | let defaultFile = "./log.log" 36 | let consoleEcho = ConsoleLogger() 37 | let fmt = DateFormatter() 38 | fileprivate init(){ 39 | fmt.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZZ" 40 | } 41 | 42 | func filelog(priority: LogPriority, _ args: String, _ eventid: String, _ logFile: String, _ even: Bool) { 43 | // Validate level threshold first 44 | guard priority >= threshold else { return } 45 | 46 | var prefixList = [String]() 47 | 48 | if options.contains(.priority) { 49 | prefixList.append(priority.stringRepresentation(even: even)) 50 | } 51 | if options.contains(.eventId) { 52 | prefixList.append("[\(eventid)]") 53 | } 54 | if options.contains(.timestamp) { 55 | prefixList.append("[\(fmt.string(from: Date()))]") 56 | } 57 | 58 | // Add a space to seperate the prefix list from the message when the list contains at least 1 value 59 | let prefix = prefixList.count > 0 ? "\(prefixList.joined(separator: " ")) " : "" 60 | 61 | var useFile = logFile 62 | if logFile.isEmpty { useFile = defaultFile } 63 | let ff = File(useFile) 64 | defer { ff.close() } 65 | do { 66 | try ff.open(.append) 67 | try ff.write(string: "\(prefix)\(args)\n") 68 | } catch { 69 | consoleEcho.critical(message: "\(error)", even) 70 | } 71 | } 72 | 73 | func debug(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 74 | consoleEcho.debug(message: message, even) 75 | filelog(priority: .debug, message, eventid, logFile, even) 76 | } 77 | 78 | func info(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 79 | consoleEcho.info(message: message, even) 80 | filelog(priority: .info, message, eventid, logFile, even) 81 | } 82 | 83 | func warning(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 84 | consoleEcho.warning(message: message, even) 85 | filelog(priority: .warning, message, eventid, logFile, even) 86 | } 87 | 88 | func error(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 89 | consoleEcho.error(message: message, even) 90 | filelog(priority: .error, message, eventid, logFile, even) 91 | } 92 | 93 | func critical(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 94 | consoleEcho.critical(message: message, even) 95 | filelog(priority: .critical, message, eventid, logFile, even) 96 | } 97 | 98 | func terminal(message: String, _ eventid: String, _ logFile: String, _ even: Bool) { 99 | consoleEcho.terminal(message: message, even) 100 | filelog(priority: .terminal, message, eventid, logFile, even) 101 | } 102 | } 103 | 104 | /// Provision for logging information to a log file 105 | public struct LogFile { 106 | private init(){} 107 | 108 | static var logger = FileLogger() 109 | 110 | /** 111 | Threshold for priorties to log 112 | 113 | e.g. when set to `.error` only error, critical and terminal messages will actually be logged 114 | */ 115 | public static var threshold: LogPriority { 116 | get { 117 | return logger.threshold 118 | } 119 | set { 120 | logger.threshold = newValue 121 | } 122 | } 123 | 124 | /** 125 | Log options that indicate which fields (e.g. priority, event ID) should be send to the log file. 126 | 127 | To use the default set: 128 | 129 | ``` 130 | LogFile.options = .default 131 | ``` 132 | 133 | To use a custom set: 134 | 135 | ``` 136 | LogFile.options = [.level, .timestamp] 137 | ``` 138 | */ 139 | public static var options: LogOptions { 140 | get { 141 | return logger.options 142 | } 143 | set { 144 | logger.options = newValue 145 | } 146 | } 147 | 148 | /// The location of the log file. 149 | /// The default location is relative, "log.log" 150 | public static var location = "./log.log" 151 | 152 | /// Whether or not to even off the log messages 153 | /// If set to true log messages will be inline with each other 154 | public static var even = false 155 | 156 | /// Logs a [DEBUG] message to the log file. 157 | /// Also echoes the message to the console. 158 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 159 | /// Takes an optional "eventid" param, which helps to link related events together. 160 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 161 | @discardableResult 162 | public static func debug(_ message: @autoclosure () -> String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> String { 163 | LogFile.logger.debug(message: message(), eventid, logFile, evenIdents) 164 | return eventid 165 | } 166 | 167 | /// Logs a [INFO] message to the log file. 168 | /// Also echoes the message to the console. 169 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 170 | /// Takes an optional "eventid" param, which helps to link related events together. 171 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 172 | @discardableResult 173 | public static func info(_ message: String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> String { 174 | LogFile.logger.info(message: message, eventid, logFile, evenIdents) 175 | return eventid 176 | } 177 | 178 | /// Logs a [WARNING] message to the log file. 179 | /// Also echoes the message to the console. 180 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 181 | /// Takes an optional "eventid" param, which helps to link related events together. 182 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 183 | @discardableResult 184 | public static func warning(_ message: String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> String { 185 | LogFile.logger.warning(message: message, eventid, logFile, evenIdents) 186 | return eventid 187 | } 188 | 189 | /// Logs a [ERROR] message to the log file. 190 | /// Also echoes the message to the console. 191 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 192 | /// Takes an optional "eventid" param, which helps to link related events together. 193 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 194 | @discardableResult 195 | public static func error(_ message: String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> String { 196 | LogFile.logger.error(message: message, eventid, logFile, evenIdents) 197 | return eventid 198 | } 199 | 200 | /// Logs a [CRIICAL] message to the log file. 201 | /// Also echoes the message to the console. 202 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 203 | /// Takes an optional "eventid" param, which helps to link related events together. 204 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 205 | @discardableResult 206 | public static func critical(_ message: String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> String { 207 | LogFile.logger.critical(message: message, eventid, logFile, evenIdents) 208 | return eventid 209 | } 210 | 211 | /// Logs a [EMERG] message to the log file. 212 | /// Also echoes the message to the console. 213 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 214 | /// Takes an optional "eventid" param, which helps to link related events together. 215 | public static func terminal(_ message: String, eventid: String = Foundation.UUID().uuidString, logFile: String = location, evenIdents: Bool = even) -> Never { 216 | LogFile.logger.terminal(message: message, eventid, logFile, evenIdents) 217 | fatalError(message) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Sources/PerfectLogger/LogOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogOptions.swift 3 | // PerfectLogger 4 | // 5 | // Created by Michiel Horvers on 04/05/2019. 6 | // Copyright (C) 2019 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2019 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import Foundation 21 | 22 | public struct LogOptions: OptionSet { 23 | /// Rawvalue of this option set 24 | public let rawValue: Int 25 | 26 | public init(rawValue: Int) { 27 | self.rawValue = rawValue 28 | } 29 | 30 | /* 31 | * MARK: Default sets 32 | */ 33 | 34 | /// Default option set 35 | public static let `default`: LogOptions = [.priority, .eventId, .timestamp] 36 | 37 | /// Will only log the message itself 38 | public static let none: LogOptions = [] 39 | 40 | /* 41 | * MARK: Options 42 | */ 43 | 44 | /// Logs the level/priority (e.g. "[DEBUG]") 45 | public static let priority = LogOptions(rawValue: 1 << 0) 46 | 47 | /// Logs the event ID (e.g. "[34E621FD-9A57-47A0-BD3A-1B432B77BA30]") 48 | public static let eventId = LogOptions(rawValue: 1 << 1) 49 | 50 | /// Logs the timestamp (e.g. "[2019-05-04 13:25:55 GMT+02:00]") 51 | public static let timestamp = LogOptions(rawValue: 1 << 2) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Sources/PerfectLogger/LogPriority.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogPriority.swift 3 | // PerfectLogger 4 | // 5 | // Created by Michiel Horvers on 04/05/2019. 6 | // Copyright (C) 2019 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2019 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | 21 | import Foundation 22 | 23 | /** 24 | Priority of the message. 25 | 26 | Either one of: 27 | * debug 28 | * info 29 | * warning 30 | * error 31 | * critical 32 | * terminal 33 | 34 | */ 35 | public enum LogPriority: Int { 36 | case debug 37 | case info 38 | case warning 39 | case error 40 | case critical 41 | case terminal 42 | } 43 | 44 | internal extension LogPriority { 45 | /** 46 | Returns the string representation for this level. 47 | 48 | If `even` set to true, log messages will be inline with each other 49 | 50 | - parameter even: Whether or not to even off the log messages 51 | 52 | - returns: The string representation for this log level 53 | */ 54 | func stringRepresentation(even: Bool) -> String { 55 | switch self { 56 | case .debug: return "[DEBUG]" 57 | case .info: return even ? "[INFO] " : "[INFO]" 58 | case .warning: return even ? "[WARN] " : "[WARNING]" 59 | case .error: return "[ERROR]" 60 | case .critical: return even ? "[CRIT] " : "[CRITICAL]" 61 | case .terminal: return even ? "[EMERG]" : "[EMERG]" 62 | } 63 | } 64 | } 65 | 66 | extension LogPriority: Comparable { 67 | public static func >(lhs: LogPriority, rhs: LogPriority) -> Bool { 68 | return lhs.rawValue > rhs.rawValue 69 | } 70 | 71 | public static func <(lhs: LogPriority, rhs: LogPriority) -> Bool { 72 | return lhs.rawValue < rhs.rawValue 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/PerfectLogger/RemoteLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteLogger.swift 3 | // Perfect-Logger 4 | // 5 | // Created by Jonathan Guthrie on 2017-01-09. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | 21 | import PerfectLib 22 | import PerfectCURL 23 | import cURL 24 | import Foundation 25 | 26 | 27 | public struct RemoteLogger { 28 | public static var logServer = "http://localhost:8100" 29 | public static var token = "" 30 | public static var appid = "" 31 | 32 | /// Whether or not to even off the log messages 33 | /// If set to true log messages will be inline with each other 34 | public static var even = false 35 | 36 | static let consoleEcho = ConsoleLogger() 37 | 38 | fileprivate init(){} 39 | 40 | static func log(priority: String, _ detail: [String:Any], _ eventid: String) throws { 41 | let useragent = "PerfectServer2.0" 42 | let logAPI = "/api/v1/log/" 43 | var obj = [String:Any]() 44 | obj["appuuid"] = RemoteLogger.appid 45 | obj["eventid"] = eventid 46 | obj["loglevel"] = priority 47 | obj["detail"] = detail 48 | var body = "" 49 | do { 50 | try body = obj.jsonEncodedString() 51 | } catch { 52 | throw error 53 | } 54 | 55 | var url = RemoteLogger.logServer + logAPI + RemoteLogger.token 56 | 57 | let curlObject = CURL(url: url) 58 | curlObject.setOption(CURLOPT_HTTPHEADER, s: "Accept: application/json") 59 | curlObject.setOption(CURLOPT_HTTPHEADER, s: "Cache-Control: no-cache") 60 | curlObject.setOption(CURLOPT_USERAGENT, s: useragent) 61 | 62 | 63 | if !body.isEmpty { 64 | let byteArray = [UInt8](body.utf8) 65 | curlObject.setOption(CURLOPT_POST, int: 1) 66 | curlObject.setOption(CURLOPT_POSTFIELDSIZE, int: byteArray.count) 67 | curlObject.setOption(CURLOPT_COPYPOSTFIELDS, v: UnsafeMutablePointer(mutating: byteArray)) 68 | curlObject.setOption(CURLOPT_HTTPHEADER, s: "Content-Type: application/json") 69 | } 70 | 71 | 72 | 73 | var header = [UInt8]() 74 | var bodyIn = [UInt8]() 75 | 76 | var code = 0 77 | var data = [String: Any]() 78 | var raw = [String: Any]() 79 | 80 | var perf = curlObject.perform() 81 | defer { curlObject.close() } 82 | 83 | while perf.0 { 84 | if let h = perf.2 { 85 | header.append(contentsOf: h) 86 | } 87 | if let b = perf.3 { 88 | bodyIn.append(contentsOf: b) 89 | } 90 | perf = curlObject.perform() 91 | } 92 | if let h = perf.2 { 93 | header.append(contentsOf: h) 94 | } 95 | if let b = perf.3 { 96 | bodyIn.append(contentsOf: b) 97 | } 98 | let _ = perf.1 99 | 100 | // no need to parse. It's really just one way... 101 | 102 | } 103 | 104 | /// Logs a [DEBUG] message to the log server. 105 | /// Also echoes the message to the console. 106 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 107 | /// Takes an optional "eventid" param, which helps to link related events together. 108 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 109 | @discardableResult 110 | public static func debug(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> String { 111 | do { 112 | consoleEcho.debug(message: try detail.jsonEncodedString(), evenIdents) 113 | try RemoteLogger.log(priority: "debug", detail, eventid) 114 | } catch { 115 | consoleEcho.error(message: "\(error)", evenIdents) 116 | } 117 | return eventid 118 | } 119 | 120 | /// Logs a [INFO] message to the log server. 121 | /// Also echoes the message to the console. 122 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 123 | /// Takes an optional "eventid" param, which helps to link related events together. 124 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 125 | @discardableResult 126 | public static func info(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> String { 127 | do { 128 | consoleEcho.info(message: try detail.jsonEncodedString(), evenIdents) 129 | try RemoteLogger.log(priority: "info", detail, eventid) 130 | } catch { 131 | consoleEcho.error(message: "\(error)", evenIdents) 132 | } 133 | return eventid 134 | } 135 | 136 | /// Logs a [WARNING] message to the log server. 137 | /// Also echoes the message to the console. 138 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 139 | /// Takes an optional "eventid" param, which helps to link related events together. 140 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 141 | @discardableResult 142 | public static func warning(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> String { 143 | do { 144 | consoleEcho.warning(message: try detail.jsonEncodedString(), evenIdents) 145 | try RemoteLogger.log(priority: "warning", detail, eventid) 146 | } catch { 147 | consoleEcho.error(message: "\(error)", evenIdents) 148 | } 149 | return eventid 150 | } 151 | 152 | /// Logs a [ERROR] message to the log server. 153 | /// Also echoes the message to the console. 154 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 155 | /// Takes an optional "eventid" param, which helps to link related events together. 156 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 157 | @discardableResult 158 | public static func error(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> String { 159 | do { 160 | consoleEcho.error(message: try detail.jsonEncodedString(), evenIdents) 161 | try RemoteLogger.log(priority: "error", detail, eventid) 162 | } catch { 163 | consoleEcho.error(message: "\(error)", evenIdents) 164 | } 165 | return eventid 166 | } 167 | 168 | /// Logs a [CRIICAL] message to the log server. 169 | /// Also echoes the message to the console. 170 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 171 | /// Takes an optional "eventid" param, which helps to link related events together. 172 | /// Returns an eventid string. If one was supplied, it will be echoed back, else a new one will be generated for reuse. 173 | @discardableResult 174 | public static func critical(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> String { 175 | do { 176 | consoleEcho.critical(message: try detail.jsonEncodedString(), evenIdents) 177 | try RemoteLogger.log(priority: "critical", detail, eventid) 178 | } catch { 179 | consoleEcho.error(message: "\(error)", evenIdents) 180 | } 181 | return eventid 182 | } 183 | 184 | /// Logs a [EMERG] message to the log server. 185 | /// Also echoes the message to the console. 186 | /// Specifiy a logFile parameter to direct the logging to a file other than the default. 187 | /// Takes an optional "eventid" param, which helps to link related events together. 188 | public static func terminal(_ detail: [String:Any] = [String:Any](), eventid: String = Foundation.UUID().uuidString, evenIdents: Bool = even) -> Never { 189 | do { 190 | consoleEcho.critical(message: try detail.jsonEncodedString(), evenIdents) 191 | try RemoteLogger.log(priority: "emerg", detail, eventid) 192 | let str = try detail.jsonEncodedString() 193 | fatalError(str) 194 | } catch { 195 | fatalError("Unspecfified fatal error. Additionally the object failed to render as JSON.") 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectLoggerTests 3 | 4 | XCTMain([ 5 | testCase(PerfectLoggerTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PerfectLoggerTests/PerfectLoggerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectLogger 3 | 4 | class PerfectLoggerTests: XCTestCase { 5 | func testBasic() { 6 | LogFile.location = "./test_set.txt" 7 | LogFile.critical("test critical default", logFile:"./test_log.txt") 8 | let eid = LogFile.critical("test critical default file") 9 | LogFile.critical("test critical default file 1") 10 | LogFile.critical("test critical default file 2", eventid: eid) 11 | } 12 | 13 | func testRemote() { 14 | RemoteLogger.token = "eecb34a4-4c1c-43ed-af24-3bed830a3112" 15 | RemoteLogger.appid = "59b4a4b8-fd5e-45b1-b8ed-48bcab82049f" 16 | RemoteLogger.logServer = "http://localhost:8181" 17 | 18 | var obj = [String: Any]() 19 | obj["one"] = "donkey" 20 | 21 | RemoteLogger.critical(obj) 22 | 23 | obj["two"] = "hares" 24 | let eid = RemoteLogger.critical(obj) 25 | 26 | obj["three"] = "hippo" 27 | 28 | RemoteLogger.info(obj) 29 | 30 | obj["four"] = "tigers" 31 | RemoteLogger.critical(obj, eventid: eid) 32 | } 33 | 34 | 35 | 36 | static var allTests : [(String, (PerfectLoggerTests) -> () throws -> Void)] { 37 | return [ 38 | ("testBasic", testBasic), 39 | ] 40 | } 41 | } 42 | --------------------------------------------------------------------------------