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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Pulse SDK Integration Tutorial for iOS: Network Logger
30 mins
- Getting Started
- Introducing Pulse
- PulseCore
- PulseUI
- Document-Based Pulse Apps
- Pulse Integration
- Logging Basic Data
- Setting Up Pulse Logs
- Introducing SwiftLog
- Log Levels
- Message
- Metadata Value
- Logging With SwiftLog
- Using PulseUI to View Logs
- Capturing Network Traffic
- Adding a Logger Instance
- Implementing URLSession Delegates
- Update Search to Work with a Delegate
- Update NetworkService with URLSessionTaskDelegate
- Update NetworkService with URLSessionDataDelegate
- Update the List View Model
- Capturing Image Requests
- Set up ImageDownloader
- Update MovieDetailViewModel
- Where to Go From Here?
Logging Basic Data
In addition to network logs, Pulse lets you take advantage of SwiftLog to log any data you need outside of networking-related concerns. You’ll view these logs alongside your network logs.
Setting Up Pulse Logs
First, initialize and configure the Pulse logging backend. Open AppMain.swift. At the top of the file, add an import for Pulse
and Logging
under the other imports:
import Pulse
import Logging
In init()
, add the following code under setupNavbarAppearance()
:
LoggingSystem.bootstrap(PersistentLogHandler.init)
This code configures SwiftLog to use Pulse’s PersistentLogHandler
.
Next, you’ll log some information using SwiftLog, the underlying subsystem Pulse uses.
Introducing SwiftLog
SwiftLog is an open-source logging implementation by Apple. SwiftLog statements have three components: Log Levels, Message and MetadataValue.
Log Levels
SwiftLog provides seven built-in log levels:
trace
debug
info
notice
warning
error
critical
You’ll use these levels as part of each log statement based on the severity.
Message
The message is the primary piece of information sent to a log statement. You can make use of interpolated strings in the message to add dynamic data at runtime in your logs.
Metadata Value
This optional parameter helps you attach more data to the log statement. It can be a String
, Array
or Dictionary
. You won’t use this value in your log statements here, but if you ever need to attach more context to your logs, this is the place to do so.
Are you ready to add your first log using SwiftLog API? Here you go! :]
Logging With SwiftLog
Open MovieListViewModel.swift. Then import Logging under the other imports:
import Logging
Under the declaration of the networkService
property, add:
let logger = Logger(label: "com.razeware.moviesearch")
This line creates a SwiftLog Logger
instance you can use to log events. You’ve given it a label that will separate these log messages from any others.
Next, you’ll log an event when the user performs a search.
Add the following code inside the search()
function right before the loading = true
:
logger.info("Performing search with term: \(self.searchText)")
So you’ve added a log. But you have no way to view the logs! In the next section, you’ll remedy that.
Using PulseUI to View Logs
Pulse comes with a built-in viewer for logs available on macOS, iOS, iPadOS and tvOS. You can use it anywhere in your UI where you want to see what’s happening in your network stack.
In this tutorial, you’ll set up the viewer as a modal view that displays via a toolbar button. You might want to make this view only available in debug builds or hide it deeper in the UI in a production app. Pulse is an excellent debugging tool, but you wouldn’t want it front and center in your app’s UI!
Press Shift-Command-O and open ContentView.swift. At the top, right under the SwiftUI import, add an import for PulseUI:
import PulseUI
Next, under the declaration of var viewModel
, add a state variable to control sheet visibility:
@State
private var showingSheet = false
This state variable controls when the PulseUI log view renders as a modal.
Next, right under the .searchable
modifier, add a sheet modifier:
.sheet(isPresented: $showingSheet) {
MainView()
}
PulseUI provides MainView
. It’s an easy-to-use view that displays a lot of information about your network and SwiftLog data.
You added the ability to display the log view in a sheet, but you haven’t created a way to trigger the display yet. You’ll handle that now via a toolbar button.
Under the .onSubmit
action, add a toolbar modifier:
// 1
.toolbar {
// 2
ToolbarItem(placement: .navigationBarTrailing) {
// 3
Button {
// 4
showingSheet = true
} label: {
// 5
Image(systemName: "wifi")
}
}
}
This code:
- Adds a toolbar to the navigation bar.
- Creates a
ToolbarItem
aligned to the right. - Adds a
Button
as content to theToolbarItem
. - When a user taps the button, sets the
showingSheet
variable totrue
. - Uses the SFSymbol for WiFi as an icon for the button.
That’s all you need to display your log UI. Build and run.
When the app launches, you’ll see your new toolbar item on the right side of the navigation bar:
Perform a couple of searches, then click the new toolbar button. You’ll see your search logs:
You won’t see much on the other tabs yet. You’ll explore them after you start capturing network traffic.
Capturing Network Traffic
Pulse has two methods for logging and capturing network traffic. You can use a combination of URLSessionTaskDelegate
and URLSessionDataDelegate
, or set up automated logging when your app launches.
The recommended approach is to use delegation to log your network requests. The automated approach uses an Objective-C runtime feature called swizzling to inject Pulse into the depths of URLSession
. This allows Pulse to capture all requests regardless of origin. While this may be desirable in some instances, it is something that can be dangerous. So you’ll follow the recommended approach here.
Next, you’ll update the networking stack to use delegation as a way to insert Pulse into the mix.
Adding a Logger Instance
Open NetworkService.swift. Then add an import for PulseCore at the top of the file under the other imports:
import PulseCore
Under the declaration of urlSession
, create a logger
:
private let logger = NetworkLogger()
NetworkLogger
is the class Pulse provides to perform all your logging functions. You’ll use this instance in the next step to log network activity.
Implementing URLSession Delegates
First, you need to change the network interaction to enable Pulse logging. You need to hook up Pulse to various different parts of the network request. This means you will switch from handling the search network request with an inline completion block, to a cached block that will execute as part of one of the URLSession
delegate callback.
Update Search to Work with a Delegate
In NetworkService.swift, add the following property underneath the declaration of logger
:
var searchCompletion: ((Result<[Movie], NetworkError>) -> Void)?
This property has the same signature as the completion block for search(for:)
.
Now, update the search(for:)
function to remove parsing, error and completion handling. All of that responsibility will shift to the delegate.
Replace the search(for:)
function with the following code:
@discardableResult
// 1
func search(for searchTerm: String) -> URLSessionDataTask? {
// 2
guard let url = try? url(for: searchTerm) else {
searchCompletion?(.failure(NetworkError.invalidURL))
return nil
}
// 3
let task = urlSession.dataTask(with: url)
// 4
task.delegate = self
// 5
task.resume()
// 6
return task
}
This code:
- Removes the completion argument and passes the search term entered in the search bar.
- Then checks for a valid URL. If the URL is not valid then it calls the completion handler, if it exists.
- Creates a data task from the URL.
- Then sets the task’s delegate to
self
. - Calls
resume()
to start the request. - Finally, returns the task so you can cancel it if needed.
You’ll see a warning, but don’t worry. You’ll fix it soon.