From 4e9c294d5c14b1a996ee7799725df55214e19622 Mon Sep 17 00:00:00 2001 From: opficdev Date: Thu, 12 Feb 2026 11:10:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Infra/Service/AuthService.swift | 25 +++-- .../Service/PushNotificationService.swift | 48 ++++++--- .../AppleAuthenticationService.swift | 43 +++++--- .../GithubAuthenticationService.swift | 98 ++++++++++++------- .../GoogleAuthenticationService.swift | 54 ++++++---- DevLog/Infra/Service/TodoService.swift | 81 ++++++++++----- DevLog/Infra/Service/UserService.swift | 74 ++++++++++---- .../Service/WebPageMetadataService.swift | 31 +++--- DevLog/Infra/Service/WebPageService.swift | 55 ++++++++--- DevLog/Infra/Util/Logger.swift | 93 ++++++++++++++++++ 10 files changed, 444 insertions(+), 158 deletions(-) create mode 100644 DevLog/Infra/Util/Logger.swift diff --git a/DevLog/Infra/Service/AuthService.swift b/DevLog/Infra/Service/AuthService.swift index fd200fd..c47e564 100644 --- a/DevLog/Infra/Service/AuthService.swift +++ b/DevLog/Infra/Service/AuthService.swift @@ -10,6 +10,7 @@ import FirebaseFirestore final class AuthService { private let store = Firestore.firestore() + private let logger = Logger(category: "AuthService") var uid: String? { Auth.auth().currentUser?.uid @@ -20,13 +21,25 @@ final class AuthService { } func getProviderID() async throws -> String? { - guard let uid = uid else { return nil } + logger.info("Fetching current provider ID") + + guard let uid = uid else { + logger.warning("No user ID available") + return nil + } - let document = try await store - .collection("users/\(uid)/userData") - .document("info") - .getDocument() + do { + let document = try await store + .collection("users/\(uid)/userData") + .document("info") + .getDocument() - return document.data()?["currentProvider"] as? String + let providerID = document.data()?["currentProvider"] as? String + logger.info("Successfully fetched provider ID: \(providerID ?? "nil")") + return providerID + } catch { + logger.error("Failed to fetch provider ID", error: error) + throw error + } } } diff --git a/DevLog/Infra/Service/PushNotificationService.swift b/DevLog/Infra/Service/PushNotificationService.swift index 41db682..b909bc3 100644 --- a/DevLog/Infra/Service/PushNotificationService.swift +++ b/DevLog/Infra/Service/PushNotificationService.swift @@ -10,19 +10,32 @@ import FirebaseFirestore final class PushNotificationService { private let store = Firestore.firestore() + private let logger = Logger(category: "PushNotificationService") /// 푸시 알림 On/Off 설정 func fetchPushNotificationEnabled() async throws -> Bool { + logger.info("Fetching push notification enabled status") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let settingsRef = store.document("users/\(uid)/userData/settings") - let doc = try await settingsRef.getDocument() + do { + let settingsRef = store.document("users/\(uid)/userData/settings") + let doc = try await settingsRef.getDocument() - if let allowPush = doc.data()?["allowPushNotification"] as? Bool { return allowPush } + if let allowPush = doc.data()?["allowPushNotification"] as? Bool { + logger.info("Push notification enabled: \(allowPush)") + return allowPush + } - throw FirestoreError.dataNotFound("allowPushNotification") + logger.error("Push notification setting not found") + throw FirestoreError.dataNotFound("allowPushNotification") + } catch { + logger.error("Failed to fetch push notification status", error: error) + throw error + } } /// 푸시 알림 시간 설정 @@ -47,23 +60,32 @@ final class PushNotificationService { /// 푸시 알림 설정 업데이트 func updatePushNotificationSettings(isEnabled: Bool, components: DateComponents) async throws { + logger.info("Updating push notification settings - enabled: \(isEnabled)") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let settingsRef = store.document("users/\(uid)/userData/settings") + do { + let settingsRef = store.document("users/\(uid)/userData/settings") - var dict: [String: Any] = ["allowPushNotification": isEnabled] + var dict: [String: Any] = ["allowPushNotification": isEnabled] - if let hour = components.hour { - dict["pushNotificationHour"] = hour - } + if let hour = components.hour { + dict["pushNotificationHour"] = hour + } - if let minute = components.minute { - dict["pushNotificationMinute"] = minute - } + if let minute = components.minute { + dict["pushNotificationMinute"] = minute + } - try await settingsRef.setData(dict, merge: true) + try await settingsRef.setData(dict, merge: true) + logger.info("Successfully updated push notification settings") + } catch { + logger.error("Failed to update push notification settings", error: error) + throw error + } } /// 푸시 알림 기록 요청 diff --git a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift index 24d8901..2184833 100644 --- a/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift @@ -20,23 +20,29 @@ final class AppleAuthenticationService: AuthenticationService { private let messaging = Messaging.messaging() private var user: User? { Auth.auth().currentUser } private let providerID = AuthProviderID.apple + private let logger = Logger(category: "AppleAuthService") func signIn() async throws -> AuthenticationDataResponse { - let response = try await authenticateWithAppleAsync() + logger.info("Starting Apple sign in") - let nonce = response.nonce - let credential = response.credential - let authorizationCode = response.authorizationCode - let idTokenString = response.idTokenString - - // Firebase Function을 통해 customToken 요청 - let customToken = try await requestAppleCustomToken( - idToken: idTokenString, - authorizationCode: authorizationCode - ) - - // customToken으로 Firebase 로그인 - let result = try await Auth.auth().signIn(withCustomToken: customToken) + do { + let response = try await authenticateWithAppleAsync() + + let nonce = response.nonce + let credential = response.credential + let authorizationCode = response.authorizationCode + let idTokenString = response.idTokenString + + // Firebase Function을 통해 customToken 요청 + logger.debug("Requesting custom token from Firebase Function") + let customToken = try await requestAppleCustomToken( + idToken: idTokenString, + authorizationCode: authorizationCode + ) + + // customToken으로 Firebase 로그인 + logger.debug("Signing in with custom token") + let result = try await Auth.auth().signIn(withCustomToken: customToken) let changeRequest = result.user.createProfileChangeRequest() var displayName: String? @@ -72,9 +78,14 @@ final class AppleAuthenticationService: AuthenticationService { try await result.user.link(with: appleCredential) } - let fcmToken = try await messaging.token() + let fcmToken = try await messaging.token() - return result.user.toData(providerID: .apple, fcmToken: fcmToken) + logger.info("Successfully signed in with Apple") + return result.user.toData(providerID: .apple, fcmToken: fcmToken) + } catch { + logger.error("Failed to sign in with Apple", error: error) + throw error + } } func signOut(_ uid: String) async throws { diff --git a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift index a93633f..05b6da5 100644 --- a/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift @@ -18,16 +18,23 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { private let messaging = Messaging.messaging() private var user: User? { Auth.auth().currentUser } private let providerID = AuthProviderID.gitHub + private let logger = Logger(category: "GithubAuthService") func signIn() async throws -> AuthenticationDataResponse { - // 1. GitHub OAuth 로그인 요청 - let authorizationCode = try await requestAuthorizationCode() - - // 2. Firebase Functions를 통해 customToken 발급 요청 - let (accessToken, customToken) = try await requestTokens(authorizationCode: authorizationCode) + logger.info("Starting GitHub sign in") - // 3. Firebase 로그인 - let result = try await Auth.auth().signIn(withCustomToken: customToken) + do { + // 1. GitHub OAuth 로그인 요청 + logger.debug("Requesting authorization code") + let authorizationCode = try await requestAuthorizationCode() + + // 2. Firebase Functions를 통해 customToken 발급 요청 + logger.debug("Requesting tokens from Firebase Function") + let (accessToken, customToken) = try await requestTokens(authorizationCode: authorizationCode) + + // 3. Firebase 로그인 + logger.debug("Signing in with custom token") + let result = try await Auth.auth().signIn(withCustomToken: customToken) // 4. Firebase Auth 사용자 프로필 업데이트 let githubUser = try await requestUserProfile(accessToken: accessToken) @@ -45,13 +52,18 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { try await result.user.link(with: credential) } - let fcmToken = try await messaging.token() - - return result.user.toData( - providerID: .gitHub, - fcmToken: fcmToken, - accessToken: accessToken - ) + let fcmToken = try await messaging.token() + + logger.info("Successfully signed in with GitHub") + return result.user.toData( + providerID: .gitHub, + fcmToken: fcmToken, + accessToken: accessToken + ) + } catch { + logger.error("Failed to sign in with GitHub", error: error) + throw error + } } func signOut(_ uid: String) async throws { @@ -79,36 +91,56 @@ final class GithubAuthenticationService: NSObject, AuthenticationService { } func link(uid: String, email: String) async throws { - let tokensRef = store.document("users/\(uid)/userData/tokens") - let authorizationCode = try await requestAuthorizationCode() - let (accessToken, _) = try await requestTokens(authorizationCode: authorizationCode) + logger.info("Linking GitHub account for user: \(uid)") + + do { + let tokensRef = store.document("users/\(uid)/userData/tokens") + let authorizationCode = try await requestAuthorizationCode() + let (accessToken, _) = try await requestTokens(authorizationCode: authorizationCode) - let githubUser = try await requestUserProfile(accessToken: accessToken) + let githubUser = try await requestUserProfile(accessToken: accessToken) - guard let githubEmail = githubUser.email else { - try await revokeAccessToken(accessToken: accessToken) - throw EmailFetchError.emailNotFound - } + guard let githubEmail = githubUser.email else { + logger.error("GitHub email not found") + try await revokeAccessToken(accessToken: accessToken) + throw EmailFetchError.emailNotFound + } - if githubEmail != email { - try await revokeAccessToken(accessToken: accessToken) - throw EmailFetchError.emailMismatch - } + if githubEmail != email { + logger.error("Email mismatch - Expected: \(email), Got: \(githubEmail)") + try await revokeAccessToken(accessToken: accessToken) + throw EmailFetchError.emailMismatch + } - try await tokensRef.setData(["githubAccessToken": accessToken], merge: true) + try await tokensRef.setData(["githubAccessToken": accessToken], merge: true) - let credential = OAuthProvider.credential(providerID: AuthProviderID.gitHub, accessToken: accessToken) - try await user?.link(with: credential) + let credential = OAuthProvider.credential(providerID: AuthProviderID.gitHub, accessToken: accessToken) + try await user?.link(with: credential) + + logger.info("Successfully linked GitHub account") + } catch { + logger.error("Failed to link GitHub account", error: error) + throw error + } } func unlink(_ uid: String) async throws { - try await revokeAccessToken() + logger.info("Unlinking GitHub account for user: \(uid)") + + do { + try await revokeAccessToken() - let tokensRef = store.document("users/\(uid)/userData/tokens") + let tokensRef = store.document("users/\(uid)/userData/tokens") - try await tokensRef.updateData(["githubAccessToken": FieldValue.delete()]) + try await tokensRef.updateData(["githubAccessToken": FieldValue.delete()]) - _ = try await user?.unlink(fromProvider: providerID.rawValue) + _ = try await user?.unlink(fromProvider: providerID.rawValue) + + logger.info("Successfully unlinked GitHub account") + } catch { + logger.error("Failed to unlink GitHub account", error: error) + throw error + } } func requestAuthorizationCode() async throws -> String { diff --git a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift index f03a2b0..5798975 100644 --- a/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift +++ b/DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift @@ -18,35 +18,47 @@ final class GoogleAuthenticationService: AuthenticationService { private let messaging = Messaging.messaging() private var user: User? { Auth.auth().currentUser } private let provider = TopViewControllerProvider() + private let logger = Logger(category: "GoogleAuthService") @MainActor func signIn() async throws -> AuthenticationDataResponse { + logger.info("Starting Google sign in") + guard let topViewController = provider.topViewController() else { + logger.error("Top view controller not found") throw UIError.notFoundTopViewController } - let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController) - - guard let idToken = signIn.user.idToken?.tokenString else { - throw URLError(.badServerResponse) - } - - let accessToken = signIn.user.accessToken.tokenString - let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken) - - let result = try await Auth.auth().signIn(with: credential) - - if let photoURL = signIn.user.profile?.imageURL(withDimension: 200) { - let changeRequest = result.user.createProfileChangeRequest() - changeRequest.photoURL = photoURL - changeRequest.displayName = signIn.user.profile?.name - - try await changeRequest.commitChanges() + do { + let signIn = try await GIDSignIn.sharedInstance.signIn(withPresenting: topViewController) + + guard let idToken = signIn.user.idToken?.tokenString else { + logger.error("ID token not found") + throw URLError(.badServerResponse) + } + + let accessToken = signIn.user.accessToken.tokenString + let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken) + + logger.debug("Signing in with Google credential") + let result = try await Auth.auth().signIn(with: credential) + + if let photoURL = signIn.user.profile?.imageURL(withDimension: 200) { + let changeRequest = result.user.createProfileChangeRequest() + changeRequest.photoURL = photoURL + changeRequest.displayName = signIn.user.profile?.name + + try await changeRequest.commitChanges() + } + + let fcmToken = try await messaging.token() + + logger.info("Successfully signed in with Google") + return result.user.toData(providerID: .google, fcmToken: fcmToken) + } catch { + logger.error("Failed to sign in with Google", error: error) + throw error } - - let fcmToken = try await messaging.token() - - return result.user.toData(providerID: .google, fcmToken: fcmToken) } func signOut(_ uid: String) async throws { diff --git a/DevLog/Infra/Service/TodoService.swift b/DevLog/Infra/Service/TodoService.swift index c455694..442e8be 100644 --- a/DevLog/Infra/Service/TodoService.swift +++ b/DevLog/Infra/Service/TodoService.swift @@ -9,42 +9,75 @@ import FirebaseFirestore final class TodoService { private let store = Firestore.firestore() + private let logger = Logger(category: "TodoService") func fetchPinnedTodos(_ uid: String) async throws -> [TodoResponse] { - let collection = store.collection("users/\(uid)/todoLists/") - - let query = collection.whereField(("isPinned"), isEqualTo: true) - .order(by: "createdAt", descending: true) - - let snapshot = try await query.getDocuments() + logger.info("Fetching pinned todos for user: \(uid)") - return snapshot.documents.compactMap { TodoResponse(from: $0) } + do { + let collection = store.collection("users/\(uid)/todoLists/") + + let query = collection.whereField(("isPinned"), isEqualTo: true) + .order(by: "createdAt", descending: true) + + let snapshot = try await query.getDocuments() + let todos = snapshot.documents.compactMap { TodoResponse(from: $0) } + + logger.info("Successfully fetched \(todos.count) pinned todos") + return todos + } catch { + logger.error("Failed to fetch pinned todos", error: error) + throw error + } } func fetchTodos(uid: String, kind: TodoKind) async throws -> [TodoResponse] { - let collection = store.collection("users/\(uid)/todoLists/") - - let query = collection.whereField("kind", isEqualTo: kind.rawValue) - .order(by: "createdAt", descending: true) - - let snapshot = try await query.getDocuments() + logger.info("Fetching todos of kind: \(kind.rawValue) for user: \(uid)") - return snapshot.documents.compactMap { TodoResponse(from: $0) } + do { + let collection = store.collection("users/\(uid)/todoLists/") + + let query = collection.whereField("kind", isEqualTo: kind.rawValue) + .order(by: "createdAt", descending: true) + + let snapshot = try await query.getDocuments() + let todos = snapshot.documents.compactMap { TodoResponse(from: $0) } + + logger.info("Successfully fetched \(todos.count) todos") + return todos + } catch { + logger.error("Failed to fetch todos", error: error) + throw error + } } func upsertTodo(uid: String, request: TodoRequest) async throws { - let collection = store.collection("users/\(uid)/todoLists/") - - let docRef = collection.document(request.id) - - try await docRef.setData(request.toDictionary(), merge: true) + logger.info("Upserting todo: \(request.id)") + + do { + let collection = store.collection("users/\(uid)/todoLists/") + let docRef = collection.document(request.id) + try await docRef.setData(request.toDictionary(), merge: true) + + logger.info("Successfully upserted todo") + } catch { + logger.error("Failed to upsert todo", error: error) + throw error + } } func deleteTodo(uid: String, todoID: String) async throws { - let collection = store.collection("users/\(uid)/todoLists/") - - let docRef = collection.document(todoID) - - try await docRef.delete() + logger.info("Deleting todo: \(todoID)") + + do { + let collection = store.collection("users/\(uid)/todoLists/") + let docRef = collection.document(todoID) + try await docRef.delete() + + logger.info("Successfully deleted todo") + } catch { + logger.error("Failed to delete todo", error: error) + throw error + } } } diff --git a/DevLog/Infra/Service/UserService.swift b/DevLog/Infra/Service/UserService.swift index ef37c4b..b7116f7 100644 --- a/DevLog/Infra/Service/UserService.swift +++ b/DevLog/Infra/Service/UserService.swift @@ -12,10 +12,16 @@ import FirebaseFunctions final class UserService { private let store = Firestore.firestore() private let functions = Functions.functions(region: "asia-northeast3") + private let logger = Logger(category: "UserService") // 유저를 Firestore에 저장 및 업데이트 func upsertUser(_ response: AuthenticationDataResponse) async throws { - guard let user = Auth.auth().currentUser else { throw AuthError.notAuthenticated } + logger.info("Upserting user with provider: \(response.providerID)") + + guard let user = Auth.auth().currentUser else { + logger.error("User not authenticated") + throw AuthError.notAuthenticated + } let infoRef = store.document("users/\(user.uid)/userData/info") let tokensRef = store.document("users/\(user.uid)/userData/tokens") let settingsRef = store.document("users/\(user.uid)/userData/settings") @@ -58,46 +64,72 @@ final class UserService { "allowPushNotification": true, "pushNotificationHour": 9, "pushNotificationMinute": 0], merge: true) + + logger.info("Successfully upserted user: \(user.uid)") } func fetchUserProfile() async throws -> UserProfileResponse { + logger.info("Fetching user profile") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let infoRef = store.document("users/\(uid)/userData/info") + do { + let infoRef = store.document("users/\(uid)/userData/info") + let data = try await infoRef.getDocument().data() - let data = try await infoRef.getDocument().data() + guard let provider = data?["currentProvider"] as? String, + let name = data?[provider == "apple.com" ? "appleName" : "name"] as? String, + let email = data?["email"] as? String, + let statusMessage = data?["statusMsg"] as? String + else { + logger.error("User profile data not found") + throw FirestoreError.dataNotFound("User Profile") + } - guard let provider = data?["currentProvider"] as? String, - let name = data?[provider == "apple.com" ? "appleName" : "name"] as? String, - let email = data?["email"] as? String, - let statusMessage = data?["statusMsg"] as? String - else { - throw FirestoreError.dataNotFound("User Profile") + logger.info("Successfully fetched user profile for: \(email)") + return UserProfileResponse( + name: name, + email: email, + statusMessage: statusMessage, + avatarURL: Auth.auth().currentUser?.photoURL + ) + } catch { + logger.error("Failed to fetch user profile", error: error) + throw error } - - return UserProfileResponse( - name: name, - email: email, - statusMessage: statusMessage, - avatarURL: Auth.auth().currentUser?.photoURL - ) } func upsertStatusMessage(_ message: String) async throws { + logger.info("Upserting status message") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let infoRef = store.document("users/\(uid)/userData/info") - - try await infoRef.setData(["statusMsg": message], merge: true) + do { + let infoRef = store.document("users/\(uid)/userData/info") + try await infoRef.setData(["statusMsg": message], merge: true) + logger.info("Successfully upserted status message") + } catch { + logger.error("Failed to upsert status message", error: error) + throw error + } } func updateFCMToken(_ userId: String, fcmToken: String) async throws { - let tokensRef = store.document("users/\(userId)/userData/tokens") + logger.info("Updating FCM token for user: \(userId)") - try await tokensRef.setData(["fcmToken": fcmToken], merge: true) + do { + let tokensRef = store.document("users/\(userId)/userData/tokens") + try await tokensRef.setData(["fcmToken": fcmToken], merge: true) + logger.info("Successfully updated FCM token") + } catch { + logger.error("Failed to update FCM token", error: error) + throw error + } } } diff --git a/DevLog/Infra/Service/WebPageMetadataService.swift b/DevLog/Infra/Service/WebPageMetadataService.swift index d40d031..9f08b97 100644 --- a/DevLog/Infra/Service/WebPageMetadataService.swift +++ b/DevLog/Infra/Service/WebPageMetadataService.swift @@ -9,24 +9,33 @@ import Foundation import LinkPresentation final class WebPageMetadataService { + private let logger = Logger(category: "WebPageMetadataService") func fetchMetadata(from response: WebPageResponse) async throws -> WebPageMetadata { + logger.info("Fetching metadata for URL: \(response.urlString)") + guard let url = URL(string: response.urlString) else { + logger.error("Invalid URL: \(response.urlString)") throw URLError(.badURL) } - let provider = LPMetadataProvider() - provider.timeout = 10.0 + do { + let provider = LPMetadataProvider() + provider.timeout = 10.0 - let metadata = try await provider.startFetchingMetadata(for: url) + let metadata = try await provider.startFetchingMetadata(for: url) + let imageURL = try await extractImageURL(from: metadata.imageProvider, url: url) - let imageURL = try await extractImageURL(from: metadata.imageProvider, url: url) - - return WebPageMetadata( - title: metadata.title, - url: url, - displayURL: metadata.url ?? url, - imageURL: imageURL - ) + logger.info("Successfully fetched metadata for: \(metadata.title ?? "Unknown")") + return WebPageMetadata( + title: metadata.title, + url: url, + displayURL: metadata.url ?? url, + imageURL: imageURL + ) + } catch { + logger.error("Failed to fetch metadata", error: error) + throw error + } } private func extractImageURL(from imageProvider: NSItemProvider?, url: URL) async throws -> URL? { diff --git a/DevLog/Infra/Service/WebPageService.swift b/DevLog/Infra/Service/WebPageService.swift index 7ea88de..ab6d734 100644 --- a/DevLog/Infra/Service/WebPageService.swift +++ b/DevLog/Infra/Service/WebPageService.swift @@ -10,43 +10,72 @@ import FirebaseFirestore final class WebPageService { private let store = Firestore.firestore() + private let logger = Logger(category: "WebPageService") /// 저장한 웹페이지를 모두 불러옴 func fetchWebPages() async throws -> [WebPageResponse] { + logger.info("Fetching web pages") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let webPageInfoRef = store.document("users/\(uid)/userData/webPageInfos") - let doc = try await webPageInfoRef.getDocument() + do { + let webPageInfoRef = store.document("users/\(uid)/userData/webPageInfos") + let doc = try await webPageInfoRef.getDocument() - if doc.exists, let data = doc.data() { - if let webPageInfos = data["webPageInfos"] as? [String] { - return webPageInfos.map { WebPageResponse(urlString: $0) } + if doc.exists, let data = doc.data() { + if let webPageInfos = data["webPageInfos"] as? [String] { + logger.info("Successfully fetched \(webPageInfos.count) web pages") + return webPageInfos.map { WebPageResponse(urlString: $0) } + } } + logger.error("Web page data not found or invalid") + throw URLError(.badServerResponse) + } catch { + logger.error("Failed to fetch web pages", error: error) + throw error } - throw URLError(.badServerResponse) } /// 웹페이지를 추가 또는 업데이트 func upsertWebPage(_ urlString: String) async throws { + logger.info("Upserting web page: \(urlString)") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let infosRef = store.document("users/\(uid)/userData/webPageInfos") - try await infosRef.setData( - ["webPageInfos": FieldValue.arrayUnion([urlString])], - merge: true - ) + do { + let infosRef = store.document("users/\(uid)/userData/webPageInfos") + try await infosRef.setData( + ["webPageInfos": FieldValue.arrayUnion([urlString])], + merge: true + ) + logger.info("Successfully upserted web page") + } catch { + logger.error("Failed to upsert web page", error: error) + throw error + } } func deleteWebPage(_ urlString: String) async throws { + logger.info("Deleting web page: \(urlString)") + guard let uid = Auth.auth().currentUser?.uid else { + logger.error("User not authenticated") throw AuthError.notAuthenticated } - let infosRef = store.document("users/\(uid)/userData/webPageInfos") - try await infosRef.updateData(["webPageInfos": FieldValue.arrayRemove([urlString])]) + do { + let infosRef = store.document("users/\(uid)/userData/webPageInfos") + try await infosRef.updateData(["webPageInfos": FieldValue.arrayRemove([urlString])]) + logger.info("Successfully deleted web page") + } catch { + logger.error("Failed to delete web page", error: error) + throw error + } } } diff --git a/DevLog/Infra/Util/Logger.swift b/DevLog/Infra/Util/Logger.swift new file mode 100644 index 0000000..6a44025 --- /dev/null +++ b/DevLog/Infra/Util/Logger.swift @@ -0,0 +1,93 @@ +// +// Logger.swift +// DevLog +// +// Created by 최윤진 on 2/12/26. +// + +import Foundation +import os.log + +final class Logger { + private let subsystem: String + private let category: String + private let osLog: OSLog + + init(subsystem: String = Bundle.main.bundleIdentifier ?? "DevLog", category: String) { + self.subsystem = subsystem + self.category = category + self.osLog = OSLog(subsystem: subsystem, category: category) + } + + func debug( + _ message: String, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + log(message, type: .debug, file: file, function: function, line: line) + } + + func info( + _ message: String, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + log(message, type: .info, file: file, function: function, line: line) + } + + func warning( + _ message: String, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + log(message, type: .default, file: file, function: function, line: line) + } + + func error( + _ message: String, + error: Error? = nil, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + var fullMessage = message + if let error = error { + fullMessage += " | Error: \(error.localizedDescription)" + } + log(fullMessage, type: .error, file: file, function: function, line: line) + } + + func fault( + _ message: String, + error: Error? = nil, + file: String = #file, + function: String = #function, + line: Int = #line + ) { + var fullMessage = message + if let error = error { + fullMessage += " | Error: \(error.localizedDescription)" + } + log(fullMessage, type: .fault, file: file, function: function, line: line) + } + + private func log( + _ message: String, + type: OSLogType, + file: String, + function: String, + line: Int + ) { + let fileName = (file as NSString).lastPathComponent + let logMessage = "[\(fileName):\(line)] \(function) - \(message)" + + #if DEBUG + os_log("%{public}@", log: osLog, type: type, logMessage) + #else + os_log("%{private}@", log: osLog, type: type, logMessage) + #endif + } +}