Skip to content

Commit

Permalink
Network logging
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira committed Aug 8, 2023
1 parent 48bfe61 commit f3b6c2f
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/internal/http/DescopeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class DescopeClient: HTTPClient {

init(config: DescopeConfig) {
self.config = config
super.init(baseURL: config.baseURL, networking: config.networking)
super.init(baseURL: config.baseURL, logging: config.logging, networking: config.networking)
}

// MARK: - OTP
Expand Down
27 changes: 23 additions & 4 deletions src/internal/http/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import Foundation

class HTTPClient {
let baseURL: String
let logging: DescopeConfig.Logging?
let networking: DescopeConfig.Networking

init(baseURL: String, networking: DescopeConfig.Networking?) {
init(baseURL: String, logging: DescopeConfig.Logging?, networking: DescopeConfig.Networking?) {
self.baseURL = baseURL
self.logging = logging
self.networking = networking ?? DefaultNetworking()
}

Expand Down Expand Up @@ -57,7 +59,6 @@ class HTTPClient {
private func call(_ route: String, method: String, headers: [String: String], params: [String: String?], body: Data?) async throws -> (Data, HTTPURLResponse) {
let request = try makeRequest(route: route, method: method, headers: headers, params: params, body: body)
let (data, response) = try await sendRequest(request)
guard let response = response as? HTTPURLResponse else { throw DescopeError(httpError: .invalidResponse) }
if let error = DescopeError(httpStatusCode: response.statusCode) {
throw errorForResponseData(data) ?? error
}
Expand Down Expand Up @@ -87,10 +88,28 @@ class HTTPClient {
return url
}

private func sendRequest(_ request: URLRequest) async throws -> (Data, URLResponse) {
private func sendRequest(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
do {
return try await networking.call(request: request)
logging?.log(.info, "Starting network call", request.url)
#if DEBUG
if let body = request.httpBody, let requestBody = String(bytes: body, encoding: .utf8) {
logging?.log(.debug, "Sending request body", requestBody)
}
#endif

let (data, response) = try await networking.call(request: request)
guard let response = response as? HTTPURLResponse else { throw DescopeError(httpError: .invalidResponse) }

logging?.log(.info, "Network call finished", request.url, response.statusCode)
#if DEBUG
if let responseBody = String(bytes: data, encoding: .utf8) {
logging?.log(.debug, "Received response body", responseBody)
}
#endif

return (data, response)
} catch {
logging?.log(.error, "Network call failed", request.url)
throw DescopeError.networkError.with(cause: error)
}
}
Expand Down
51 changes: 23 additions & 28 deletions src/sdk/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,16 @@ public struct DescopeConfig {

/// An optional object to handle logging in the Descope SDK.
///
/// The default value of this property in `release` builds is `nil` and thus logging will
/// be completely disabled. In` debug` builds a default logger instance is used that prints
/// error and warning log messages to the standard output.
/// The default value of this property is `nil` and thus logging will be completely
/// disabled. During development if you encounter any issues you can create an
/// instance of the `Logging` class to enable logging.
///
/// During development if you encounter an issues you can set the log level to `debug` to
/// see all log messages.
/// Descope.config = DescopeConfig(projectId: "...", logging: DescopeConfig.Logging())
///
/// If the your application uses some logging framework or third party service you can
/// forward the Descope SDK logging messages to it by creating a new subclass of `Logging`
/// and overriding the `output` method.
public var logging: Logging? = nil
/// If your application uses some logging framework or third party service you can forward
/// the Descope SDK logging messages to it by creating a new subclass of `Logging` and
/// overriding the `output` method.
public var logging: Logging?

/// An optional object to override how HTTP requests are performed.
///
Expand All @@ -40,41 +39,37 @@ public struct DescopeConfig {
/// the Descope console.
/// - baseURL: An optional override for the URL of the Descope server, in case it
/// needs to be accessed through a CNAME record.
public init(projectId: String, baseURL: String? = nil) {
public init(projectId: String, baseURL: String? = nil, logging: Logging? = nil) {
self.projectId = projectId
self.baseURL = baseURL ?? self.baseURL
#if DEBUG
self.logging = Logging()
#endif
self.logging = logging
}
}

/// Optional features primarily for testing and debugging.
extension DescopeConfig {
/// The `Logging` class can be used to customize logging functionality in the Descope SDK.
///
/// The default behavior is for error and warning log messages to be written to the standard
/// output using the `print()` function. Set the `level` property to `info` or `debug` for
/// more verbose logging.
///
/// let config = DescopeConfig(projectId: "...")
/// config.logging?.level = .debug
/// Descope.config = config
/// The default behavior is for log messages to be written to the standard output using
/// the `print()` function.
///
/// To customize how logging functions in the Descope SDK create a subclass of `Logging`
/// and override the ``output(level:message:debug:)`` method. See the documentation
/// for that method for more details.
open class Logging {
/// The severity of a log message.
public enum Level: Int {
case error, warn, info, debug
case error, info, debug
}

/// The maximum log level that should be printed.
///
/// The default value is `warn`.
public var level: Level = .warn
public let level: Level

/// Creates a new `Logging` object.
public init(level: Level = .debug) {
self.level = level
}

/// Formats the log message and prints it.
///
/// Override this method to customize how to handle log messages from the Descope SDK.
Expand Down Expand Up @@ -130,15 +125,15 @@ extension DescopeConfig {
/// use code such as this:
///
/// let config = DescopeConfig(projectId: "...")
/// config.networking = SessionNetworking(session: appSession)
/// config.networking = AppNetworking(session: appSession)
/// let descopeSDK = DescopeSDK(config: config)
///
/// // ... elsewhere
///
/// class SessionNetworking: DescopeConfig.Networking {
/// class AppNetworking: DescopeConfig.Networking {
/// let session: URLSession
///
/// init(session: URLSession) {
/// init(_ session: URLSession) {
/// self.session = session
/// }
///
Expand All @@ -156,7 +151,7 @@ extension DescopeConfig {
/// - Note: The code that calls this function expects the response object to be an
/// instance of the `HTTPURLResponse` class and will throw an error if it's not.
/// This isn't reflected here to keep this simple to use and aligned with the
/// types returned by the `URLSession` functions.
/// types in the `data(for:)` method in `URLSession`.
open func call(request: URLRequest) async throws -> (Data, URLResponse) {
throw DescopeError.networkError.with(message: "Custom implementations must override the call(request:) method")
}
Expand Down
2 changes: 1 addition & 1 deletion test/http/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest
@testable import DescopeKit

class TestHttpMethods: XCTestCase {
let client = HTTPClient(baseURL: "http://example", networking: MockHTTP.networking)
let client = HTTPClient(baseURL: "http://example", logging: nil, networking: MockHTTP.networking)

func testGet() async throws {
MockHTTP.push(json: MockResponse.json, headers: MockResponse.headers) { request in
Expand Down

0 comments on commit f3b6c2f

Please sign in to comment.