Logging framework: CocoaLumberjack

      6 Comments on Logging framework: CocoaLumberjack

In this quick article we'll quickly go over a logging framework that takes your print statements to the next level. We'll be talking about CocoaLumberjack.We all know how useful a simple print statement can be while you’re developing an app. In this quick article we’ll quickly go over a logging framework that takes your print statements to the next level. We’ll be talking about CocoaLumberjack.

A Bit of Info

You use print statements in your daily development. And you know how useful they are. You also know that things can get out of control pretty fast. Just remember how many times you found yourself having to scroll through thousands of lines of console output to find something of interest. Imagine you just checked out code and found that someone on your team added a print statement that was logging a couple of hundred lines of text. And let’s face it, most of the logs we add are status messages (collection prints, checkpoints, etc…)

First thing you’ll love about CocoaLumberjack will probably be the log levels you can use. Not all log messages are equally important, and you can set the log level of the messages you’re logging. So when you’re debugging you can print the most detailed logs, or slightly less detailed logs, it’s up to you.

Another great feature is the log output. You can choose where to send output of your log commands. Obviously, you’ll want to send it to your console, but you can also choose the system logger, or the file logger. Now this is where things get interesting.

I could bore you for another couple of paragraphs, or just tell you what I want to do. I want to log stuff in the console while developing an app, but I also want for my users to be able to send me a log file. And I don’t want everything logged into that log file, just the important stuff.

If you wanted to do this on your own, you’re looking at a couple of days of work, but with CocoaLumberjack it’s a couple of minutes of work. Let’s get started.

A Bit of Code

CocoaLumberjack is available as a pod, it’s the simplest way of integrating it with your project, so go ahead and run ‘pod install’ after adding it to your podfile:

pod 'CocoaLumberjack/Swift'

If you’re using swift in your project make sure you import the correct pod.

Setup

You’ll have to import the ‘CocoaLumberjack’ into every file you plan on using it, and you’ll have to set it up before you use it. So let’s import it to our AppDelegate and set it up:

public let fileLogger: DDFileLogger = DDFileLogger()
private func setupLogger() {
    DDLog.add(DDTTYLogger.sharedInstance)
        
    // File logger
    fileLogger.rollingFrequency = TimeInterval(60*60*24)
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7
    DDLog.add(fileLogger, with: .info)
}

Here we’re adding loggers to CocoaLumberjack using the DDLog.add() method, The first logger we’re adding is our standard console output. The second logger is a file logger. We’re keeping a public variable to our file logger in the AppDelegate (more on this later), and we’re keeping the default settings from the CocoaLumberjack examples. We’ll be storing the last 7 days worth of logs and we’ll create a new file each day. What’s interesting to note here is that we’ll only log messages with the level of ‘.info’ or above. We don’t want to spam our logging file with unnecessary messages.

Usage

If you used print statements, you could pretty much replace all instances with one of the ‘DDLog’ statements, check out these couple of examples:

DDLogVerbose("Did select settings action")
DDLogInfo("Did clear cache")
DDLogError("Failed to create cell")
DDLogWarn("Failed to load post details with error: \(error.localizedDescription)")

Everything you log using any of these methods will be logged into the console, but only calls to ‘DDLogInfo’ and higher will be saved into a file.

Sending Logs

A great use case for your logs is for your users to upload the logs to you. You can easily send your logs as an email attachment. Let’s quickly go over an example. Somewhere in your app you’ll have a button that will call a method like this:

    private func sendLogs() {
        guard
            let appDelegate = UIApplication.shared.delegate as? AppDelegate,
            MFMailComposeViewController.canSendMail() == true
            else {
                return
        }
        
        let mailCompose = MFMailComposeViewController()
        mailCompose.mailComposeDelegate = self
        mailCompose.setSubject("Logs for agostini.tech!")
        mailCompose.setMessageBody("", isHTML: false)
        
        let logURLs = appDelegate.fileLogger.logFileManager.sortedLogFilePaths
            .map { URL.init(fileURLWithPath: $0, isDirectory: false) }
        
        var logsDict: [String: Data] = [:] // File Name : Log Data
        logURLs.forEach { (fileUrl) in
            guard let data = try? Data(contentsOf: fileUrl) else { return }
            logsDict[fileUrl.lastPathComponent] = data
        }
        
        for (fileName, logData)  in logsDict {
            mailCompose.addAttachmentData(logData, mimeType: "text/plain", fileName: fileName)
        }
        
        present(mailCompose, animated: true, completion: nil)
    }

After the sanity checks and initialising the mail composer we use the ‘fileLogger’ public variable from the AppDelegate. ‘fileLogger’ has a reference to the file manager that will give you an array of file paths where the logs are saved. We’ll convert these file paths to URLs and create data from it. We’ll save the data in a dictionary where the keys will be the file names.

Next, we’ll iterate over the dictionary and add attachments to our mail. All that’s left is to actually send the mail…

And there you have it, your users sending you logs. When you receive the logs you can see that the logs only contain the necessary data:

How easy was that πŸ™‚

Conclusion

CocoaLumberjack can do so much more, but I believe this will cover most of your use cases. If you’re dealing with a hard to reproduce bug, a good log file is invaluable. This makes it really easy to get the logs off your users and fix production issues.

You can customise your logs beyond belief. You can create custom log formatters, log writers, you can do everything. One of the examples on their GitHub repo is saving the logs in a zip file (I recommend going through that one).

I hope you learned something new today, and that you’ll see a benefit of using a great logging library like CocoaLumberjack. Happy bugfixing and as usual…

Have a nice day πŸ™‚

Dejan.

More resources

6 thoughts on “Logging framework: CocoaLumberjack

  1. Sumit Nihalani

    Hey Dejan,
    this article was very helpful for me.
    However, I have one doubt!
    I actually want two log files.
    One to save general logs and one to save location logs.
    Using your code I can just have one log file.
    How to implement two different log files?

    Reply
      1. Sumit Nihalani

        Dejan,
        That answer on SO is a little confusing since they are using MACROS over there.
        I have been scratching my head for days to use lumberjack in swift and then your article was very helpful.
        Would really appreciate if you can tell me the solution in swift.
        Thanks Man!

        Reply
        1. Dejan Agostini Post author

          Hi Sumit,

          I would love to help you out, but I’m super busy for the next couple of weeks. Sorry πŸ™ If it’s something that can wait, I’ll be happy to write up a small article in 2-3 weeks time.

          Have a nice day πŸ™‚
          ~D;

          Reply
  2. Saurabh

    Hi Dejan,
    Great article. Was greatly helpful.. An addition to this i’m trying to use custom log levels. in swift.. and the from the documentation i’m not getting the procedure clearly.. Could you guide me on how will I be able to make the thing happen..
    Any help is greatly appreciated..

    Thank you in advance.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.