├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ └── chris.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── FileLogging │ ├── swift_log_file.swift │ └── swift_log_xcglogger.swift ├── Tests ├── LinuxMain.swift └── swift-log-fileTests │ ├── Utilities.swift │ ├── swift_log_fileTests.swift │ └── swift_log_xcgloggerTests.swift ├── Tools └── addVersion.sh └── VERSIONS.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .swiftpm 3 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/chris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/chris.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FileLogger.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | swift-log-file.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | FileLogging 21 | 22 | primary 23 | 24 | 25 | swift-log-file 26 | 27 | primary 28 | 29 | 30 | swift-log-fileTests 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Christopher Prince 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-log", 6 | "repositoryURL": "https://github.com/apple/swift-log.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "173f567a2dfec11d74588eea82cecea555bdc0bc", 10 | "version": "1.4.0" 11 | } 12 | }, 13 | { 14 | "package": "XCGLogger", 15 | "repositoryURL": "https://github.com/DaveWoodCom/XCGLogger.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "a9c4667b247928a29bdd41be2ec2c8d304215a54", 19 | "version": "7.0.1" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swift-log-file", 8 | products: [ 9 | .library( 10 | name: "FileLogging", 11 | targets: ["FileLogging"]), 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), 15 | .package(url: "https://github.com/DaveWoodCom/XCGLogger.git", from: "7.0.0") 16 | ], 17 | targets: [ 18 | .target( 19 | name: "FileLogging", 20 | dependencies: [ 21 | "XCGLogger", 22 | .product(name: "Logging", package: "swift-log") 23 | ]), 24 | .testTarget( 25 | name: "swift-log-fileTests", 26 | dependencies: [ 27 | "FileLogging", 28 | "XCGLogger" 29 | ]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-log-file 2 | 3 | [SwiftLog](https://github.com/apple/swift-log) compatible file log handler. 4 | 5 | This currently only supports iOS. 6 | 7 | ## Example: Just logging to a file 8 | 9 | ```swift 10 | let logFileURL = URL(/* your local log file here */) 11 | let logger = try FileLogging.logger(label: "Foobar", localFile: logFileURL) 12 | logger.error("Test Test Test") 13 | ``` 14 | 15 | ## Example: Logging to both the standard output (Xcode console if using Xcode) and a file. 16 | 17 | ```swift 18 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 19 | let fileLogger = try FileLogging(to: logFileURL) 20 | 21 | LoggingSystem.bootstrap { label in 22 | let handlers:[LogHandler] = [ 23 | FileLogHandler(label: label, fileLogger: fileLogger), 24 | StreamLogHandler.standardOutput(label: label) 25 | ] 26 | 27 | return MultiplexLogHandler(handlers) 28 | } 29 | 30 | let logger = Logger(label: "Test") 31 | ``` 32 | 33 | Note in that last example, if you use `LoggingSystem.bootstrap`, make sure to create your `Logger` *after* the `LoggingSystem.bootstrap` usage (or you won't get the effects of the `LoggingSystem.bootstrap`). 34 | 35 | ## Example: Using XCGLogger 36 | 37 | [XCGLogger](https://github.com/DaveWoodCom/XCGLogger.git) supports rotating file logs amongst other features. 38 | 39 | ```swift 40 | let logFileURL = URL(/* your local log file here */) 41 | let xcgLogger = /* Make your XCGLogger, using logFileURL */ 42 | let logger = XCGLogging.logger(label: "Test", logger: xcgLogger) 43 | logger.error("Test Test Test") 44 | ``` 45 | 46 | ## Example: Logging to both the standard output (Xcode console if using Xcode) and a file using XCGLogger. 47 | 48 | ```swift 49 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 50 | let xcgLogger = /* Make your XCGLogger, using logFileURL */ 51 | 52 | LoggingSystem.bootstrap { label in 53 | let handlers:[LogHandler] = [ 54 | XCGLoggerHandler(label: label, logger: xcgLogger), 55 | StreamLogHandler.standardOutput(label: label) 56 | ] 57 | 58 | return MultiplexLogHandler(handlers) 59 | } 60 | 61 | let logger = Logger(label: "Test") 62 | ``` 63 | 64 | For more examples, see the unit tests and refer to [apple/swift-log's README](https://github.com/apple/swift-log#the-core-concepts) 65 | -------------------------------------------------------------------------------- /Sources/FileLogging/swift_log_file.swift: -------------------------------------------------------------------------------- 1 | import Logging 2 | import Foundation 3 | 4 | // Adapted from https://nshipster.com/textoutputstream/ 5 | struct FileHandlerOutputStream: TextOutputStream { 6 | enum FileHandlerOutputStream: Error { 7 | case couldNotCreateFile 8 | } 9 | 10 | private let fileHandle: FileHandle 11 | let encoding: String.Encoding 12 | 13 | init(localFile url: URL, encoding: String.Encoding = .utf8) throws { 14 | if !FileManager.default.fileExists(atPath: url.path) { 15 | guard FileManager.default.createFile(atPath: url.path, contents: nil, attributes: nil) else { 16 | throw FileHandlerOutputStream.couldNotCreateFile 17 | } 18 | } 19 | 20 | let fileHandle = try FileHandle(forWritingTo: url) 21 | fileHandle.seekToEndOfFile() 22 | self.fileHandle = fileHandle 23 | self.encoding = encoding 24 | } 25 | 26 | mutating func write(_ string: String) { 27 | if let data = string.data(using: encoding) { 28 | fileHandle.write(data) 29 | } 30 | } 31 | } 32 | 33 | public struct FileLogging { 34 | let stream: TextOutputStream 35 | private var localFile: URL 36 | 37 | public init(to localFile: URL) throws { 38 | self.stream = try FileHandlerOutputStream(localFile: localFile) 39 | self.localFile = localFile 40 | } 41 | 42 | public func handler(label: String) -> FileLogHandler { 43 | return FileLogHandler(label: label, fileLogger: self) 44 | } 45 | 46 | public static func logger(label: String, localFile url: URL) throws -> Logger { 47 | let logging = try FileLogging(to: url) 48 | return Logger(label: label, factory: logging.handler) 49 | } 50 | } 51 | 52 | // Adapted from https://github.com/apple/swift-log.git 53 | 54 | /// `FileLogHandler` is a simple implementation of `LogHandler` for directing 55 | /// `Logger` output to a local file. Appends log output to this file, even across constructor calls. 56 | public struct FileLogHandler: LogHandler { 57 | private let stream: TextOutputStream 58 | private var label: String 59 | 60 | public var logLevel: Logger.Level = .info 61 | 62 | private var prettyMetadata: String? 63 | public var metadata = Logger.Metadata() { 64 | didSet { 65 | self.prettyMetadata = self.prettify(self.metadata) 66 | } 67 | } 68 | 69 | public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 70 | get { 71 | return self.metadata[metadataKey] 72 | } 73 | set { 74 | self.metadata[metadataKey] = newValue 75 | } 76 | } 77 | 78 | public init(label: String, fileLogger: FileLogging) { 79 | self.label = label 80 | self.stream = fileLogger.stream 81 | } 82 | 83 | public init(label: String, localFile url: URL) throws { 84 | self.label = label 85 | self.stream = try FileHandlerOutputStream(localFile: url) 86 | } 87 | 88 | public func log(level: Logger.Level, 89 | message: Logger.Message, 90 | metadata: Logger.Metadata?, 91 | source: String, 92 | file: String, 93 | function: String, 94 | line: UInt) { 95 | let prettyMetadata = metadata?.isEmpty ?? true 96 | ? self.prettyMetadata 97 | : self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new })) 98 | 99 | var stream = self.stream 100 | stream.write("\(self.timestamp()) \(level) \(self.label) :\(prettyMetadata.map { " \($0)" } ?? "") \(message)\n") 101 | } 102 | 103 | private func prettify(_ metadata: Logger.Metadata) -> String? { 104 | return !metadata.isEmpty ? metadata.map { "\($0)=\($1)" }.joined(separator: " ") : nil 105 | } 106 | 107 | private func timestamp() -> String { 108 | var buffer = [Int8](repeating: 0, count: 255) 109 | var timestamp = time(nil) 110 | let localTime = localtime(×tamp) 111 | strftime(&buffer, buffer.count, "%Y-%m-%dT%H:%M:%S%z", localTime) 112 | return buffer.withUnsafeBufferPointer { 113 | $0.withMemoryRebound(to: CChar.self) { 114 | String(cString: $0.baseAddress!) 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/FileLogging/swift_log_xcglogger.swift: -------------------------------------------------------------------------------- 1 | 2 | import Logging 3 | import Foundation 4 | import XCGLogger 5 | 6 | public struct XCGLogging { 7 | let logger:XCGLogger 8 | 9 | public init(logger:XCGLogger) { 10 | self.logger = logger 11 | } 12 | 13 | public func handler(label: String) -> XCGLoggerHandler { 14 | return XCGLoggerHandler(label: label, logger: logger) 15 | } 16 | 17 | public static func logger(label: String, logger:XCGLogger) -> Logger { 18 | let logging = XCGLogging(logger: logger) 19 | return Logger(label: label, factory: logging.handler) 20 | } 21 | } 22 | 23 | /// `XCGLoggerHandler` is an implementation of `LogHandler` that makes use of the XCGLogger. 24 | public struct XCGLoggerHandler: LogHandler { 25 | private var xcgLogger:XCGLogger 26 | private var label: String 27 | 28 | public var logLevel: Logger.Level = .info 29 | 30 | public var metadata = Logger.Metadata() 31 | 32 | public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { 33 | get { 34 | return self.metadata[metadataKey] 35 | } 36 | set { 37 | self.metadata[metadataKey] = newValue 38 | } 39 | } 40 | 41 | public init(label: String, logger:XCGLogger) { 42 | self.label = label 43 | self.xcgLogger = logger 44 | } 45 | 46 | public func log(level: Logger.Level, 47 | message: Logger.Message, 48 | metadata: Logger.Metadata?, 49 | source: String, 50 | file: String = #file, 51 | function: String = #function, 52 | line: UInt = #line) { 53 | 54 | let xcgLoggerLevel = convert(level: level) 55 | xcgLogger.logln(xcgLoggerLevel, functionName: function, fileName: file, lineNumber: Int(line), userInfo: metadata ?? self.metadata) { 56 | return message 57 | } 58 | } 59 | 60 | func convert(level: Logger.Level) -> XCGLogger.Level { 61 | switch level { 62 | case .trace: 63 | return .verbose 64 | 65 | case .debug: 66 | return .debug 67 | 68 | case .info: 69 | return .info 70 | 71 | case .notice: 72 | return .notice 73 | 74 | case .warning: 75 | return .warning 76 | 77 | case .error: 78 | return .error 79 | 80 | case .critical: 81 | return .severe 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import swift_log_fileTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += swift_log_fileTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/swift-log-fileTests/Utilities.swift: -------------------------------------------------------------------------------- 1 | 2 | import Foundation 3 | 4 | enum TestError: Error { 5 | case noDocumentDirectory 6 | case cannotGetFileSize 7 | } 8 | 9 | protocol Utilities { 10 | } 11 | 12 | extension Utilities { 13 | func getDocumentsDirectory() throws -> URL { 14 | let paths = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask) 15 | guard paths.count > 0 else { 16 | throw TestError.noDocumentDirectory 17 | } 18 | 19 | return paths[0] 20 | } 21 | 22 | func getFileSize(file: URL) throws -> UInt64 { 23 | let attr = try FileManager.default.attributesOfItem(atPath: file.path) 24 | guard let fileSize = attr[FileAttributeKey.size] as? UInt64 else { 25 | throw TestError.cannotGetFileSize 26 | } 27 | 28 | return fileSize 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/swift-log-fileTests/swift_log_fileTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FileLogging 3 | @testable import Logging 4 | 5 | final class swift_log_fileTests: XCTestCase, Utilities { 6 | let logFileName = "LogFile.txt" 7 | 8 | func testLogToFileUsingBootstrap() throws { 9 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 10 | print("\(logFileURL)") 11 | let fileLogger = try FileLogging(to: logFileURL) 12 | // Using `bootstrapInternal` so that running `swift test` won't fail. If using this in production code, just use `bootstrap`. 13 | LoggingSystem.bootstrapInternal(fileLogger.handler) 14 | 15 | let logger = Logger(label: "Test") 16 | 17 | // Not really an error. 18 | logger.error("Test Test Test") 19 | 20 | try? FileManager.default.removeItem(at: logFileURL) 21 | } 22 | 23 | func testLogToFileAppendsAcrossLoggerCalls() throws { 24 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 25 | print("\(logFileURL)") 26 | let fileLogger = try FileLogging(to: logFileURL) 27 | // Using `bootstrapInternal` so that running `swift test` won't fail. If using this in production code, just use `bootstrap`. 28 | LoggingSystem.bootstrapInternal(fileLogger.handler) 29 | let logger = Logger(label: "Test") 30 | 31 | // Not really an error. 32 | logger.error("Test Test Test") 33 | let fileSize1 = try getFileSize(file: logFileURL) 34 | 35 | logger.error("Test Test Test") 36 | let fileSize2 = try getFileSize(file: logFileURL) 37 | 38 | XCTAssert(fileSize2 > fileSize1) 39 | try? FileManager.default.removeItem(at: logFileURL) 40 | } 41 | 42 | func testLogToFileAppendsAcrossConstructorCalls() throws { 43 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 44 | print("\(logFileURL)") 45 | let fileLogger = try FileLogging(to: logFileURL) 46 | 47 | let logger1 = Logger(label: "Test", factory: fileLogger.handler) 48 | logger1.error("Test Test Test") 49 | let fileSize1 = try getFileSize(file: logFileURL) 50 | 51 | let logger2 = Logger(label: "Test", factory: fileLogger.handler) 52 | logger2.error("Test Test Test") 53 | let fileSize2 = try getFileSize(file: logFileURL) 54 | 55 | XCTAssert(fileSize2 > fileSize1) 56 | try? FileManager.default.removeItem(at: logFileURL) 57 | } 58 | 59 | // Adapted from https://nshipster.com/swift-log/ 60 | func testLogToBothFileAndConsole() throws { 61 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 62 | let fileLogger = try FileLogging(to: logFileURL) 63 | 64 | LoggingSystem.bootstrap { label in 65 | let handlers:[LogHandler] = [ 66 | FileLogHandler(label: label, fileLogger: fileLogger), 67 | StreamLogHandler.standardOutput(label: label) 68 | ] 69 | 70 | return MultiplexLogHandler(handlers) 71 | } 72 | 73 | let logger = Logger(label: "Test") 74 | 75 | // TODO: Manually check that the output also shows up in the Xcode console. 76 | logger.error("Test Test Test") 77 | try? FileManager.default.removeItem(at: logFileURL) 78 | } 79 | 80 | func testLoggingUsingLoggerFactoryConstructor() throws { 81 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 82 | let fileLogger = try FileLogging(to: logFileURL) 83 | 84 | let logger = Logger(label: "Test", factory: fileLogger.handler) 85 | 86 | logger.error("Test Test Test") 87 | let fileSize1 = try getFileSize(file: logFileURL) 88 | 89 | logger.error("Test Test Test") 90 | let fileSize2 = try getFileSize(file: logFileURL) 91 | 92 | XCTAssert(fileSize2 > fileSize1) 93 | try? FileManager.default.removeItem(at: logFileURL) 94 | } 95 | 96 | func testLoggingUsingConvenienceMethod() throws { 97 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 98 | 99 | let logger = try FileLogging.logger(label: "Foobar", localFile: logFileURL) 100 | 101 | logger.error("Test Test Test") 102 | let fileSize1 = try getFileSize(file: logFileURL) 103 | 104 | logger.error("Test Test Test") 105 | let fileSize2 = try getFileSize(file: logFileURL) 106 | 107 | XCTAssert(fileSize2 > fileSize1) 108 | try? FileManager.default.removeItem(at: logFileURL) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/swift-log-fileTests/swift_log_xcgloggerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift_log_xcgloggerTests.swift 3 | // swift-log-fileTests 4 | // 5 | // Created by Christopher G Prince on 2/6/21. 6 | // 7 | 8 | import XCTest 9 | @testable import FileLogging 10 | @testable import Logging 11 | import XCGLogger 12 | 13 | class swift_log_xcgloggerTests: XCTestCase, Utilities { 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | let logFileName = "LogFile.txt" 23 | 24 | func testLogToFileUsingBootstrap() throws { 25 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 26 | print("\(logFileURL)") 27 | 28 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 29 | let fileLogger = XCGLogging(logger: xcgLogger) 30 | 31 | // Using `bootstrapInternal` so that running `swift test` won't fail. If using this in production code, just use `bootstrap`. 32 | LoggingSystem.bootstrapInternal(fileLogger.handler) 33 | 34 | let logger = Logger(label: "Test") 35 | 36 | logger.error("Test Test Test") 37 | } 38 | 39 | func testLogToFileAppendsAcrossLoggerCalls() throws { 40 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 41 | print("\(logFileURL)") 42 | 43 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 44 | let fileLogger = XCGLogging(logger: xcgLogger) 45 | 46 | // Using `bootstrapInternal` so that running `swift test` won't fail. If using this in production code, just use `bootstrap`. 47 | LoggingSystem.bootstrapInternal(fileLogger.handler) 48 | let logger = Logger(label: "Test") 49 | 50 | // Not really an error. 51 | logger.error("Test Test Test") 52 | let fileSize1 = try getFileSize(file: logFileURL) 53 | 54 | logger.error("Test Test Test") 55 | let fileSize2 = try getFileSize(file: logFileURL) 56 | 57 | XCTAssert(fileSize2 > fileSize1) 58 | try? FileManager.default.removeItem(at: logFileURL) 59 | } 60 | 61 | func testLogToFileAppendsAcrossConstructorCalls() throws { 62 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 63 | print("\(logFileURL)") 64 | 65 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 66 | let fileLogger = XCGLogging(logger: xcgLogger) 67 | 68 | let logger1 = Logger(label: "Test", factory: fileLogger.handler) 69 | logger1.error("Test Test Test") 70 | let fileSize1 = try getFileSize(file: logFileURL) 71 | 72 | let logger2 = Logger(label: "Test", factory: fileLogger.handler) 73 | logger2.error("Test Test Test") 74 | let fileSize2 = try getFileSize(file: logFileURL) 75 | 76 | XCTAssert(fileSize2 > fileSize1) 77 | try? FileManager.default.removeItem(at: logFileURL) 78 | } 79 | 80 | func testLogToBothFileAndConsole() throws { 81 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 82 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 83 | 84 | LoggingSystem.bootstrap { label in 85 | let handlers:[LogHandler] = [ 86 | XCGLoggerHandler(label: label, logger: xcgLogger), 87 | StreamLogHandler.standardOutput(label: label) 88 | ] 89 | 90 | return MultiplexLogHandler(handlers) 91 | } 92 | 93 | let logger = Logger(label: "Test") 94 | 95 | // TODO: Manually check that the output also shows up in the Xcode console. 96 | logger.error("Test Test Test Boogba") 97 | try? FileManager.default.removeItem(at: logFileURL) 98 | } 99 | 100 | func testLoggingUsingLoggerFactoryConstructor() throws { 101 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 102 | 103 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 104 | let fileLogger = XCGLogging(logger: xcgLogger) 105 | 106 | let logger = Logger(label: "Test", factory: fileLogger.handler) 107 | 108 | logger.error("Test Test Test") 109 | let fileSize1 = try getFileSize(file: logFileURL) 110 | 111 | logger.error("Test Test Test") 112 | let fileSize2 = try getFileSize(file: logFileURL) 113 | 114 | XCTAssert(fileSize2 > fileSize1) 115 | try? FileManager.default.removeItem(at: logFileURL) 116 | } 117 | 118 | func testLoggingUsingConvenienceMethod() throws { 119 | let logFileURL = try getDocumentsDirectory().appendingPathComponent(logFileName) 120 | 121 | let xcgLogger = makeFileXCGLogger(logFileURL: logFileURL) 122 | let logger = XCGLogging.logger(label: "Test", logger: xcgLogger) 123 | 124 | logger.error("Test Test Test") 125 | let fileSize1 = try getFileSize(file: logFileURL) 126 | 127 | logger.error("Test Test Test") 128 | let fileSize2 = try getFileSize(file: logFileURL) 129 | 130 | XCTAssert(fileSize2 > fileSize1) 131 | try? FileManager.default.removeItem(at: logFileURL) 132 | } 133 | 134 | // MARK: Helpers 135 | 136 | func makeFileXCGLogger(logFileURL: URL, level: XCGLogger.Level = .verbose) -> XCGLogger { 137 | // Create a logger object with no destinations 138 | let log = XCGLogger(identifier: "advancedLogger", includeDefaultDestinations: false) 139 | 140 | // Create a file log destination 141 | let fileDestination = AutoRotatingFileDestination(writeToFile: logFileURL.path, identifier: "advancedLogger.fileDestination", shouldAppend: true) 142 | 143 | // Optionally set some configuration options 144 | fileDestination.outputLevel = level 145 | fileDestination.showLogIdentifier = false 146 | fileDestination.showFunctionName = true 147 | fileDestination.showThreadName = true 148 | fileDestination.showLevel = true 149 | fileDestination.showFileName = true 150 | fileDestination.showLineNumber = true 151 | fileDestination.showDate = true 152 | 153 | // Trying to get max total log size that could be sent to developer to be around 1MByte; this comprises one current log file and two archived log files. 154 | fileDestination.targetMaxFileSize = (1024 * 1024) / 3 // 1/3 MByte 155 | 156 | // These are archived log files. 157 | fileDestination.targetMaxLogFiles = 2 158 | 159 | // Process this destination in the background 160 | fileDestination.logQueue = XCGLogger.logQueue 161 | 162 | // Add the destination to the logger 163 | log.add(destination: fileDestination) 164 | 165 | // Add basic app info, version info etc, to the start of the logs 166 | log.logAppDetails() 167 | return log 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Tools/addVersion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RELEASE_TAG=$1 4 | 5 | if [ "${RELEASE_TAG}nothing" == "nothing" ]; then 6 | echo "Please give a release tag, e.g., 0.12.1" 7 | exit 8 | fi 9 | 10 | echo "Adding release tag: $RELEASE_TAG" 11 | 12 | git add -A 13 | git commit -m "version $RELEASE_TAG" 14 | git tag -a "$RELEASE_TAG" -m "version $RELEASE_TAG" 15 | git push 16 | git push --tags 17 | -------------------------------------------------------------------------------- /VERSIONS.txt: -------------------------------------------------------------------------------- 1 | swift-log-file 2 | 3 | Version 0.1.0 (2/7/21) 4 | * Added support for XCGLogging 5 | 6 | Version 0.0.2 (1/28/21) 7 | * Beta testing --------------------------------------------------------------------------------