Like it or not, Swift seems to be here to stay, and become the primary language for Cocoa applications on both iOS and OSX. Like probably most of you, I’ve developed a list of best practices and Objective-C code snippets over the years. Porting these components to Swift it’s a nice occasion to dig deeper into the language one step at a time, and it’s doubtless easier than porting a full application in one shot.
I’m starting with log file management, in particular I’ll address these two basic needs:
- write your logs to a custom file destination,
- use different log levels such as info, debug, warning, and so on.
Writing to a custom destination
The simplest way to log things in Cocoa is the
NSLog() function, that writes all entries to the application standard error stream (
stderr). When an OSX application is compiled for release, all these messages go to the “catch-all” system log, while during development they are catched by XCode and displayed in the debug area.
But what if I want my application to have its own log file called
~/Library/Logs/MyApp.log? The trick here is: redirect the standard error stream of the application to a custom file path. In Objective-C you would do it in the
main.m file, before launching
NSApplicationMain(). In Swift I wrote a function named
SetCustomLogFilename(), packaged in a
LogUtils.swift library file. I can call the function inside the
AppDelegate class of an OSX application, by simply overriding the
init() method, just like this:
In the example above I first fetch the application name from the Info dictionary, then I call the default
init() and finally set the log file by calling
SetCustomLogFilename(self.appName). The actual
SetCustomLogFilename() function can be implemented like this:
Firstly I compose the full path for the destination log file. Then I obtain the handle of the
stderr stream using
NSFileHandle and create a backup copy of it with
dup(). A this point we need a file handle for our log file, I’m using
NSFileHandle again here, but first I have to create the empty file since
NSFileHandle won’t do it for me. With the new handle in place we can call
dup2() to redirect the
stderr stream to our new file. If the returned value is
-1 there is an error that we log to
This solution is not only more professional, it’s also useful when monitoring your application, because you can see only relevant messages without noise. Moreover, if your application has a feedback/crash reporter, you can send these messages to your issue management platform.
But wait: what is that
ErrorLog() function? I’ll come to it in a minute.
Different log entries
When you use
NSLog() to write your messages, you log everything everytime. Wouldn’t it be nice if you could different log levels (eg.
ERROR) and, for example, automatically disable debug messages when compiling your app for distribution? In Objective-C you could extend
NSLog() by defining a couple of macros in a common header file:
In Swift it’s a little bit tricky, you can still use “
#if/#else/#endif” preprocessor macros (although more constrained), but you don’t have the same freedom with variadic functions. So I solved this way:
In this case I’m defining some wrapper functions around
NSLog(), each function can be called with a formatted string parameter, like this:
It’s not as immediate as the Objective-C version but it’s still simple and pretty clean, you can grab the code on GitHub and adapt it to your needs. This solution works for me so far, if you need a more featured log library you can use the more powerful XCGLogger written by Dave Wood.
And you, what solution or framework do you yse for your applications? Feel free to share your thoughts in the comments below.