Pulse SDK Integration Tutorial for iOS: Network Logger

Learn how to set up network logger for your app using Pulse SDK. Pulse framework provides you a UI to display the logs in your debug app and also persist the logs that can be exported anytime. By Mark Struzinski.

5 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Update NetworkService with URLSessionTaskDelegate

While in NetworkService.swift, update NetworkService to make it a class instead of a struct and have it inherit from NSObject.

Update the declaration as follows:

class NetworkService: NSObject {
  ...
}

This change is required so that the class can become a URLSession delegate.

Add an extension to NetworkService and declare conformance to URLSessionTaskDelegate and URLSessionDataDelegate and add the first delegate method by adding the following code at the bottom of the file:

extension NetworkService: URLSessionTaskDelegate, URLSessionDataDelegate {
  func urlSession(
    _ session: URLSession, 
    dataTask: URLSessionDataTask, 
    didReceive response: URLResponse, 
    completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
  ) {
    // 1
    logger.logDataTask(dataTask, didReceive: response)
    // 2
    if let response = response as? HTTPURLResponse,
      response.statusCode != 200 {
      searchCompletion?(.failure(.invalidResponseType))
    }

    // 3
    completionHandler(.allow)
  }
}

This delegate function fires when urlSession receives a response. Here’s a code breakdown:

  1. This is your first Pulse network logging statement! It tells Pulse to log that the data task received a response.
  2. If the response isn’t a 200 status code, or success, you call searchCompletion with a failure result.
  3. However if the request was successful, you call the delegate call’s completion handler to allow the data task to continue.

Next, add two more delegate methods:

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

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

The above functions add more Pulse log points for the data task at the following points:

  1. Successful completion of the task.
  2. When metrics have been collected.

You have one final delegate protocol to implement.

Update NetworkService with URLSessionDataDelegate

Because you’ve switched to using URLSession delegates, you still need a way to hook into the response when data is received, parse it and update the backing array when you have updated results.

At the bottom of the extension, add your final delegate function:

func urlSession(
  _ session: URLSession, 
  dataTask: URLSessionDataTask, 
  didReceive data: Data
) {
  // 1
  logger.logDataTask(dataTask, didReceive: data)
  
  do {
    // 2
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase

    let movieResponse = try decoder.decode(MovieResponse.self, from: data)
    // 3
    searchCompletion?(.success(movieResponse.list))
  } catch {
    // 4
    searchCompletion?(.failure(NetworkError.invalidParse))
  }
}

This function:

  1. Sends a log statement to Pulse for the receipt of data.
  2. Attempts to decode the response.
  3. If successful, calls searchCompletion with a success result.
  4. If parsing fails, calls completion with an appropriate error and a failure result.

Next, you need to update MovieListViewModel to incorporate the changes to the search request.

Update the List View Model

Open MovieListViewModel.swift. Add a function under search() to handle the search response:

private func processSearchResponse(result: Result<[Movie], NetworkError>) {
  // 1
  DispatchQueue.main.async {
    // 2
    self.loading = false
    // 3
    guard let list = try? result.get() else {
      return
    }
    
    // 4
    self.movieList = list
  }
}

The above code:

  1. Moves to the main queue because it’ll trigger a UI update.
  2. Sets the loading property to false. Updating this property re-renders the UI and causes the progress spinner to disappear.
  3. If there are no results, returns early so as not to replace any existing list with an empty one.
  4. Sets the published property movieList to the search results. This will trigger an update to the search results UI.

Now, update the search() function to:

func search() {
  // 1
  if let task = currentTask {
    task.cancel()
    currentTask = nil
  }
  // 2
  DispatchQueue.main.async {
    guard !self.searchText.isEmpty else {
      self.movieList = []
      return
    }
  }

  // 3
  logger.info("Performing search with term: \(self.searchText)")
  loading = true
  
  // 4
  networkService.searchCompletion = processSearchResponse(result:)
  
  // 5
  let task = networkService.search(for: searchText)
  // 6
  currentTask = task
}

Here’s a code breakdown:

  1. If currentTask isn’t nil, that means a request is in progress. Cancel it to perform the new one.
  2. If searchText is empty, clear the list.
  3. Then, log the search and set the loading state to true.
  4. Set the new searchCompletion property to the processSearchResponse(result:).
  5. Create the task and update the currentTask property.

That’s it! Build and run. You’ll see the app behave in the same way. But now, when you perform a few searches and tap the network button, you’ll get logs of the network activity!

Make a few different searches and then go into the Pulse UI. Take some time to inspect the Pulse UI now that you have some log data to explore.

The first view you’ll see is the Console tab:

Pulse console log results

This view mixes all log items: network and standard SwiftLog entries. There is textual search and filter UI as well. You can filter by date and log level:

Filter UI

Tapping an item gives you a detail summary:

Detail log summary

Pin detail items by tapping the pin icon at the top. The Share button lets you share your Pulse document for others to view in one of the dedicated iOS or macOS apps.

Pin an item now to save it.

Tapping View on the Response Body section shows you a formatted JSON response body:

Detail response body

Back in the main request view, tap the Headers segment at the top and you’ll see a dedicated view of request and response headers:

Detail log headers

Select the Metrics segment and you’ll see network performance stats:

Metrics display

Back in the main Pulse UI console, if you select the network tab, you’ll only see network logs and exclude the SwiftLog standard entries:

Network tab

Select the Pins tab to see the item you pinned as a favorite:

Pins tab

Then select Settings to see some options available to you:

Settings tab

Browse Files lets you browse the Files app for stored Pulse logs from outside this app. Unfortunately, an apparent bug in this UI doesn’t let this modal experience dismiss. So if you go into this then you’ll need to kill the app and relaunch it to get back to the app.

Remote Logging pairs with Pulse Pro to allow livestreaming of network logs via the macOS app.

You have one more area to update for Pulse logging.

Capturing Image Requests

The list and detail views both use NetworkService to download poster images. You’ll add logging for image requests as well. It makes sense to split this functionality into a helper class to add the logging.