Bugs come in all shapes and sizes. Some bugs are nice and fuzzy and easy to fix because you can launch your app and go right to spot where the bug happens. If you’re lucky, Xcode will even show you which line caused your app to crash.
Other bugs are more sinister. They lurk in the background and happen when you least expect it. They are the dreaded stuff of nightmares and Listerine commercials.
In this tutorial, you’ll discover a secret weapon you can use to crush those sinister bugs in the background. When wielded properly, this weapon is like shining a flashlight into the deepest darkest crevices of your app. Those creepy crawlers won’t stand a chance!
That weapon is SwiftyBeaver, a Swift-based logging framework for iOS and macOS.
In this tutorial, you’ll learn how to debug using SwiftyBeaver and pick up the following skills along the way:
- When SwiftyBeaver logging is better than
print()
andNSLog()
- How to access your SwiftyBeaver logs on Crypto Cloud
- How to use Log Levels for different types of log messages
- How to filter logs to make bug hunting even easier
Note: Before starting this tutorial, you should be familiar with Cocoapods and the CoreLocation framework. Check out the linked tutorials to brush up on these skills first.
Getting Started
Download the starter project here. It includes all of the project files and artwork you will need to complete this tutorial.
You are going to build an app for tracking your children. If they get too far away from home, the app will warn them to stay in the safe zone.
It’s called Ten PM (Do you know where your children are?) after the famous public service announcements from the ’80s.
Open TenPM.xcworkspace and look around the project. Now open Main.storyboard and you’ll see two view controllers inside a navigation controller:
The view controller on the left is the one parents can use to tell the app where “home” is, and also to specify a maximum distance they will allow their child to wander. The backing class is TrackingSetupViewController, which requests location tracking authorization and stores an input distance for geofencing.
The view controller on the right is the one the kids will use. It shows a map with a green circle that represents the safe zone. It also has a text box down below to tell them if they are currently safe or not. The backing class is TrackingViewController
, which draws an overlay on an MKMapView
illustrating the allowable child locations.
Here are a few other files of note:
- LocationTracker.swift: Acts as the Core Location Manager’s delegate. It handles location updates and permission changes.
- ParentProfile.swift: Stores important user data to
NSUserDefaults
. The data includes the parents’ home location and the distance their kids are allowed to wander.
Using The Starter App
Build and run the Ten PM app on the iOS Simulator. You will see the view controlled by TrackingSetupViewController
:
Before you set up tracking, you need to make sure the simulator is providing a starting location. To do that, select the simulator and in the toolbar select Debug\Location\Apple.
Tap the SETUP TRACKING button and the app will ask your permission to start gathering location data. Because this screen is for the parents, there is an extra alert confirming that you are the parent and you are at home. Select Yes and then Allow. Once the app determines your location, it will save your home address to use as the center point for your kid’s safe zone circle.
Now input 1 in the prompt for Safe Distance from home. This means you don’t want to be any further than 1 kilometer from the simulated Apple location.
Tap NEXT and you will be taken to the view backed by TrackingViewController
. This view has an annotation with your current location along with a green circle outlining your safe zone.
Now that you are all set up, it’s time to pretend that it’s the year 2011 and you’ve got an iPhone 4 prototype you’re taking out for a stroll. Let’s simulate a new location, just outside of your safe zone, where you’ve set up a meeting with a reporter from Gawker.
In the simulator menu, go to Debug\Location again, but this time choose Custom Location. Enter 37.3397 as your latitude and -122.0426 as your longitude and hit OK.
You should see the blue dot move to the new location, and the message at the bottom should change to tell you that you are dangerously far from home.
That’s the basic rundown of Ten PM’s functionality. It’s a fairly simple app that can be expanded upon pretty easily. You could have it start sending remote notifications to the parents once their child goes outside of the safe zone, etc.
But you’re not going to do that today. Instead, you’ll use this app to simulate some nasty background bugs that you will then fix with the assistance of SwiftyBeaver.
Installing SwiftyBeaver
First, install SwiftyBeaver via Cocoapods. Find the root directory for Ten PM and locate the Podfile. Open it in the editor of your choice, then add the following line under # Pods for TenPM
:
pod 'SwiftyBeaver' |
Open a Terminal and navigate to the directory with your Podfile. Type the following:
pod install |
After a minute or so, the installation of SwiftyBeaver will complete. Now open AppDelegate.swift and add the following import to the top:
import SwiftyBeaver |
Build the project, and you’ll see SwiftyBeaver is now available to the project because the project will successfully compile.
Note: This install guide only works with Cocoapods and Swift 3. More detailed installation instructions can be found on the SwiftyBeaver GitHub Repository page.
Writing Your First Logs With SwiftyBeaver
Whew. Are you ready to write some code?
Look at AppDelegate.swift again. This is where you will set up logging for SwiftyBeaver. Did you notice the skeleton method provided in the stater project?
setupSwiftyBeaverLogging()
gets called every time your application launches, and as the name implies, you’ll use it to prepare SwiftyBeaver for use. Go to that method and add the following:
let console = ConsoleDestination() SwiftyBeaver.addDestination(console) |
SwiftyBeaver isn’t any ordinary logging tool. It has multiple inputs called Destinations that you can configure to suit your needs. A Destination defines where your logs appear.
ConsoleDestination()
creates a Console Destination, which you added as an active SwiftyBeaver destination. Now any log messages you create will show up in the console, just like a print()
statement would.
In application(_:didFinishLaunchingWithOptions:)
, add the following after the call to setupSwiftyBeaverLogging
:
SwiftyBeaver.info("Hello SwiftyBeaver Logging!") |
This will print an info level log to the console when the app starts up. You’ll learn more about log levels in a moment.
Build and run Ten PM again. Check the console, and you’ll see the following log message:
12:06:05.402 💙 INFO AppDelegate.application():36 - Hello SwiftyBeaver Logging!
Cool! You’re logging to the console with SwiftyBeaver. The heart relates to the log level, which are explained below.
A Brief Explanation of Log Levels
At this point, I am sure you’re wondering why SwiftyBeaver makes you use a method named info()
instead of the far more intuitive log()
or print()
.
It has to do with something called Log Levels.
Not everything you log is equally important. Some logs are useful for giving the programmer a bit of extra contextual information. Other logs communicate more serious issues like errors and warnings.
When reading through a log file, it is especially helpful to have log messages that are categorized by the threat they pose to your application. It helps you filter through the less important messages quickly so you can fix bugs faster.
SwiftyBeaver sticks to log level conventions and adopts these five log levels:
- Verbose: The lowest priority level. Use this one for contextual information.
- Debug: Use this level for printing variables and results that will help you fix a bug or solve a problem.
- Info: This is typically used for information useful in a more general support context. In other words, info that is useful for non developers looking into issues.
- Warning: Use this log level when reaching a condition that won’t necessarily cause a problem but strongly leads the app in that direction.
- Error: The most serious and highest priority log level. Use this only when your app has triggered a serious error.
How to Write to Different Log Levels With SwiftyBeaver
Just as you used SwiftyBeaver’s info()
method to log to the info log level, you can use the other four methods — verbose()
, debug()
, warning()
, and error()
— to log the other four levels.
Give it a try. Back in application(_:didFinishLaunchingWithOptions:)
, replace the call to info()
with this:
SwiftyBeaver.debug("Look ma! I am logging to the DEBUG level.") |
Now build and run your app. You should see a different colored heart icon and a log message at the debug level.
14:48:14.155 💚 DEBUG AppDelegate.application():36 - Look ma! I am logging to the DEBUG level.
Notice that the color of the icon changed to green to indicate the Debug log level. It’s one of the reasons why SwiftyBeaver is better than print()
and NSLog()
. You can quickly scan your logs to find messages at the log level you are interested in.
Note: Try not to abuse the higher priority log levels. Warnings and errors should be reserved for situations that require attention. It’s just like that old saying, “When everything is urgent, nothing is.”
Setting Up The SwiftyBeaver Crypto Cloud
One of the coolest features in SwiftyBeaver is the ability to log directly to the cloud. SwiftyBeaver comes with a macOS App that lets you view your logs in real time. If you have ever wondered what’s happening in your app that is installed on thousands of devices, now you can know.
Download The SwiftyBeaver Mac App. Open the app, and fill out the form to create an account.
Next, you will be taken directly to a dialog box that asks you to save a file. It’s a little strange because they don’t tell you what the file is for.
Name it TenPMLogs, chose any location you prefer and hit Save.
The file you created stores the logs for a single app, which is why you named it TenPMLogs. When you open this file using the SwiftyBeaver Mac App, you can view the logs associated with the Ten PM app.
Once you have saved your log file, you will be presented with a choice. You can either register a new app, or you can view logs from a previously registered app. You’ll continue with a New App.
Click on the Generate New App Credentials button. You should see the following screen, showing the generated ID and keys for your app:
You are going to add another log destination using the Crypto Cloud security credentials just generated. Keep this window open, and go back to setupSwiftyBeaverLogging()
in AppDelegate.swift.
Add these lines to the bottom of the method, substituting the string values shown in the macOS app for the stand-in strings below:
let platform = SBPlatformDestination(appID: "Enter App ID Here", appSecret: "Enter App Secret Here", encryptionKey: "Enter Encryption Key Here") SwiftyBeaver.addDestination(platform) |
Go back to the SwiftyBeaver Mac App and click on the Connect button. Then build and run Ten PM.
Note: If you are a silly fool like me, and you clicked the Connect button before copying your credentials into your app, you can click on the settings gear in the SwiftyBeaver Mac App to view them after connecting.
Check it out! Your log message now appears in the SwiftyBeaver Mac App. If you don’t see the logs right away, don’t despair. Sometimes, it can take a few minutes for the log messages to get to the cloud. They will show up eventually. You can see that I anxiously relaunched the app to see if it might make my log messages appear faster.
SwiftyBeaver also puts an automatic one hour expiration on all logs you generate — that is, unless you upgrade to a paid account. For most debugging tasks, this won’t be an issue. But it is worth mentioning in case you ever wonder why your older logs aren’t visible any more.
Filtering Logs By Log Level, Favorites, and Minimum Log Levels
The truly great thing about the SwiftyBeaver Mac App is the ability to filter your logs by log level. It dramatically simplifies the process of digging through log files to find the cause of a critical bug.
You may have noticed the different tabs up at the top. Each of those tabs represents a different log level. You can view multiple log levels at a time, or you can drill down and look at just the warnings and errors.
You can also star a log to mark it as a Favorite. You can view all favorites by clicking on the star in the left hand menu.
Filtering Logs With Minimum Log Levels
There’s one more feature you’re really going to love. SwiftyBeaver lets you set a minimum log level for a given log destination. If you want to reserve your Crypto Cloud account for serious warnings and errors, you can do that.
First, replace the debug log statement in applicationDidFinishLaunching()
with the following:
SwiftyBeaver.verbose("Watch me bloviate. I'm definitely not important enough for the cloud.") SwiftyBeaver.debug("I am more important, but the cloud still doesn't care.") SwiftyBeaver.info("Why doesn't the crypto cloud love me?") SwiftyBeaver.warning("I am serious enough for you to see!") SwiftyBeaver.error("Of course I'm going to show up in your console. You need to handle this.") |
Now you’re logging a message at every level. Build and run the app, and you should see all of the logs make it up to Crypto Cloud.
In setupSwiftyBeaverLogging()
, add the following right before you add the platform logging destination:
platform.minLevel = .warning |
Build and run the app again. Take another look at your Crypto Cloud console.
You should only see the warnings and errors for the latest timestamp. None of the other log statements will have made it to the Crypto Cloud console. You will still see everything in the Xcode console!
Note: You can specify a minimum log level for any type of SwiftyBeaver logging destination. You can even create multiple Crypto Cloud destinations for different log levels just to keep things nice and separate. SwiftyBeaver has a lot of wiggle room for customization.
Fixing Hard-to-Reach Bugs
It’s been fun to talk about logging to Crypto Cloud, but you have some sinister bugs you need to fix — or at least, some sinister bugs that you are going to intentionally simulate and then fix.
Start by cleaning up some of your earlier logs. Remove all of the logs from application(_:didFinishLaunchingWithOptions:)
. Also delete the platform.minLevel
setting so that the default of printing all logs occurs.
For this test, you’ll want to see all logs.
Simulating a Bug
Now that you are ready to go with cloud logging, it’s time to simulate a nasty background bug.
Open LocationTracker.swift and find locationManager(_:didUpdateLocations:)
. Insert the following code right after the two guard
statements at the top:
let bug = true if bug == true { return } |
This is rather silly, but you are pretending there’s a bug somewhere in the LocationTracker
preventing it from tracking the user’s location. The placement here prevents the notifications that indicate the user has entered or left the safe zone from posting. When the bug is “turned off”, the behavior will function normally.
Build and run to confirm the issue.
As long as you left the location in the custom area used previously, you’ll see the bug. Although you are clearly outside of the safe zone, the text is indicating you are a safe distance from home.
Bug Sleuthing With SwiftyBeaver
Now how might you do some sleuthing with SwiftyBeaver?
If you received error reports about tracking failures, and had no good guesses on the bug, put log statements everywhere to gather as much information as possible from impacted users.
First, go up to the top of the LocationTracker
class and import SwiftyBeaver:
import SwiftyBeaver |
Next, put a log statement at the top of the locationManager(_:didUpdateLocations:)
:
SwiftyBeaver.info("Got to the didUpdateLocations() method") |
Now go a few lines down and paste this line of code right after the line where you declare bug
:
SwiftyBeaver.debug("The value of bug is: \(bug)") |
Next add another inside of the conditional where you check for the presence of the bug, just before the return
:
SwiftyBeaver.error("There's definitely a bug... Aborting.") |
And finally, put one more log at the end of locationManager(_:didUpdateLocations:)
:
SwiftyBeaver.info("Got to the end the didUpdateLocations method") |
That ought to be enough information to start figuring out what’s going on in your code. For reference, here is what the entire contents of locationManager(_:didUpdateLocations:)
should look like:
SwiftyBeaver.info("Got to the didUpdateLocations() method") guard let homeLocation = ParentProfile.homeLocation else { ParentProfile.homeLocation = locations.first return } guard let safeDistanceFromHome = ParentProfile.safeDistanceFromHome else { return } let bug = true SwiftyBeaver.debug("The value of bug is: \(bug)") if bug == true { SwiftyBeaver.error("There's definitely a bug... Aborting.") return } for location in locations { let distanceFromHome = location.distance(from: homeLocation) if distanceFromHome > safeDistanceFromHome { NotificationCenter.default.post(name: TenPMNotifications.UnsafeDistanceNotification, object: nil) } else { NotificationCenter.default.post(name: TenPMNotifications.SafeDistanceNotification, object: nil) } } SwiftyBeaver.info("Got to the end the didUpdateLocations method") |
In the sim, set the location to Apple just as you did before. Now build and run the application. Although you’ll have the Xcode console logs available in this scenario, you’re going to ignore them and imagine you’re monitoring cloud logs from a remote user.
Enter a safe distance of 1 km, and press NEXT. After the map loads, change your location to a latitude of: 37.3397 and longitude: -122.0426 via a custom location.
Again, you’ve moved out of your safe zone without the text updating.
You should also notice the following logs repeating on the SwiftyBeaver Crypto Cloud, after setting the log filter to ALL:
Wow, that’s really helpful! If you go back to your code in the LocationTracker
class, you can compare it to the logs and see how far the app got before it stopped executing your code. Here it clearly bailed out in the if bug == true
check, where an error was printed.
To “fix” the bug, simply set the bug variable to false when it is declared in locationManager(_:didUpdateLocations:)
.
let bug = false |
Build and run Ten PM. Simulate starting at Apple and moving outside of the safe zone. This time, you’ll see the safe zone warning is properly triggered.
You should also see the following logs in your Crypto Cloud console.
It looks like the app got past the problem and is successfully responding to location changes once more. You squashed that bug!
Where to Go From Here?
You can find the completed sample project from this tutorial here.
Also check out File Logging with SwiftyBeaver. If you have an iPhone and want to be 100% certain that you’ll get your logs (even when there’s no Internet connection), this feature can be incredibly helpful.
For more advanced use cases, check out custom formatting for your logs.
If you have any questions or comments on this tutorial or SwiftyBeaver, please come join the discussion below!
The post SwiftyBeaver Tutorial for iOS: A Logging Platform for Swift appeared first on Ray Wenderlich.