How to use standard output streams for logging in android apps

John Tsiombikas nuclear@mutantstargoat.com

3 November 2014

android development

Android applications are strange beasts. Even though under the hood it's basically a UNIX system using Linux as its kernel, there are other layers between (native) android apps and the bare system, that some things are bound to fall through the cracks.

When developing android apps, one generally doesn't login to the actual device to compile and execute the program; instead a cross-compiler is provided by google, along with a series of tools to package, install, and execute the app. The degrees of separation from the actual controlling terminal of the application, make it harder to just print debugging messages to the stdout and stderr streams and watch the output like one could do while hacking a regular program. For this reason, the android NDK provides a set of logging functions, which append the messages to a global log buffer. The log buffer can be viewed and followed remotely, from the development machine, over USB, by using the adb logcat tool.

This is all well and good, but if you're porting a program which prints a lot of messages to stdout/stderr, or if you just like the convenience of using the standard printf/cout/etc functions, instead of funny looking stuff like __android_log_print(), you might wonder if there is a way to just use the standard I/O streams instead, right? Well so did I, and guess what... this is still UNIX so the answer is of course you can!

The simplest solution to this problem would be if we could just redirect stdout and stderr to wherever these __android_log_whatever functions are writing their messages. This will obviously be either a socket or a pipe of some sort, but alas it's not exposed in any way through the NDK APIs. We could figure out where it goes if we where to dig through JNI calls and Java classes in the intermediate layers, but a change in a future version of android or its SDKs, might break our code if we use such undocumented ways to log messages. I'd rather not go through all this trouble, and introduce incompatibilities in the process.

An easier, albeit slightly more convoluted way to attack this, which I ended up using, is to create a pipe, redirect stdout and stderr to the write end of the pipe, and start a logging thread which just blocks reading the other end of that pipe, and calls the NDK log functions to log whatever is coming through.

Here's a short snippet from my android logger demonstrating how to set that up:

static int pfd[2];
static pthread_t thr;
static const char *tag = "myapp";

int start_logger(const char *app_name)
{
    tag = app_name;

    /* make stdout line-buffered and stderr unbuffered */
    setvbuf(stdout, 0, _IOLBF, 0);
    setvbuf(stderr, 0, _IONBF, 0);

    /* create the pipe and redirect stdout and stderr */
    pipe(pfd);
    dup2(pfd[1], 1);
    dup2(pfd[1], 2);

    /* spawn the logging thread */
    if(pthread_create(&thr, 0, thread_func, 0) == -1)
        return -1;
    pthread_detach(thr);
    return 0;
}

static void *thread_func(void*)
{
    ssize_t rdsz;
    char buf[128];
    while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
        if(buf[rdsz - 1] == '\n') --rdsz;
        buf[rdsz] = 0;  /* add null-terminator */
        __android_log_write(ANDROID_LOG_DEBUG, tag, buf);
    }
    return 0;
}

For those not familiar with UNIX system programming, redirection of file descriptor is done by closing the fd in question and duplicating another existing file descriptor in its place (with dup or dup2). Also you'll notice in the logging thread I'm removing the trailing newline if it's there. That is because __android_log_write will append its own newline to any messages we log through it.

Anyway, by calling start_logger somewhere at the start of android_main, anything printed from that point onwards to stdout/stderr will end up in the android log. Feel free to use it in your programs if you find it useful.


This was initially posted in my old wordpress blog. Visit the original version to see any comments.


Discuss this post

Back to my blog