-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature] LogKit 생성 및 로그 출력 방식 일괄 통일 #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
NeoSelf1
wants to merge
25
commits into
develop
Choose a base branch
from
feature/logKit
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
742aeb8
[Feature] DiskCache 클래스 구현 (#109)
NeoSelf1 3cf7d37
[Delete] 불필요 라이브러리 import문 제거 (#109)
NeoSelf1 cab7e9d
[Refactor] 동시성 제어 관련 컴파일 에러 해결 (#109)
NeoSelf1 2d105ab
[Refactor] Actor 어노테이션 불필요한 곳 수정 (#109)
NeoSelf1 722a706
[Feat] ImageProcessor 추가 (#109)
NeoSelf1 5e82583
[Feat] ImageWrapper 추가 (#109)
NeoSelf1 daddb31
[Feat] ImageDownloader 구현 (#109)
NeoSelf1 852c52c
[Fix] Task-isolated options 관련 에러 수정 위해 임시 커밋 (#109)
NeoSelf1 152b503
[Fix] Concurrency 문제 해결 (#109)
NeoSelf1 c1006f6
[Fix] NeoImageWrapper 자체를 Sendable하게 변경하여 해결 (#109)
NeoSelf1 ee1273c
[Refactor] ImageCache 패키지 구현 (#109)
NeoSelf1 f636f62
[Refactor] 디렉토리 구조 변경 (#109)
NeoSelf1 0942e0b
[Feat] 메모리 및 디스크 저장소 조회하여 기존 이미지 가져오는 로직 추가 (#109)
NeoSelf1 7e72a5c
[Refactor] BookMatchKit loggers 초기과 구문 임시 저장
NeoSelf1 b2922a3
[Feat] LogCategory, LogSubsystem, LogLevel 열거형 정의
NeoSelf1 f56862b
[Feat] LogKitActor 및 LogKit 클래스 구현체 구현
NeoSelf1 a50887a
[Feat] LogKit Facade 계층 구현
NeoSelf1 72831f4
[Refactor] CSV 파일 형식에 맞춰 다중라인 출력 String문 단일라인으로 수정
NeoSelf1 422e571
[Delete] 각종 모듈에서 사용되고 있던 Logger 일괄 제거 및 LogKit로 통합
NeoSelf1 f345304
[Refactor] 각 패키지에 대해 LogKit 패키지 의존성 추가
NeoSelf1 6d43051
[Refactor] Log 관련 메서드 모두 LogKit 패키지 메서드로 일괄 변경
NeoSelf1 610e367
[Refactor] DesignSystem 라이브러리 내부 Kingfisher 라이브러리 의존성 제거 및 LogKit 내부 …
NeoSelf1 4a9ef1f
[Feat] Swift 6 점진적 대응 위한 컴파일 버전 낮추기
ericKwon95 7d1c4bb
[Feat] FIFO 순서의 로깅 위한 시리얼큐 적용
ericKwon95 67f3355
[Refactor] 파일 쓰기, 로그 엔진, 로그 엔진 래퍼로 역할 및 파일 분리
ericKwon95 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,74 +6,80 @@ struct LoggerKey: Hashable { | |
| let category: LogCategory | ||
| } | ||
|
|
||
| /// 단순히 전역 액터 속성만 정의 | ||
| /// 단순히 전역 액터 속성만 정의하는 것이 아니라, 로깅 인터페이스도 제공 | ||
| @globalActor | ||
| public actor LogKitActor { | ||
| // MARK: - Static Properties | ||
|
|
||
| public static let shared = LogKitActor() | ||
|
|
||
| // MARK: - Properties | ||
|
|
||
| /// _LogKit의 인스턴스를 직접 관리 | ||
| private let logKit = _LogKit() | ||
|
|
||
| // MARK: - Functions | ||
|
|
||
| /// _LogKit의 메서드를 액터 내부에서 호출 | ||
| public func log( | ||
| _ level: LogLevel, | ||
| message: String, | ||
| subSystem: LogSubSystem = .app, | ||
| category: LogCategory = .general, | ||
| file: String = #file, | ||
| function: String = #function, | ||
| line: Int = #line | ||
| ) { | ||
| // 액터 내부에서는 await 없이 동기적으로 호출 가능 | ||
| logKit.log( | ||
| level, | ||
| message: message, | ||
| subSystem: subSystem, | ||
| category: category, | ||
| file: file, | ||
| function: function, | ||
| line: line | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| public final class _LogKit { | ||
| // MARK: - Static Properties | ||
| final class _LogKit { | ||
| // MARK: - Properties | ||
|
|
||
| /// @globalActor를 사용해 shared 인스턴스에만 격리 도메인을 지정하는 것은 동시성 격리를 부분적으로 선택적으로 적용 | ||
| /// 어떤 부분이 격리되어야 하고 어떤 부분이 일반 동기 코드로 실행되어도 되는지 더 세밀하게 제어 | ||
| /// 실제로 모든 코드가 항상 격리될 필요는 없기 때문에 성능상 이점도 존재 | ||
| /// 공유 자원에 대한 접근은 조정해야 하지만, 모든 기능이 액터 내부에 있을 필요는 없는 경우 적합 | ||
| @LogKitActor | ||
| static let shared = _LogKit() | ||
|
|
||
| /// logger 인스턴스를 생성하는 로직을 초기화기 내부에서 호출 하는 것은 actor 내에 actor-isolated 메서드를 동기적으로(synchronous) | ||
| /// 호출하는 것입니다. | ||
| /// Actor의 초기화 과정에서는 actor의 격리 매커니즘이 완전히 설정되지 않았기 때문에 actor-isolated 메서드를 직접 호출할 수 없습니다. | ||
| /// `actor는 공유 가변 상태에 대한 안전한 접근을 보장하기 위한 동시성 타입` | ||
| /// 이를 위해 actor 내부 모든 메서드와 프로퍼티는 기본적으로 actor-isolated 되어있기에, actor 외부에선 await 키워드와 함께 호출되어야 하며, | ||
| /// 동시에 외부에서 호출되더라도 자동으로 직렬화됩니다. | ||
| /// | ||
| /// 각 카테고리에 해당한느 로거 인스턴스를 Actor 속성이 아닌, 정적 속성으로 생성 | ||
| /// static 프로퍼티 및 메서드는 인스턴스와 무관하게 타입 자체에 속하기에 actor의 격리 메커니즘 밖에 존재하여, actor-isolation 제약을 받지 않게 | ||
| /// 됨. | ||
| private static let loggers: [LoggerKey: Logger] = { | ||
| var map: [LoggerKey: Logger] = [:] | ||
|
|
||
| for subSystem in LogSubSystem.allCases { | ||
| for category in LogCategory.allCases { | ||
| let logger = Logger(subsystem: subSystem.rawValue, category: category.rawValue) | ||
| map[LoggerKey(subSystem: subSystem, category: category)] = logger | ||
| } | ||
| } | ||
|
|
||
| return map | ||
| }() | ||
|
|
||
| private let dateFormatter: DateFormatter | ||
| private let fileManager: FileManager | ||
|
|
||
| private var loggers: [LoggerKey: Logger] = [:] | ||
|
|
||
| private let appStartTime: String | ||
| private var currentCSVFileID: Int = 1 | ||
| private var currentCSVFileID = 1 | ||
| private var currentCSVFileURL: URL? | ||
| private var currentCSVFileSize: UInt64 = 0 | ||
| private let maxCSVFileSize: UInt64 = 6 * 1024 // 6KB size limit | ||
| private let maxCSVFileSize: UInt64 = 60 * 1024 // 60KB | ||
|
|
||
| // MARK: - Lifecycle | ||
| private init() { | ||
|
|
||
| init() { | ||
| dateFormatter = DateFormatter() | ||
| dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" | ||
|
|
||
| fileManager = FileManager.default | ||
|
|
||
| let startTimeFormatter = DateFormatter() | ||
| startTimeFormatter.dateFormat = "yyyyMMdd_HHmm" | ||
| appStartTime = startTimeFormatter.string(from: Date()) | ||
|
|
||
| let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] | ||
|
|
||
|
|
||
| initializeLoggers() | ||
| createNewCSVFile() | ||
| } | ||
|
|
||
| // MARK: - Functions | ||
|
|
||
| // MARK: - Public Methods | ||
|
|
||
| func log( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. public 선언이 안 되어 있는 듯 합니당
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터널로 사용할 계획이셨군요~ 짱짱입니다! |
||
| _ level: LogLevel, | ||
| message: String, | ||
|
|
@@ -82,14 +88,14 @@ public final class _LogKit { | |
| file: String = #file, | ||
| function: String = #function, | ||
| line: Int = #line | ||
| ) async { | ||
| ) { | ||
| let fileName = (file as NSString).lastPathComponent | ||
|
|
||
| let logger = getLogger(subSystem: subSystem, category: category) | ||
| logger.log(level: level.osLogType, "[\(fileName):\(line)] \(function) - \(message)") | ||
|
|
||
| let timestamp = dateFormatter.string(from: Date()) | ||
|
|
||
| // CSV 로그 추가 | ||
| writeToCSVFile( | ||
| timestamp: timestamp, | ||
|
|
@@ -102,26 +108,40 @@ public final class _LogKit { | |
| category: category.rawValue | ||
| ) | ||
| } | ||
|
|
||
| // MARK: - Private Methods | ||
|
|
||
|
|
||
| private func initializeLoggers() { | ||
| var map: [LoggerKey: Logger] = [:] | ||
|
|
||
| for subSystem in LogSubSystem.allCases { | ||
| for category in LogCategory.allCases { | ||
| let logger = Logger(subsystem: subSystem.rawValue, category: category.rawValue) | ||
| map[LoggerKey(subSystem: subSystem, category: category)] = logger | ||
| } | ||
| } | ||
|
|
||
| loggers = map | ||
| } | ||
|
|
||
| private func getLogger(subSystem: LogSubSystem, category: LogCategory) -> Logger { | ||
| let key = LoggerKey(subSystem: subSystem, category: category) | ||
| if let logger = _LogKit.loggers[key] { | ||
|
|
||
| if let logger = loggers[key] { | ||
| return logger | ||
| } else { | ||
| return Logger(subsystem: subSystem.rawValue, category: category.rawValue) | ||
| } | ||
| } | ||
|
|
||
| private func createNewCSVFile() { | ||
| let documentsPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] | ||
| let fileName = "\(appStartTime)-\(currentCSVFileID).csv" | ||
| let fileURL = documentsPath.appendingPathComponent(fileName) | ||
|
|
||
| // CSV 헤더 생성 | ||
| let headerRow = "Timestamp,Level,FileName,Line,Function,Message,SubSystem,Category\n" | ||
|
|
||
| do { | ||
| try headerRow.write(to: fileURL, atomically: true, encoding: .utf8) | ||
| currentCSVFileURL = fileURL | ||
|
|
@@ -131,31 +151,45 @@ public final class _LogKit { | |
| print("CSV 로그 파일 생성 실패: \(error)") | ||
| } | ||
| } | ||
|
|
||
| private func writeToCSVFile(timestamp: String, level: String, fileName: String, line: String, function: String, message: String, subSystem: String, category: String) { | ||
|
|
||
| private func writeToCSVFile( | ||
| timestamp: String, | ||
| level: String, | ||
| fileName: String, | ||
| line: String, | ||
| function: String, | ||
| message: String, | ||
| subSystem: String, | ||
| category: String | ||
| ) { | ||
| let escapedMessage = message.replacingOccurrences(of: "\"", with: "\"\"") | ||
| let escapedFunction = function.replacingOccurrences(of: "\"", with: "\"\"") | ||
|
|
||
| // CSV 행 생성 | ||
| let csvRow = "\"\(timestamp)\",\"\(level)\",\"\(fileName)\",\"\(line)\",\"\(escapedFunction)\",\"\(escapedMessage)\",\"\(subSystem)\",\"\(category)\"\n" | ||
|
|
||
| guard let csvData = csvRow.data(using: .utf8) else { return } | ||
| let csvRow = | ||
| "\"\(timestamp)\",\"\(level)\",\"\(fileName)\",\"\(line)\",\"\(escapedFunction)\",\"\(escapedMessage)\",\"\(subSystem)\",\"\(category)\"\n" | ||
|
|
||
| guard let csvData = csvRow.data(using: .utf8) else { | ||
| return | ||
| } | ||
| let dataSize = UInt64(csvData.count) | ||
|
|
||
| // 현재 파일이 최대 크기를 초과하는지 확인 | ||
| if currentCSVFileSize + dataSize > maxCSVFileSize { | ||
| currentCSVFileID += 1 | ||
| createNewCSVFile() | ||
| } | ||
|
|
||
| // CSV 파일에 로그 추가 | ||
| guard let fileURL = currentCSVFileURL else { return } | ||
|
|
||
| guard let fileURL = currentCSVFileURL else { | ||
| return | ||
| } | ||
|
|
||
| if let fileHandle = try? FileHandle(forWritingTo: fileURL) { | ||
| fileHandle.seekToEndOfFile() | ||
| fileHandle.write(csvData) | ||
| try? fileHandle.close() | ||
|
|
||
| currentCSVFileSize += dataSize | ||
| } | ||
| } | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FileManager 자체가 thread-safe한 것으로 알고 있는데, actor로 한 번 더 감싸주신 이유가 궁금합니다~!