Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
742aeb8
[Feature] DiskCache 클래스 구현 (#109)
NeoSelf1 Feb 22, 2025
3cf7d37
[Delete] 불필요 라이브러리 import문 제거 (#109)
NeoSelf1 Feb 22, 2025
cab7e9d
[Refactor] 동시성 제어 관련 컴파일 에러 해결 (#109)
NeoSelf1 Feb 22, 2025
2d105ab
[Refactor] Actor 어노테이션 불필요한 곳 수정 (#109)
NeoSelf1 Feb 22, 2025
722a706
[Feat] ImageProcessor 추가 (#109)
NeoSelf1 Feb 24, 2025
5e82583
[Feat] ImageWrapper 추가 (#109)
NeoSelf1 Feb 24, 2025
daddb31
[Feat] ImageDownloader 구현 (#109)
NeoSelf1 Feb 24, 2025
852c52c
[Fix] Task-isolated options 관련 에러 수정 위해 임시 커밋 (#109)
NeoSelf1 Feb 24, 2025
152b503
[Fix] Concurrency 문제 해결 (#109)
NeoSelf1 Feb 24, 2025
c1006f6
[Fix] NeoImageWrapper 자체를 Sendable하게 변경하여 해결 (#109)
NeoSelf1 Feb 24, 2025
ee1273c
[Refactor] ImageCache 패키지 구현 (#109)
NeoSelf1 Feb 24, 2025
f636f62
[Refactor] 디렉토리 구조 변경 (#109)
NeoSelf1 Feb 26, 2025
0942e0b
[Feat] 메모리 및 디스크 저장소 조회하여 기존 이미지 가져오는 로직 추가 (#109)
NeoSelf1 Feb 26, 2025
7e72a5c
[Refactor] BookMatchKit loggers 초기과 구문 임시 저장
NeoSelf1 Mar 2, 2025
b2922a3
[Feat] LogCategory, LogSubsystem, LogLevel 열거형 정의
NeoSelf1 Mar 2, 2025
f56862b
[Feat] LogKitActor 및 LogKit 클래스 구현체 구현
NeoSelf1 Mar 2, 2025
a50887a
[Feat] LogKit Facade 계층 구현
NeoSelf1 Mar 2, 2025
72831f4
[Refactor] CSV 파일 형식에 맞춰 다중라인 출력 String문 단일라인으로 수정
NeoSelf1 Mar 2, 2025
422e571
[Delete] 각종 모듈에서 사용되고 있던 Logger 일괄 제거 및 LogKit로 통합
NeoSelf1 Mar 2, 2025
f345304
[Refactor] 각 패키지에 대해 LogKit 패키지 의존성 추가
NeoSelf1 Mar 2, 2025
6d43051
[Refactor] Log 관련 메서드 모두 LogKit 패키지 메서드로 일괄 변경
NeoSelf1 Mar 2, 2025
610e367
[Refactor] DesignSystem 라이브러리 내부 Kingfisher 라이브러리 의존성 제거 및 LogKit 내부 …
NeoSelf1 Mar 2, 2025
4a9ef1f
[Feat] Swift 6 점진적 대응 위한 컴파일 버전 낮추기
ericKwon95 Mar 18, 2025
7d1c4bb
[Feat] FIFO 순서의 로깅 위한 시리얼큐 적용
ericKwon95 Mar 18, 2025
67f3355
[Refactor] 파일 쓰기, 로그 엔진, 로그 엔진 래퍼로 역할 및 파일 분리
ericKwon95 Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions BookKitty/BookKitty.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
E5A6B99E2D5F54C300A2E06D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E5A6B99C2D5F54C300A2E06D /* PrivacyInfo.xcprivacy */; };
E5DE19642D62A7D3007D37E2 /* BookOCRKit in Frameworks */ = {isa = PBXBuildFile; productRef = E5DE19632D62A7D3007D37E2 /* BookOCRKit */; };
E5FC698C2D52414B002875FD /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = E5FC698B2D52414B002875FD /* SnapKit */; };
E5FFE2F02D6A3F4200A0F7CF /* NeoImage in Frameworks */ = {isa = PBXBuildFile; productRef = E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */; };
E93048662D559553008E9467 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = E93048652D559553008E9467 /* RxCocoa */; };
E97DC3702D50C161009ADFEA /* DesignSystem in Frameworks */ = {isa = PBXBuildFile; productRef = E97DC36F2D50C161009ADFEA /* DesignSystem */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -87,6 +88,7 @@
606DA2402D4206F200C7FAA3 /* RxRelay in Frameworks */,
E97DC3702D50C161009ADFEA /* DesignSystem in Frameworks */,
4584C5AF2D685AB300173282 /* FirebaseAnalytics in Frameworks */,
E5FFE2F02D6A3F4200A0F7CF /* NeoImage in Frameworks */,
60A1CC0E2D54A2DB00091568 /* BookRecommendationKit in Frameworks */,
606DA2422D4206F200C7FAA3 /* RxSwift in Frameworks */,
4584C5B32D685AB300173282 /* FirebaseCrashlytics in Frameworks */,
Expand Down Expand Up @@ -170,6 +172,7 @@
4584C5AE2D685AB300173282 /* FirebaseAnalytics */,
4584C5B02D685AB300173282 /* FirebaseCore */,
4584C5B22D685AB300173282 /* FirebaseCrashlytics */,
E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */,
);
productName = BookKitty;
productReference = 60551C652D40E6E800CFC16A /* BookKitty.app */;
Expand Down Expand Up @@ -346,7 +349,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUHZ238M29;
DEVELOPMENT_TEAM = H856SYKNM8;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BookKitty/App/Info.plist;
Expand Down Expand Up @@ -385,7 +388,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUHZ238M29;
DEVELOPMENT_TEAM = H856SYKNM8;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BookKitty/App/Info.plist;
Expand Down Expand Up @@ -704,6 +707,10 @@
package = E5FC698A2D52414B002875FD /* XCRemoteSwiftPackageReference "SnapKit" */;
productName = SnapKit;
};
E5FFE2EF2D6A3F4200A0F7CF /* NeoImage */ = {
isa = XCSwiftPackageProductDependency;
productName = NeoImage;
};
E93048652D559553008E9467 /* RxCocoa */ = {
isa = XCSwiftPackageProductDependency;
package = 606DA23C2D4206F200C7FAA3 /* XCRemoteSwiftPackageReference "RxSwift" */;
Expand Down
4 changes: 2 additions & 2 deletions BookKitty/BookKitty/DesignSystem/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let package = Package(
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"),
.package(url: "https://github.com/devxoul/Then.git", from: "3.0.0"),
.package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.5.1"),
.package(url: "https://github.com/onevcat/Kingfisher.git", from: "8.2.0"),
.package(path: "../NeoImage"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand All @@ -31,7 +31,7 @@ let package = Package(
.product(name: "SnapKit", package: "SnapKit"),
.product(name: "Then", package: "Then"),
.product(name: "Lottie", package: "lottie-spm"),
.product(name: "Kingfisher", package: "Kingfisher"),
.product(name: "NeoImage", package: "NeoImage"),
],
resources: [
.process("Resource/Fonts"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import Kingfisher
import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -95,14 +95,16 @@ extension FlexibleImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import Kingfisher
import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -110,14 +110,16 @@ extension HeightFixedImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 임성수 on 2/4/25.
//

import NeoImage
import SnapKit
import UIKit

Expand Down Expand Up @@ -107,14 +108,16 @@ extension WidthFixedImageView {
return
}

kf.setImage(
let options = NeoImageOptions(
transition: .fade(0.2),
retryStrategy: .times(3)
)

neo.setImage(
with: url,
placeholder: UIImage(named: "DefaultBookImage", in: Bundle.module, compatibleWith: nil),
options: [
.transition(.fade(0.2)), // 부드러운 페이드 효과
.cacheOriginalImage, // 원본 이미지 캐싱
],
completionHandler: { [weak self] result in
options: options,
completion: { [weak self] result in
guard let self else {
return
}
Expand Down
196 changes: 196 additions & 0 deletions BookKitty/BookKitty/LogKit/Sources/LogKit/LogKitActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import Foundation
import OSLog

struct LoggerKey: Hashable {
let subSystem: LogSubSystem
let category: LogCategory
}

/// 단순히 전역 액터 속성만 정의하는 것이 아니라, 로깅 인터페이스도 제공
@globalActor
public actor LogKitActor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileManager 자체가 thread-safe한 것으로 알고 있는데, actor로 한 번 더 감싸주신 이유가 궁금합니다~!

// 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
)
}
}

final class _LogKit {
// MARK: - Properties

/// @globalActor를 사용해 shared 인스턴스에만 격리 도메인을 지정하는 것은 동시성 격리를 부분적으로 선택적으로 적용
/// 어떤 부분이 격리되어야 하고 어떤 부분이 일반 동기 코드로 실행되어도 되는지 더 세밀하게 제어
/// 실제로 모든 코드가 항상 격리될 필요는 없기 때문에 성능상 이점도 존재
/// 공유 자원에 대한 접근은 조정해야 하지만, 모든 기능이 액터 내부에 있을 필요는 없는 경우 적합
private let dateFormatter: DateFormatter
private let fileManager: FileManager
private var loggers: [LoggerKey: Logger] = [:]

private let appStartTime: String
private var currentCSVFileID = 1
private var currentCSVFileURL: URL?
private var currentCSVFileSize: UInt64 = 0
private let maxCSVFileSize: UInt64 = 60 * 1024 // 60KB

// MARK: - Lifecycle

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())

initializeLoggers()
createNewCSVFile()
}

// MARK: - Functions

// MARK: - Public Methods

func log(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public 선언이 안 되어 있는 듯 합니당

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터널로 사용할 계획이셨군요~ 짱짱입니다!

_ level: LogLevel,
message: String,
subSystem: LogSubSystem = .app,
category: LogCategory = .general,
file: String = #file,
function: String = #function,
line: Int = #line
) {
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,
level: level.rawValue,
fileName: fileName,
line: String(line),
function: function,
message: message,
subSystem: subSystem.rawValue,
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 = 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
currentCSVFileSize = UInt64(headerRow.utf8.count)
print("새 CSV 로그 파일이 생성되었습니다: \(fileName)")
} catch {
print("CSV 로그 파일 생성 실패: \(error)")
}
}

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 dataSize = UInt64(csvData.count)

// 현재 파일이 최대 크기를 초과하는지 확인
if currentCSVFileSize + dataSize > maxCSVFileSize {
currentCSVFileID += 1
createNewCSVFile()
}

// CSV 파일에 로그 추가
guard let fileURL = currentCSVFileURL else {
return
}

if let fileHandle = try? FileHandle(forWritingTo: fileURL) {
fileHandle.seekToEndOfFile()
fileHandle.write(csvData)
try? fileHandle.close()

currentCSVFileSize += dataSize
}
}
}
34 changes: 34 additions & 0 deletions BookKitty/BookKitty/LogKit/Sources/LogKit/LogKitType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import OSLog

public enum LogSubSystem: String, CaseIterable, Sendable {
case bookOCR = "bookOCR"
case bookRecommendation = "bookRecommendation"
case designSystem = "designSystem"
case database = "database"
case app = "app"
}

public enum LogCategory: String, CaseIterable, Sendable {
case general = "general"
case network = "network"
case userAction = "userAction"
case lifecycle = "lifecycle"
}

public enum LogLevel: String {
case debug = "DEBUG"
case info = "INFO"
case log = "LOG"
case error = "ERROR"

// MARK: - Computed Properties

var osLogType: OSLogType {
switch self {
case .debug: return .debug
case .info: return .info
case .log: return .default
case .error: return .error
}
}
}
Loading