[Feat] 네트워킹 로깅 플러그인 구현#34
Conversation
Walkthrough
Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NetworkService
participant BitnagilLoggingPlugin
participant Server
Client->>NetworkService: performRequest(endpoint)
NetworkService->>BitnagilLoggingPlugin: willSend(request, endpoint)
BitnagilLoggingPlugin-->>NetworkService: (로깅 후) request 반환
NetworkService->>Server: HTTP 요청 전송
Server-->>NetworkService: HTTP 응답 수신
NetworkService->>BitnagilLoggingPlugin: didReceive(response, data, endpoint)
BitnagilLoggingPlugin-->>NetworkService: (로깅 후) 제어 반환
NetworkService-->>Client: 결과 반환
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
Projects/DataSource/Sources/NetworkService/Plugin/BitnagilLoggingPlugin.swift (7)
49-55: JSON 파싱 오류 처리 개선을 고려해보세요.현재 JSON 파싱 실패 시 빈 문자열로 처리하고 있는데, 파싱 실패 이유를 로깅하면 디버깅에 도움이 될 수 있습니다.
- if - let data = data, - let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { - code = json["code"] as? String ?? "" - message = json["message"] as? String ?? "" - } + if let data = data { + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { + code = json["code"] as? String ?? "" + message = json["message"] as? String ?? "" + } + } catch { + BitnagilLogger.log(logType: .debug, message: "JSON 파싱 실패: \(error.localizedDescription)") + } + }
11-11: 접근 제어자 추가를 고려해보세요.구조체가 NetworkService 내부에서만 사용된다면
internal또는private접근 제어자를 명시하여 모듈 경계를 명확히 하는 것을 고려해보세요.
17-21: HTTP 바디 처리 로직 개선현재 구현은 적절하지만 몇 가지 개선점이 있습니다:
- 대용량 바디 데이터에 대한 처리가 없습니다
- 한국어 메시지 사용이 일관적이지 않습니다
다음과 같이 개선할 수 있습니다:
let body: String if let bodyData = request.httpBody { - body = String(data: bodyData, encoding: .utf8) ?? "디코딩 실패" + if bodyData.count > 1024 { + body = "용량이 큰 데이터 (\(bodyData.count) bytes)" + } else { + body = String(data: bodyData, encoding: .utf8) ?? "디코딩 실패" + } } else { body = "내용 없음" }
39-42: HTTPURLResponse 타입캐스팅 실패 처리타입캐스팅 실패에 대한 에러 로깅이 적절합니다. 다만 실제 response 타입 정보를 포함하면 디버깅에 더 도움이 될 것 같습니다.
- BitnagilLogger.log(logType: .error, message: "🚨\(urlString)에 대한 response는 HTTPResponse가 아닙니다.🚨") + BitnagilLogger.log(logType: .error, message: "🚨\(urlString)에 대한 response는 HTTPResponse가 아닙니다. 실제 타입: \(type(of: response))🚨")
46-55: JSON 파싱 에러 처리 개선현재
try?를 사용하여 JSON 파싱 실패를 조용히 무시하고 있습니다. 파싱 실패 시에도 로그에 이를 명시하면 디버깅에 도움이 될 것입니다.// code/message 추출 var code: String = "" var message: String = "" if let data = data { - if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { - code = json["code"] as? String ?? "" - message = json["message"] as? String ?? "" + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { + code = json["code"] as? String ?? "" + message = json["message"] as? String ?? "" + } + } catch { + BitnagilLogger.log(logType: .debug, message: "JSON 파싱 실패: \(error.localizedDescription)") } }
11-11: 구조체 접근 제어자 고려사항현재
BitnagilLoggingPlugin이internal접근 수준으로 선언되어 있습니다. 다른 모듈에서 이 플러그인을 사용할 가능성이 있다면public선언을 고려해보세요.
49-55: JSON 파싱 로직 개선 제안현재 JSON 파싱이 실패하면 빈 문자열로 처리되는데, 디버깅 목적을 고려하면 파싱 실패 이유를 로그에 남기는 것이 더 유용할 수 있습니다.
다음과 같이 개선할 수 있습니다:
- if - let data = data, - let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { - code = json["code"] as? String ?? "" - message = json["message"] as? String ?? "" - } + if let data = data { + do { + let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + code = json?["code"] as? String ?? "" + message = json?["message"] as? String ?? "" + } catch { + code = "JSON 파싱 실패" + message = error.localizedDescription + } + }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Projects/DataSource/Sources/NetworkService/NetworkService.swift(2 hunks)Projects/DataSource/Sources/NetworkService/Plugin/BitnagilLoggingPlugin.swift(1 hunks)
👮 Files not reviewed due to content moderation or server errors (2)
- Projects/DataSource/Sources/NetworkService/Plugin/BitnagilLoggingPlugin.swift
- Projects/DataSource/Sources/NetworkService/NetworkService.swift
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
🔇 Additional comments (17)
Projects/DataSource/Sources/NetworkService/Plugin/BitnagilLoggingPlugin.swift (8)
11-11: 구조체 선언이 적절합니다.NetworkPlugin 프로토콜을 올바르게 구현하고 있습니다.
17-21: 바디 데이터 처리 로직이 적절합니다.nil 체크와 UTF-8 디코딩 실패에 대한 처리가 잘 되어 있습니다.
36-67: 응답 로깅 구현이 효율적입니다.HTTPURLResponse 타입 체크와 JSON 파싱을 통한 구조적 로깅이 잘 구현되어 있습니다. 전체 응답 바디 대신 필요한 필드만 추출하여 로그 가독성을 높인 점이 좋습니다.
15-15: 헤더 로깅 시 보안 고려사항헤더에는 토큰 정보가 포함되어 있어 민감한 정보가 로그에 노출될 수 있습니다. 검색된 학습 내용에 따르면 현재 테스트 단계에서는 토큰 값 로깅을 선호하지만, 프로덕션 환경에서는 보안 위험이 있을 수 있습니다.
프로덕션 배포 전에 민감한 헤더 값(Authorization, X-API-Key 등)을 마스킹하는 로직 추가를 고려해보세요:
- let headers = request.allHTTPHeaderFields ?? [:] + let headers = sanitizeHeaders(request.allHTTPHeaderFields ?? [:])그리고 헤더 새니타이제이션 함수를 추가:
private func sanitizeHeaders(_ headers: [String: String]) -> [String: String] { let sensitiveKeys = ["Authorization", "X-API-Key", "Cookie"] return headers.mapValues { key, value in sensitiveKeys.contains(key) ? "***" : value } }
57-66: 응답 로깅 구조가 잘 설계되었습니다.요청 URL, 상태 코드, 그리고 서버의 code/message만 로깅하여 가독성을 유지하면서도 필요한 정보를 제공하는 균형 잡힌 접근 방식입니다. PR 목표에 부합합니다.
12-34: 요청 로깅 구현이 잘 되어 있습니다URLRequest의 주요 정보들(URL, HTTP 메서드, 헤더, 바디)을 모두 포함하여 디버깅에 유용한 로그를 제공하고 있습니다. 바디 디코딩 실패 시 적절한 대체 메시지도 제공합니다.
17-21: 토큰 정보 로깅에 대한 보안 고려사항헤더에 포함된 토큰 정보가 로그에 기록됩니다. 이전 학습 내용에 따르면 현재 테스트 단계에서는 디버깅을 위해 실제 토큰 값 로깅을 선호하지만, 프로덕션 환경에서는 보안상 토큰 정보를 마스킹하는 것을 고려해야 합니다.
프로덕션 배포 전에 민감한 정보 로깅에 대한 정책을 확인하고, 필요시 토큰 마스킹 기능 추가를 검토해보세요.
36-67: 응답 로깅 구현이 적절합니다HTTPURLResponse 타입 체크, 상태 코드 추출, JSON에서 code/message 필드 파싱이 안전하게 구현되어 있습니다. JSON 파싱 실패 시에도 빈 문자열로 처리하여 로그가 중단되지 않도록 했습니다.
Projects/DataSource/Sources/NetworkService/NetworkService.swift (9)
21-22: 플러그인 통합이 올바르게 구현되었습니다.BitnagilLoggingPlugin이 적절한 순서로 plugins 배열에 추가되었습니다. 로깅은 일반적으로 다른 플러그인들의 작업 완료 후에 수행되는 것이 좋으므로 배열의 마지막에 위치시킨 것이 적절합니다.
81-81: 코드 포맷팅 개선이 좋습니다.guard 문을 한 줄로 정리하여 가독성이 향상되었습니다.
86-86: 일관된 코드 스타일이 적용되었습니다.다른 guard 문과 동일한 스타일로 통일되어 코드 일관성이 향상되었습니다.
21-22: 로깅 플러그인 통합이 올바르게 구현되었습니다.
BitnagilLoggingPlugin이 적절한 위치에 추가되어 다른 플러그인들과 함께 작동할 수 있도록 구성되었습니다. 플러그인 순서도 논리적으로 배치되어 있습니다 (토큰 주입 → 토큰 갱신 → 재시도 → 로깅).
81-81: Guard문 포맷팅 개선한 줄로 정리된 guard문이 더 간결하고 읽기 쉽습니다.
86-86: 일관된 포맷팅 스타일다른 guard문과 일관된 스타일로 정리되어 코드 가독성이 향상되었습니다.
21-22: 로깅 플러그인 통합이 적절합니다
BitnagilLoggingPlugin이 플러그인 배열에 올바르게 추가되어 네트워크 요청/응답 로깅을 담당하게 되었습니다. 플러그인 패턴을 통해 관심사가 잘 분리되었습니다.
81-81: Guard 문 포맷팅 개선Guard 문이 더 간결하게 포맷팅되어 가독성이 향상되었습니다.
Also applies to: 86-86
21-22: 수동 로깅 코드 완전 제거 확인
Projects/DataSource/Sources/NetworkService/NetworkService.swift에서BitnagilLogger호출이 없습니다.- 상태 코드 관련 수동 로깅(
status code로그)도 모두 제거되었습니다.모든 네트워크 로깅이
BitnagilLoggingPlugin으로 일원화되어 중복이 해소되었으므로, 해당 리팩토링은 적절하게 적용되었습니다.
| func willSend(request: URLRequest, endpoint: Endpoint) async throws -> URLRequest { | ||
| let urlString = request.url?.absoluteString ?? "" | ||
| let method = request.httpMethod ?? "" | ||
| let headers = request.allHTTPHeaderFields ?? [:] | ||
| let body: String | ||
| if let bodyData = request.httpBody { | ||
| body = String(data: bodyData, encoding: .utf8) ?? "디코딩 실패" | ||
| } else { | ||
| body = "내용 없음" | ||
| } | ||
|
|
||
| BitnagilLogger.log( | ||
| logType: .debug, | ||
| message: """ | ||
| REQUEST➡️ | ||
| - URL: \(urlString) | ||
| - Method: \(method) | ||
| - Headers: \(headers) | ||
| - Body: \(body) | ||
| """ | ||
| ) | ||
| return request | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
보안 민감 정보 로깅에 대한 주의가 필요합니다.
현재 코드는 모든 헤더 정보를 로깅하고 있어 토큰과 같은 민감한 정보가 노출될 수 있습니다. 검색된 학습 내용에 따르면 choijungp님이 디버깅 목적으로 토큰 값 로깅을 선호하신다고 하지만, 프로덕션 환경에서는 보안상 위험할 수 있습니다.
다음과 같이 민감한 헤더를 마스킹하는 방식을 고려해보세요:
- let headers = request.allHTTPHeaderFields ?? [:]
+ let sensitiveHeaders = ["Authorization", "X-Access-Token", "X-Refresh-Token"]
+ var headers = request.allHTTPHeaderFields ?? [:]
+ for key in sensitiveHeaders {
+ if headers[key] != nil {
+ headers[key] = "***MASKED***"
+ }
+ }또는 디버그 빌드에서만 전체 헤더를 로깅하도록 할 수 있습니다:
+ #if DEBUG
let headers = request.allHTTPHeaderFields ?? [:]
+ #else
+ var headers = request.allHTTPHeaderFields ?? [:]
+ let sensitiveHeaders = ["Authorization", "X-Access-Token", "X-Refresh-Token"]
+ for key in sensitiveHeaders {
+ if headers[key] != nil {
+ headers[key] = "***MASKED***"
+ }
+ }
+ #endif📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func willSend(request: URLRequest, endpoint: Endpoint) async throws -> URLRequest { | |
| let urlString = request.url?.absoluteString ?? "" | |
| let method = request.httpMethod ?? "" | |
| let headers = request.allHTTPHeaderFields ?? [:] | |
| let body: String | |
| if let bodyData = request.httpBody { | |
| body = String(data: bodyData, encoding: .utf8) ?? "디코딩 실패" | |
| } else { | |
| body = "내용 없음" | |
| } | |
| BitnagilLogger.log( | |
| logType: .debug, | |
| message: """ | |
| REQUEST➡️ | |
| - URL: \(urlString) | |
| - Method: \(method) | |
| - Headers: \(headers) | |
| - Body: \(body) | |
| """ | |
| ) | |
| return request | |
| } | |
| func willSend(request: URLRequest, endpoint: Endpoint) async throws -> URLRequest { | |
| let urlString = request.url?.absoluteString ?? "" | |
| let method = request.httpMethod ?? "" | |
| #if DEBUG | |
| // In debug builds, log headers unmasked for easier debugging | |
| let headers = request.allHTTPHeaderFields ?? [:] | |
| #else | |
| // In non-debug builds, mask sensitive header values | |
| var headers = request.allHTTPHeaderFields ?? [:] | |
| let sensitiveHeaders = ["Authorization", "X-Access-Token", "X-Refresh-Token"] | |
| for key in sensitiveHeaders { | |
| if headers[key] != nil { | |
| headers[key] = "***MASKED***" | |
| } | |
| } | |
| #endif | |
| let body: String | |
| if let bodyData = request.httpBody { | |
| body = String(data: bodyData, encoding: .utf8) ?? "디코딩 실패" | |
| } else { | |
| body = "내용 없음" | |
| } | |
| BitnagilLogger.log( | |
| logType: .debug, | |
| message: """ | |
| REQUEST➡️ | |
| - URL: \(urlString) | |
| - Method: \(method) | |
| - Headers: \(headers) | |
| - Body: \(body) | |
| """ | |
| ) | |
| return request | |
| } |
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/NetworkService/Plugin/BitnagilLoggingPlugin.swift
around lines 12 to 34, the current logging of all HTTP headers may expose
sensitive information like tokens. To fix this, modify the logging logic to mask
sensitive headers such as Authorization by replacing their values with a
placeholder before logging. Alternatively, restrict full header logging to debug
builds only by using conditional compilation flags, ensuring sensitive data is
not logged in production environments.
There was a problem hiding this comment.
OSLogger의 log level이 debug라 release 환경에서는 로그가 찍히지 않는다 생각했습니다. 추후 확인해보고 잘못된 지식이었다면 조치하도록 하겠습니다.
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
OSLogger debug level logs in iOS release builds vs debug builds behavior
💡 Result:
In iOS, OSLogger (using the OSLog framework) supports multiple log levels, including debug, info, and error. The key behavior difference is that debug-level logs generated by OSLogger are visible in Debug builds but are suppressed in Release builds by default. This design helps prevent leaking sensitive or verbose debug information in production and reduces performance and storage overhead[1][3][7].
Supporting Details:
-
Debug Logs in Debug Builds:
Debug logs (e.g.,Logger().debug("Debug log")) are fully visible when running Debug builds, such as in the Xcode debugger or Console app, provided filtering options for log levels are enabled[1][7]. This is intended for development and troubleshooting during active coding. -
Debug Logs in Release Builds:
In Release builds (used for App Store distribution and TestFlight), debug-level logs are not emitted or are filtered out by the unified logging system. This means you generally cannot see debug-level output from OSLogger in production builds, even if you search directly for them in the Console on a device[1][3][7].- Apple’s official logging system intentionally suppresses both
debugandtracemessages from release builds to avoid unnecessary performance impact, storage use, and potential privacy issues[1][3]. - Other log levels (e.g.,
error,fault) do remain visible in Release builds, as these are intended for significant events and errors that should be monitored in production[1][7].
- Apple’s official logging system intentionally suppresses both
-
Best Practices and Rationale:
- Custom logging wrappers sometimes use preprocessor flags (like
#if DEBUG) to allow more verbose logging in development and automatically silence or reduce log output in production[3][4]. - Comprehensive logging at debug level in production isn't supported or recommended, both for privacy and efficiency reasons[1][3].
- If you need to capture additional logs in production, you must log at the
info,error, or higher levels, depending on severity. Alternatively, you may create a custom build configuration, but this is typically discouraged unless necessary for debugging specific production issues[2][4].
- Custom logging wrappers sometimes use preprocessor flags (like
Summary Table:
| Log Level | Debug Build | Release Build |
|---|---|---|
| debug/trace | Visible | Not visible |
| info | Visible | Visible |
| error/fault | Visible | Visible |
References:
- "OSLog and Unified logging as recommended by Apple - SwiftLee"[1]
- "Replacing your logging framework with OSLog - roger.ml"[3]
- "Modern logging with the OSLog framework in Swift - Donny Wals"[7]
If you require debug-level log information in production for troubleshooting, consider elevating affected log statements to the info or error level (temporarily), or, as a last resort, implement a custom logging mechanism with explicit configuration to ensure it does not violate user privacy or App Store requirements[2][3][4].
Citations:
- 1: https://www.avanderlee.com/debugging/oslog-unified-logging/
- 2: https://interrupt.memfault.com/blog/debug-vs-release-builds
- 3: https://www.roger.ml/p/oslog
- 4: https://dev.to/remshams/custom-build-configurations-for-testing-and-debugging-in-ios-2dgj
- 5: https://discussions.apple.com/thread/2684741
- 6: https://forum.juce.com/t/best-practice-to-track-down-a-release-build-only-bug-under-visual-studio/33525
- 7: https://www.donnywals.com/modern-logging-with-the-oslog-framework-in-swift/
- 8: Different result in debug and release builds pointfreeco/swift-composable-architecture#1086
- 9: https://forums.swift.org/t/disabling-os-log-in-release-scheme-only/44631
- 10: https://developer.apple.com/documentation/xcode/testing-a-release-build
@taipaise 맞습니다! 웹 검색 결과 확인해보니 당신의 이해가 정확합니다.
OSLogger의 debug 레벨 로그는 실제로 Release 빌드에서 자동으로 억제됩니다:
- Debug 빌드: debug 레벨 로그가 표시됨
- Release 빌드: debug 레벨 로그가 자동으로 필터링되어 표시되지 않음
- 다른 레벨들 (info, error, fault): Release 빌드에서도 계속 표시됨
이는 Apple이 의도적으로 설계한 보안 및 성능 최적화 기능입니다. 따라서 현재 구현이 보안상 안전하다고 볼 수 있습니다.
다만 코드의 명확성을 위해 이런 동작에 대한 주석을 추가하는 것을 고려해볼 수 있습니다:
// Debug 레벨 로그는 Release 빌드에서 자동으로 억제되어 민감한 정보가 프로덕션에 노출되지 않음
BitnagilLogger.log(
logType: .debug,
message: """
REQUEST➡️
- URL: \(urlString)
- Method: \(method)
- Headers: \(headers) // Release 빌드에서는 로깅되지 않음
- Body: \(body)
"""
)정확한 지식을 가지고 계셨고, 확인해보겠다는 열린 자세도 훌륭합니다! 🐰
(\ /)
( ._. )
o_(")(") // 배워가는 토끼
🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
choijungp
left a comment
There was a problem hiding this comment.
헐 ...... 완전 띵 덕분에 로그 확인이 훨씬 더 수월할 것 같아요 ... ....
미루기만 했던 일을 띵은 바로 해내시다니 !!!!!!
짱푸루부 !!!!!!!!!!!!!! 👍🏻
🌁 Background
📱 Screenshot
👩💻 Contents
Summary by CodeRabbit
신규 기능
리팩터링