Frameworks Need Better Logging

Over the past couple years, I’ve worked with quite a few third-party frameworks. These experiences have highlighted a significant deficiency common in many frameworks; most frameworks suck at logging. More recently, I’ve had the chance to build a few frameworks from the ground up, so I’ve used those to explore how logging could be done differently.

Bad: NSLog

There are two common ways to handle logging inside a framework, and both of them stink. The first is “good ol’ fashioned” NSLog() or print/f(). As a framework does its work, it merrily peppers the console with diagnostic or error information. These messages are usually helpful during development, but this pattern leaves a lot to be desired when customers report bugs and the console messages are absent.

Also Bad: Another Framework

The second is the utilization of a logging framework like CocoaLumberjack.1 Logging frameworks are great because they offer lots of flexibility, including mechanisms for collecting and sending logs in bug reports. Seems great, right? The framework can push error messages into the log, and my app can bundle those log messages up when it sends in a bug report. Except that this app doesn’t use CocoaLumberjack – it uses NSLogger. What’s worse is that another framework in the app uses GTMLogger. All of the sudden, there are three logging frameworks and they don’t work together.

A Simpler Way

Great frameworks should strive for zero dependencies. Since integrating a logging framework into your framework is contrary to that goal, let’s explore a simple approach that gives your framework’s integrators more control of logging in their apps.

The approach below is pretty simple, but after using it a number of times in frameworks I’ve helped build, it has proven quite effective. The example I’ll give is written in Objective-C, but the pattern translates to Swift nicely.

Public Interface

Here are the public parts: a register and unregister function, along with a callback signature. I also prefer to provide basic levels, but try not to get too carried away – keep it simple.

App Integration

Early in the process lifecycle, the app can register a callback with the framework. This block of code gets called any time the framework produces a logging message. Since a level is passed along, the application can determine how to either map the message to another logging system, or simply discard it.

Framework Internals

The internals of the framework’s logging are even simpler: a basic C function for producing log messages. This, of course, could be improved to use preprocessor macros to include inline file names, line numbers, and compile-time configuration for log levels.

Any time the framework needs to produce a log message, it simply calls the log function:

Logging Implementation

Here’s the basic logging implementation from inside the framework. Obviously, your version may require more safety checking (locks around the callback, etc.) or more sophisticated dispatching to protect certain threads and queues, but this should get you started.

Hopefully this gives you some ideas about how you can improve logging in your own frameworks to make integration even easier. If you have questions or want to share some ideas, you can find me on Twitter.

  1. I’m not picking on CocoaLumberjack at all here – I use it in my apps, and you should consider doing so as well. It’s a great, battle-tested framework.

The Cost of Swift

Before I get started, I need to preface this essay with a few important points:

I really like Swift. I’ve built quite a few apps with it, both at work an on my own. Astra is almost entirely written in Swift, with only a few classes that touch Core Audio written in Objective-C.

My opinions are my own – they don’t reflect those of my employer or my coworkers.

Swift, a language that is less than three years old (publicly), has gained an amazing amount of momentum within the iOS/Mac community, as well as the broader programming populace. It boasts some ambitious goals of speed, safety, and expressiveness. Naturally, Apple’s emphasis on the language has also bolstered its adoption.

After building my own product in Swift and watching other product owners struggle with a common issue, it’s time to address a problem with the language: the cost of using Swift. To demonstrate, I’m going to share a story.

Astra 1.0

Astra was initially written in Swift 2.3. In the interest of releasing version 1.0 of the app, I delayed the Swift 3 migration last fall and shipped with Swift 2.3. As the product owner, I had to make a conscious decision about when I was going to bite off the Swift 3 migration, and getting the app in the market outweighed the debt of an older Swift version.

Once version 1.0 shipped, I began working on the next minor version of the app – a few new features, some enhancements – typical dot-fix things. As anticipated, a patch release needed to be shipped, so I fixed a couple critical bugs in 1.0.1, and then enjoyed the holidays with my family.

Side Projects Cost Time

Astra is a side project; I have a full time job, as well as a family and other “real world” responsibilities. When I get time to work on side projects, it’s usually for 30-60 minutes at a time, and almost always after 10:30pm.

All projects have some sort of constraint. It’s usually budget. Since I work on Astra for “free,” my constraint isn’t budget, but time. Each minute I spend working on it takes me away from my family, sleep, other hobbies, or a whole list of other things I really need to do.

The Swift 3 migration took me four nights, somewhere between 6-8 hours in total. This effort spanned a couple weeks, since I don’t necessarily have consecutive days to work on Astra. This was relatively quick, compared to other stories I’ve heard and witnessed.

Once I had code that compiled, I spent another couple nights getting the app to work again. In the end, I spent well over two weeks migrating the app to Swift 3.

This amount of time has very real consequences, but users will never see those efforts as direct  improvements in the product.

I track Astra bugs and features in Trello, and I try to keep each card small enough to complete in one sitting. I keep the list prioritized, but when I know I have a small amount of time, I hunt for a card I know I can do all at once.

The Astra board didn’t change for over two weeks because of the Swift 3 migration. That was two weeks I didn’t get new features or enhancements built, or customer-reported issues fixed. I was simply agreeing to proposed changes by the tools, and then fixing the problems it created along the way.

Developer Happiness

The language an app is written in does very little to serve the needs of a customer. There are weak arguments that some languages result in fewer crashes or bugs, but that doesn’t play out in reality. A product built with proper testing practices always demonstrates a higher level of quality in my experience, regardless of language.

Fundamentally, a developer’s role in app development is to translate interaction and visual designs into functioning software. That means it’s their job to deal with unexpected errors and failures. It’s risk management. These risks also have to be balanced with product timelines – press events, market changes, and competition.

When the ability to ship a product is affected by a tool or programming language, a product owner has to question if it is the right tool. For software, the programming language is a means to speed up development of a product. For iOS, Swift keeps developers from having to write in C or Assembly, much like using UIKit keeps them from having to develop graphical interfaces from scratch. And while Swift is pleasant to use as a developer, it doesn’t provide tangible benefits to a product owner, much less end users.

Apple’s Timeline

Swift is both an academic venture and a product for Apple. It is mastered by two conflicting objectives – to aggressively evolve and simplify difficult development tasks, while marching along a scheduled release cadence. Caught in the middle, developers who choose to use the language end up with code that breaks regularly.

Last week, we also learned that ABI stability has been deferred from Swift 4.

Apple has been aggressive in deprecating versions of Swift, which means Apple dictates Swift migration timelines, and not the product owner. The next release of Xcode is expected to remove support for Swift 2.3. In the case of Astra, this hasn’t been a problem yet, but easily could be.

Emergency Hotfixes

I had to spin a quick 1.0.2 release to Astra a few weeks ago. If you’re following along, you realize that meant fixing a bug in Swift 2.3. In addition to fixing the bug quickly, I had to decide how to get the the fix in both Swift 2.3 and Swift 3 branches. In the end, I implemented the bug fix twice, due to how divergent the project was after other refactoring.

With a fix in place, I had to build the code. Fortunately, I haven’t jumped to Xcode betas, so Swift 2.3 is still supported on my build machine. What if the timeline had played out differently? Sure, Apple still allows submissions from old Xcode versions. That would have meant lost time setting up an older Xcode version. There’s even more time I’m not building features or fixing bugs.

The choice to use Swift imposes a lot of otherwise unnecessary decisions and time costs.

Some Guidance

Here are a few points to consider as you decide how to embark on a new project, whether it be personal, contract, or a product at work.

If the project is intentionally going to be short-lived (less than a year), use Swift. You’re already in the realm of quick and dirty. Constrain yourself to specific versions of libraries, and make sure you clearly communicate this approach is being used to all stakeholders.

For something medium-term (1-3 years), cautiously use Swift, but avoid third-party code. Projects with Swift frameworks need to match Swift version throughout. This means carefully matching versions of frameworks you’re pulling in, especially during migration periods. This is an easy place to burn a lot of time.

If you’re building a framework (especially a commercial one), use Objective-C. Do not impose unnecessary burden on apps integrating your library. Carefully design your interfaces for elegant Swift integration, but implement your code in Objective-C.

For a projects anticipating long-term development, or that have the scrutiny of investors, or are the primary means of putting food on your table, seriously consider Objective-C. It may not be glamorous or flashy, but it is reliable and proven. Seize the opportunity to demonstrate wisdom and expertise to clients by articulating a case for avoiding new shiny things.

The Future

As a developer, I’m very excited to watch Swift continue to evolve. I’m deeply impressed by Apple’s approach, both in making the language open source, and the level of public collaboration they’ve continued to demonstrate.

As a product owner, I have to be cautious when selecting tools. If it can’t be directly translated into value for my customers, then a flashy new tool or language isn’t the best choice. Today, Swift’s breakneck pace and deprecation cadence make it a risky choice for a product, whether it be a side project or a breadwinner.

Remembering Grandpa

John Deere Model A

My father’s John Deere Model A

We said goodbye to my grandfather last week. I had the opportunity to explain to my sons that funerals are a chance to gather with all of the people who knew our loved ones to honor and remember them.

A few days later, I spent some time with my boys enjoying the sights and smells perched atop a John Deere, a place my Grandfather loved. My dad has owned this Model A for years, so we’ve also had plenty of time to develop our own affections for this tractor. It came as no surprise to me when my boys expressed their love for it as well.

The sound of those loud pops as the twin cylinders come under load has always made me grin. It’s like a joyful laugh, as if the tractor enjoys being out in the fresh air, moving across the land, doing its work. My grandfather loved it too, and passed that love to his son, me, and now my sons. My grin will be a little bigger now when I hear those pops.

What Happened To Lexi?

Over the last couple months, the single biggest question I’ve received in my support inbox has been:

“What happened to Lexi?”

As these folks noticed, I removed Lexi from the App Store at the end of August. I’ve been intentionally quite about it both online and offline, as I’ve needed some time away. In short, some issues related to the app’s name and branding put me in a position where the simplest course of action was the remove the app from the store and take down the website.

Lexi was an incredible milestone in my career as a software developer. It was my first big indie app, gaining the attention of TNW, Engadget, The Verge, and many others. It appeared in Mary Meeker’s 2016 Internet Trends report and is used by quite a few people on the Alexa team at Amazon. Its removal didn’t go unnoticed, which is incredibly humbling.

So What’s Next?

Lexi will continue to work as-is for the foreseeable future. It uses the v1 Alexa endpoints, which Amazon may choose to disable at some point in time. That means if you’re using the app, you’ll be able to continue to do so until Amazon disables the old APIs. Unfortunately, I won’t be able to update the app when that happens. Since I’ve received no support requests related to bugs or problems with the app for months, I believe users are content with things as they are in the final release.

However, I’m not content. Back in August, I had a deep backlog of features and enhancements I wanted to build, and that list has only grown in the months since. Alexa is an incredibly exciting platform, and I’m pumped to be building for it again.

Underwhelmed by Apple

During Apple’s “underwhelming” event yesterday, I couldn’t help but think of these words from Ecclesiastes 1:8:

All things are wearisome;
Man is not able to tell it.
The eye is not satisfied with seeing,
Nor is the ear filled with hearing.

Apple’s greatest gift to us may end up being the clear demonstration that the most beautiful gadgets in the world simply cannot satisfy our unquenchable thirst for more.

Our Son, Boaz

I’m usually pretty reserved about what I share online with regard to my family life, but the events of the past few weeks have changed who I am in the depths of my heart. It’s important to me that the people I know have heard this story.

My sons, the oldest two holding the youngest.

Our third son, Boaz, was born on July 16, after my wife’s 38-week appointment abruptly turned into overnight observation and emergency cesarean in the early morning hours. Boaz entered the world screaming, to the delight of his nervous father. However, his cries quickly went silent, and the delivery room nurses rushed him to the NICU, where he resumed breathing with the assistance of a respirator.

A few hours later, the NICU team discovered a large clot above his heart, and he was rushed to Dell Children’s Hospital to undergo surgery. Upon arrival at Dell, however, it became clear the clot was larger and more pervasive than the doctors originally thought. It was unlikely our son could survive the surgery required to address the clot, so the procedure was not attempted. Unfortunately, as time passed, the clot began to cause secondary consequences to Boaz’s body, the most devastating of which, was to his brain. He was placed on medicine to try and manage the clot, but not before significant neurological insults had already occurred. An MRI on the second day of his life showed devastating, global injury to his brain.

Amazingly, the other parts of Boaz’s body started showing signs of improvement in the days following the MRI. By the middle part of the next week, Boaz was breathing on his own, and had began receiving sustenance through an NG tube. Eight days after he was born (all of which were spent in the PICU and IMC at Dell), Boaz was discharged and came home with us. However, his prognosis is heartbreaking. The doctors believe we’ll have weeks or months with Boaz, but it’s unlikely we’ll measure his life in years.

As difficult as that seems, it’s actually great news for us. After facing the reality of losing our son, an extension on his life, even if brief, is a treasure for us. It gives us a chance to spend time together as a family, doing things that seemed improbable during those dark days at the PICU.

One of the most amazing parts of Boaz’s story to date has been the number of people who have been praying for him, and for us. We’ve stopped trying to estimate, after easily arriving at “thousands,” based on the networks of people who have told us about their churches, friends, and families who have also told their churches, friends, and families. We continue to pray for healing, for time, but mostly for God’s will. We pray that we would have strength to endure what comes next, and that we would live each day one at a time, not worrying about tomorrow.

Thank you for your love, support and prayers. So many beautiful things have already come of this, and we can’t wait to share those stories. We look forward to seeing how God continues to use this precious life for His glory.