Capturing Network Requests

How to capture network requests

There are multiple options for logging network requests using Pulse.

Automated Logging #

The recommended option it to use URLSessionProxyDelegate to automatically capture all of the important URLSession events, including task metrics. There are two optons how to enable it.

1. Manually

let originalDelegate = YourURLSessionDelegate()
let proxyDelegate = URLSessionProxyDelegate(delegate: originalDelegate) 
let session = URLSession(configuration: .default, delegate: proxyDelegate, delegateQueue: nil)

This option is useful when you want to enable logging on the individual URLSession level.

2. Swizzling

// Call it anywhere in your code prior to instantiating a `URLSession`
URLSessionProxyDelegate.enableAutomaticRegistration()

// Instantiate `URLSession` as usual
let session = URLSession(configuration: .default, delegate: YourURLSessionDelegate(), delegateQueue: nil)

This option is a bit easier to use. It enables logging for all URLSession instances created this way. But it requires a bit more “magic” – swizzling URLSession initializer. It shouldn’t be in debug builds (that’s where you should be using Pulse anyway).

The initial version only supports URLSessionDataTask.

Both approaches require URLSession to be used with a delegate-based approach and won’t work if you are using completion-based APIs. For completion-based APIs, you will have to use NetworkLogger APIs manually to log the events, but this is discouraged because there is no way to capture metrics and some other information this way.

URLSessionProxyDelegate is extremely small and uses responds(to:) and forwardingTarget(for:) methods to forward selectors to the actual session delegate when needed. If you want less magic, use manual integration.

Manual Logging #

URLSessionProxyDelegate uses NetworkLogger to store network requests. If you already have a networking layer set up and don’t want to rely on “magic”, use NetworkLogger directly.

final class NetworkLogger {
    func logTaskCreated(_ task: URLSessionTask)
    func logDataTask(_ dataTask: URLSessionDataTask, didReceive response: URLResponse)
    func logDataTask(_ dataTask: URLSessionDataTask, didReceive data: Data)
    func logTask(_ task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics)
    func logTask(_ task: URLSessionTask, didCompleteWithError error: Error?)
}

Alamofire Integration #

If you are using Alamofire, you can use any of the approaches outlined in this guide. For manual logging, you can use Alamofire.EventMonitor.

import Alamofire

// Don't forget to bootstrap the logging systme first.

let session = Alamofire.Session(eventMonitors: [NetworkLoggerEventMonitor(logger: logger)])

struct NetworkLoggerEventMonitor: EventMonitor {
    let logger: NetworkLogger

    func request(_ request: Request, didCreateTask task: URLSessionTask) {
        logger.logTaskCreated(task)
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        logger.logDataTask(dataTask, didReceive: data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
        logger.logTask(task, didFinishCollecting: metrics)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        logger.logTask(task, didCompleteWithError: error)
    }
}

Automatic Logging #

For fully automated logging, try Experimental.URLSessionProxy.

Experimental.URLSessionProxy.shared.isEnabled = true

The way it works is by registering a custom URLProtocol and using a secondary URLSession instance in it. This will affect your networking and should only be used for evaluating Pulse.