DIY Logger 📝
Debugging can be hard.
Sometimes we set a bunch of breakpoints, with good intentions, but then in the process of diving into the stack of callers for each of those breakpoints we lose track of the state causing the problem; and the deeper the dive, the more likely we are to get lost. 👆🏾 👉🏾 👇🏾 👈🏾 🙃
That piece of state might be a bool flag used somewhere that isn’t being reset, or an array that is being accessed from different threads, or in my case, the continuous detection requests being sent out to detect printers — requests that cannot occur concurrently and must also be rate limited.
The point is, we might know the expected behavior, but can’t seem to pin point where the deviation is occurring. To make matters worse, you may have noticed that in some swift files you can’t just
po from the debugger at a breakpoint. So when our breakpoints fail what else is there to do?
Normally, we resort to dropping
Eventually, you have a ton of logs being printed out and can’t remember exactly where that log came from or what triggered it. And when we deal with asynchronous code, the logs can be hard to track if we don’t log when the task begins and ends.
For more rigorous logging needs let create a logger!
Build A Logger 🛠
One of the benefits of using this logger is that file, function, and line number are all free. Using
#line compile-time constants as default parameters, the caller of the
write(message: ) function will provide that information at the call site, behind the scene. This means we can simply write
BasicLogger.write(message: "Hello world!") .
Another thing to note is the use of a serial queue here. File writing is not inherently thread safe and since the callers of
write(message: ) may not necessarily be on the same thread, we need to guard against concurrent calls to write to the log file. Our serial queue does just that, ensuring that file writes will occur one at a time, avoiding a race condition.
The error handling here is primitive at best, so you may need to build it out if it helps.
02-09-2017| 11:57:41 +503|: - DeviceDetection.swift apply(strategy:onDetectable:): 101: -- Applying LOOPING strat for PrintManager
02-09-2017| 11:57:41 +504|: - PrintManager.swift detectDevices(): 139: -- Detection REQUESTED
02-09-2017| 11:57:41 +505|: - PrintManager.swift detectDevices(): 204: -- returning FUTURE
02-09-2017| 11:57:41 +507|: - PrintManager.swift detectDevices(): 166: -- Detection will be issued by Printer SDKs
Accessing Your Log File
I use SimPholders to access my simulator document directory, but it’s completely possible to access the folder via terminal — although slightly cumbersome.
- First you need the file path for the simulator’s home directory. To get this, set a breakpoint and
- Copy the file path.
- Then open terminal and
cd #PASTE FILE PATH HERE#
- Go to
LogFile.txtshould be there.
- Effective logging relies on placing logs at the beginning, middle, and end of an event and marking it so.
- Place log statements at any point in the code where a return occurs and the reason for the return.
- It often helpful to explore every branch of the logic tree that your code traverses, logging events for each branch.
- Log files are particularly useful in determining baseline behavior. So once a change is made the log should differ showing if the change made a difference.
If you rather not build your own logger, there are CocoaPods that have done this, and more, for you like CocoaLumberjack 👌🏾
Anyways, that’s all I’ve got on this one! Thanks for reading and as always feel free to drop a comment below if you have anything to add! ☮️ & ❤️