├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |
--------------------------------------------------------------------------------