├── .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
--------------------------------------------------------------------------------