Skip to content

Commit

Permalink
Merge pull request #7 from KU-Stacks/feature/lgvv/dev
Browse files Browse the repository at this point in the history
[1.0.2] Logger의 message를 외부에서 사용할 수 있도록 구현
  • Loading branch information
x-0o0 authored Feb 6, 2023
2 parents c37cc49 + f6998bf commit dcd465d
Show file tree
Hide file tree
Showing 105 changed files with 306 additions and 56 deletions.
7 changes: 4 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ let package = Package(
.process("Assets.xcassets")
]
),
// .testTarget(
// name: "KuringCommonsTests",
// dependencies: ["KuringCommons"]),
.testTarget(
name: "KuringCommonsTests",
dependencies: ["KuringCommons"]
),
]
)
46 changes: 46 additions & 0 deletions Sources/KuringCommons/Log.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Log.swift
//
//
// Created by Jaesung Lee on 2023/02/06.
//

/**
MIT License

Copyright (c) 2023 Kuring

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

import Foundation

/// 로그를 나타내는 구조체입니다.
public struct Log: Hashable {
public let publisher: String = "[com.kuring.service]"
/// 로그가 기록된 시간입니다.
public let time: String
/// 로그에 담긴 메세지입니다.
public let message: String

public init(time: String, message: String) {
self.time = time
self.message = message
}
}
63 changes: 63 additions & 0 deletions Sources/KuringCommons/LogCollector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// LogCollector.swift
//
//
// Created by Jaesung Lee on 2023/02/06.
//

/**
MIT License

Copyright (c) 2023 Kuring

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

import Foundation

/// ``Log``를 수집합니다.
public class LogCollector: ObservableObject {
/// ``Log`` 를 수집하기 위한 규칙입니다.
public enum CollectingRule: Hashable {
/// 프로젝트의 Build configuration 에 상관없이 로그를 저장합니다.
case none
/// 프로젝트의 Build configuration 이 `debug` 일 때만 로그를 저장합니다.
case development
/// 프로젝트의 Build configuration 이 `Release` 일 때만 로그를 저장합니다.
case production
}
/// 가장 기본적인 ``LogCollector`` 객체 입니다. 프로젝트의 Build configuration 상관없이 **모든** 로그를 수집합니다.
public static let main = LogCollector(
id: "com.kuring.commons",
collectingRule: .none
)
/// 고유식별자.
public let id: String
/// ``Log`` 를 수집하기 위한 규칙입니다.
public let collectingRule: CollectingRule

public init(id: String, collectingRule: CollectingRule) {
self.id = id
self.collectingRule = collectingRule
}

/// 수집된 ``Log`` 객체들입니다.
@Published
public var logs: [Log] = []
}
53 changes: 47 additions & 6 deletions Sources/KuringCommons/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public class Logger {
case production
}

/// 로그를 모아두는 컬렉터들입니다. Key-Value 형태로 저장합니다.
public static var collectors: [String: LogCollector] = [:]

/// 로거에 주입할 커스텀 액션이 실행될 큐 입니다. ``Logger`` 에서 제공되는 기본 로그 메세지는 별도의 큐를 지정하지 않습니다.
/// ```swift
/// Logger.queue.async {
Expand All @@ -56,7 +59,7 @@ public class Logger {
/// ```
public static var printableModes: [RunMode] = [.development]

/// `log` 와 함께 일반 로그 메세지를 출력하고 `action` 을 실행합니다.
/// `log` 와 함께 일반 로그 메세지를 출력하고 `action` 을 실행합니다. `collectorIDs` 를 지정하면 해당 `LogCollector` 에 `log`가 저장됩니다.
/// ```swift
/// Logger.debug("Hi") {
/// // 파이어베이스, 앱스플라이어 등의 추가적인 로거를 사용할 수 있습니다.
Expand All @@ -65,7 +68,7 @@ public class Logger {
/// // [com.kuring.service] [2022년 5월 4일 수요일 오후 11:23:00 GMT+9]
/// // ✅ Hi
/// ```
public static func debug(_ log: Any?, action: (() -> Void)? = nil) {
public static func debug(_ log: Any?, collectorIDs: [String] = [], action: (() -> Void)? = nil) {
let time: String
if #available(iOS 15.0, *) {
time = Date().formatted(
Expand All @@ -77,19 +80,38 @@ public class Logger {
dateFormatter.dateFormat = "yyyy-MM-dd-EE hh:mm a"
time = dateFormatter.string(from: Date())
}
LogCollector.main.logs.append(
Log(time: "[\(time)]", message: "\(String(describing: log))")
)
#if DEBUG
guard Logger.printableModes.contains(.development) else { return }
print("[com.kuring.service] [\(time)]\n\(String(describing: log))")

collectorIDs.forEach { id in
guard let collector = Logger.collectors[id] else { return }
guard collector.collectingRule == .development else { return }
collector.logs.append(
Log(time: "[\(time)]", message: "\(String(describing: log))")
)
}
#else
Logger.queue.async {
action?()
}
guard Logger.printableModes.contains(.production) else { return }
print("[com.kuring.service] [\(time)]\n\(String(describing: log))")

collectorIDs.forEach { id in
guard let collector = Logger.collectors[id] else { return }
guard collector.collectingRule == .production else { return }
collector.logs.append(
Log(time: "[\(time)]", message: "\(String(describing: log))")
)
}
#endif
}

/// `log` 와 함께 에러메세지를 출력하고 `action` 을 실행합니다.
/// `log` 와 함께 에러메세지를 출력하고 `action` 을 실행합니다. `collectorIDs` 를 지정하면 해당 `LogCollector` 에 `log`가 저장됩니다.
/// ```swift
/// Logger.error("Hi") {
/// // 파이어베이스, 앱스플라이어 등의 추가적인 로거를 사용할 수 있습니다.
Expand All @@ -98,7 +120,7 @@ public class Logger {
/// // [com.kuring.service] [2022년 5월 4일 수요일 오후 11:23:00 GMT+9]
/// // 🚨 Hi
/// ```
public static func error(_ log: String, action: (() -> Void)? = nil) {
public static func error(_ log: String, collectorIDs: [String] = [], action: (() -> Void)? = nil) {
let time: String
if #available(iOS 15.0, *) {
time = Date().formatted(
Expand All @@ -110,15 +132,34 @@ public class Logger {
dateFormatter.dateFormat = "yyyy-MM-dd-EE hh:mm a"
time = dateFormatter.string(from: Date())
}
LogCollector.main.logs.append(
Log(time: "[\(time)]", message: "🚨 \(log)")
)
#if DEBUG
guard Logger.printableModes.contains(.development) else { return }
print("[com.kuring.service] [\(time)]\n🚨 \(log)")

collectorIDs.forEach { id in
guard let collector = Logger.collectors[id] else { return }
guard collector.collectingRule == .development else { return }
collector.logs.append(
Log(time: "[\(time)]", message: "🚨 \(log)")
)
}
#else
Logger.queue.async {
action?()
}
guard Logger.printableModes.contains(.production) else { return }
print("[com.kuring.service] [\(time)]\n🚨 \(log))")

collectorIDs.forEach { id in
guard let collector = Logger.collectors[id] else { return }
guard collector.collectingRule == .production else { return }
collector.logs.append(
Log(time: "[\(time)]", message: "🚨 \(log)")
)
}
#endif
}

Expand All @@ -129,8 +170,8 @@ public class Logger {
/// if let error = error { return }
/// }
/// ```
public static func error(_ error: Error?, action: (() -> Void)? = nil) {
public static func error(_ error: Error?, collectorIDs: [String] = [], action: (() -> Void)? = nil) {
guard let error = error else { return }
self.error(error.localizedDescription, action: action)
self.error(error.localizedDescription, collectorIDs: collectorIDs, action: action)
}
}
55 changes: 55 additions & 0 deletions Tests/KuringCommonsTests/LoggerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// LoggerTests.swift
// KuringCommonsTests
//
// Created by Jaesung Lee on 2022/05/01.
//

import XCTest
@testable import KuringCommons

class LoggerTests: XCTestCase {
let expectedGreen = UIColor(red: 61 / 255, green: 189 / 255, blue: 128 / 255, alpha: 1.0)
let expectedPink = UIColor(red: 228 / 255, green: 91 / 255, blue: 120 / 255, alpha: 1.0)

/// `LogCollector.main` 은 `Logger.printableModes` 와 상관없이 **모든 로그를 저장**해야합니다.
/// **기대결과**: 이 테스트는 `LogCollector.main` 에 로그가 저장되어야 합니다.
func test_logCollector() throws {
let message = UUID().uuidString
Logger.debug(message)
let collectedLogs = LogCollector.main.logs
let targetLog = collectedLogs.first { $0.message.contains(message) }
XCTAssert(targetLog != nil, "현재 저장된 로그는 \(collectedLogs.compactMap { $0.message }) 이고 확인하려는 로그는 \(message) 입니다")
}

/// `LogCollector.main` 은 `Logger.printableModes` 와 상관없이 **모든 로그를 저장**해야합니다.
/// `collectorForProd` 은 `Logger.printableModes` 가 `.production` 을 포함할때만 **로그를 저장**해야합니다.
/// `collectorForDev` 은 `Logger.printableModes` 가 `.development` 을 포함할때만 **로그를 저장**해야합니다.
/// **기대결과**: 이 테스트는 `.development` 에서 테스트 하므로 `LogCollector.main` 와 `collectorForDev` 에만 로그가 저장되어야 합니다.
func test_customLogCollector() throws {
let prodID = UUID().uuidString
let devID = UUID().uuidString
let collectorForProd = LogCollector(id: prodID, collectingRule: .production)
let collectorForDev = LogCollector(id: devID, collectingRule: .development)

Logger.collectors = [
prodID: collectorForProd,
devID: collectorForDev
]
Logger.printableModes = [.development]

let message = UUID().uuidString
Logger.debug(message, collectorIDs: [prodID, devID])
let collectedLogs1 = LogCollector.main.logs
let collectedLogs2 = collectorForProd.logs // Prod
let collectedLogs3 = collectorForDev.logs // Dev

let targetLog1 = collectedLogs1.first { $0.message.contains(message) }
let targetLog2 = collectedLogs2.first { $0.message.contains(message) } // Prod
let targetLog3 = collectedLogs3.first { $0.message.contains(message) } // Dev

XCTAssertNotNil(targetLog1, "현재 저장된 로그는 \(collectedLogs1.compactMap { $0.message }) 이고 확인하려는 로그는 \(message) 입니다")
XCTAssertNil(targetLog2, "현재 저장된 로그는 \(collectedLogs2.compactMap { $0.message }) 이고 확인하려는 로그는 \(message) 입니다")
XCTAssertNotNil(targetLog3, "현재 저장된 로그는 \(collectedLogs3.compactMap { $0.message }) 이고 확인하려는 로그는 \(message) 입니다")
}
}
Loading

0 comments on commit dcd465d

Please # to comment.