SureshJoshi.com ▼

Cross-Platform Mobile Logging Macro


2014-12-03

I do a lot of cross-platform C++ development, and an important part of that is finding a good cross-platform C++ logger. For regular Windows, Linux, Mac development - finding a logger isn’t an issue. Doing the same for mobile logging is an entirely different story.

Traditional C++ logging tools

There are a ton of C++ logging tools for traditional development. The Pantheios website does a good job of mentioning a few of them with respect to their performance. Otherwise, the ones I’ve had experience with are: Google Log (Glog), Boost.Log, log4cpp, log4cxx, EasyLogging, and one or two more I’ve written myself just to see whether it was even worth using a 3rd party tool. For anything other than the simplest logging, I recommend using one.

EasyLogging gets points just for having no dependency on external libraries, because those are always just a bit more of a pain to setup in a cross-platform environment. Boost is low on my list for being so damned heavy with the wonderful Boost library associated with it (however, if you’re already using Boost, it’s a better choice). In general, picking a logging framework is always a pro/con situation, and while I want to say it’s a case-by-case basis; I’d rather just say pick one that you’re comfortable setting up for any/all of your projects, has the features you need, you can’t see a noticeable impact from using it, and you’re happy with the syntax.

You can run all the performance tests you want, but the vast majority of apps won’t/shouldn’t see an impact between most of these loggers - also, picking a logger that you know will be supported for a long time is good if it’s not just hobby code.

Mobile C++ logging tools

Mobile is when things get crazy. C++ isn’t the most used mobile programming language (Java/Objective-C lock that down), but it has its applications. For me, this tends to be when there is some performance sensitive code, or a large codebase that I don’t want to be responsible for writing/maintaining in multiple languages.

Note: I know everyone has their own opinion on whether C++ should be used in mobile environments (Android docs basically say to stay away), but anything I write comes from my personal experience and biases - for me, I hate fixing bugs multiple times. Also, one thing I like about C++ in mobile environments is that it’s one of the few times when I really use Test-Driven Development (TDD). I always write unit tests, but there’s something about mobile that makes me TDD.

Anyways, when it comes to logging C++ code that is sitting inside of an iOS or Android app, things get a little trickier. You can still use one of the frameworks I mentioned above, but then you run into this odd spot of having a C++ log, and an Android/iOS log. That’s a little messy for me.

I originally thought about doing something along the lines of having an interface that would return a logging string, which would be written straight into the platform log. This could involve a pre-allocated block of memory that would be progressively filled, or a dynamically re-allocated block. I didn’t really like the idea of having a large memory block of logging strings in the first place, and even worse, losing those in case of a crash!

My logging macro 

I had seen that the Android NDK supports logging to the main Android log from NDK code, and I was sure that since Objective-C is based (in theory) on C, there could be something working there. However, above all, I didn’t want to have to write or maintain multiple loggers or litter all my C++ code with ‘if’ checks.

So, what I decided to do was to go for a good ol’ fashion macro hack.

Including this header in your cross-platform, C++ code and using the principle LOG_[LEVEL] commands will allow you to use logging with a traditional C++ app (e.g. MFC), in the Android NDK, or in Objective-C. I also included a debug/release version, where I eliminate all but the error log (so that debug logs don’t accidentally get exposed to prying eyes). For the non-mobile logging, I just pipe to std::clog or std::cerr, but those could be replaced with one of the above logging frameworks.

For good measure, I also added Crashlytics logging to the release version of the iOS code.

#ifndef __X_LOGGING_H__
#define __X_LOGGING_H__

#define LOG_VERBOSE(...)
#define LOG_DEBUG(...)
#define LOG_WARN(...)
#define LOG_ERROR(...)

#ifdef __ANDROID__

#include <android/log.h>

#define LOG_TAG "TAG"
#ifndef NDEBUG // Only expose other log values in debug
    #define LOG_VERBOSE(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
    #define LOG_DEBUG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #define LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#endif
#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

#elif __APPLE__

#include <CoreFoundation/CoreFoundation.h>

extern "C" {
    void NSLog(CFStringRef format, ...);
    void CLSLog(CFStringRef format, ...);
}

#ifdef DEBUG // Only expose other log values in debug
    #define LOG_VERBOSE(format, ...) NSLog(CFSTR(format), ##__VA_ARGS__)
    #define LOG_DEBUG(format, ...) NSLog(CFSTR(format), ##__VA_ARGS__)
    #define LOG_WARN(format, ...) NSLog(CFSTR(format), ##__VA_ARGS__)
    #define LOG_ERROR(format, ...) NSLog(CFSTR(format), ##__VA_ARGS__)
#else
    #define LOG_ERROR(format, ...) CLSLog(CFSTR(format), ##__VA_ARGS__)
#endif

#else // Non-mobile platform

#include <iostream>

#define LOG_VERBOSE(...) std::clog << __VA_ARGS__ << std::endl;
#define LOG_DEBUG(...) std::clog << __VA_ARGS__ << std::endl;
#define LOG_WARN(...) std::clog << __VA_ARGS__ << std::endl;
#define LOG_ERROR(...) std::cerr << __VA_ARGS__ << std::endl;

#endif

#endif // __X_LOGGING_H__

Feature Photo credit: pestoverde / Foter / CC BY