Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 4 additions & 12 deletions Projects/DataSource/Sources/NetworkService/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ final class NetworkService {
plugins = [
TokenInjectionPlugin(),
RefreshTokenPlugin(),
RetryPlugin()]
RetryPlugin(),
BitnagilLoggingPlugin()]
}

func request<T: Decodable>(
Expand Down Expand Up @@ -61,11 +62,6 @@ final class NetworkService {

let (data, response) = try await URLSession.shared.data(for: request)

// TODO: - 로깅 로직 수정
if let httpResponse = response as? HTTPURLResponse {
BitnagilLogger.log(logType: .info, message: "응답 코드: \(httpResponse.statusCode)")
}

if withPlugins {
for plugin in plugins {
try await plugin.didReceive(
Expand All @@ -79,19 +75,15 @@ final class NetworkService {
else { throw NetworkError.invalidResponse }

guard 200..<300 ~= httpResponse.statusCode else {
BitnagilLogger.log(logType: .error, message: "응답 코드: \(httpResponse.statusCode)")
throw NetworkError.invalidStatusCode(statusCode: httpResponse.statusCode)
}

guard !data.isEmpty
else { throw NetworkError.emptyData }
guard !data.isEmpty else { throw NetworkError.emptyData }

do {
let baseResponse = try decoder.decode(BaseResponse<T>.self, from: data)
BitnagilLogger.log(logType: .info, message: "Server Message: \(baseResponse.message)")

guard let responseDTO = baseResponse.data
else { return nil }
guard let responseDTO = baseResponse.data else { return nil }

return responseDTO
} catch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// BitnagilLoggingPlugin.swift
// DataSource
//
// Created by 이동현 on 8/3/25.
//

import Foundation
import Shared

struct BitnagilLoggingPlugin: NetworkPlugin {
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
}
Comment on lines +12 to +34
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

OSLogger의 log level이 debug라 release 환경에서는 로그가 찍히지 않는다 생각했습니다. 추후 확인해보고 잘못된 지식이었다면 조치하도록 하겠습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 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 debug and trace messages 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].
  • 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].

Summary Table:

Log Level Debug Build Release Build
debug/trace Visible Not visible
info Visible Visible
error/fault Visible Visible

References:

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:


@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 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.


func didReceive(response: URLResponse, data: Data?, endpoint: Endpoint) async throws {
let urlString = response.url?.absoluteString ?? ""

guard let httpResponse = response as? HTTPURLResponse else {
BitnagilLogger.log(logType: .error, message: "🚨\(urlString)에 대한 response는 HTTPResponse가 아닙니다.🚨")
return
}

let statusCode = httpResponse.statusCode

// code/message 추출
var code: String = ""
var message: String = ""
if
let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
{
code = json["code"] as? String ?? ""
message = json["message"] as? String ?? ""
}

BitnagilLogger.log(
logType: .debug,
message: """
RESPONSE⬅️
- 요청URL: \(urlString)
- Status: \(statusCode)
- Code: \(code)
- Message: \(message)
"""
)
}
}
Loading