How to use standard output streams for logging in android apps

android_dev

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 writting 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 familliar 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.

Advertisements

8 Responses to “How to use standard output streams for logging in android apps”

  1. ytam Says:

    Thanks for the code, very useful but not quite perfect. I noticed the output doesn’t show up immediately. It adds to the buffer but I have to call start_logger again to output the buffer to the android log. Any ideas as to whats happening?

    • Nuclear Says:

      First of all, do not call start_logger twice, or you’ll have two logging threads, one of which perpetually blocked.

      By “it adds to the buffer”, do you mean the pipe buffer? And do you mean that regardless of having data waiting in the pipe, the logging thread doesn’t wake up to log them? That probably means that the thread has stopped. Try changing the loop to an unconditional one like so:

      for(;;) {
          if(((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);
          }
      }

      Let me know if that fixes your problem.

  2. ytam Says:

    Figured it out. std out is buffered, had to flush with either sending a newline or with fflush(stdout).

  3. Pascal Marois Says:

    Wonderfull snippet !
    Just the usage of color is missed, and only the for(;;) is working for me.
    NB: To use it, dont forget to flush stdout or linebreak

  4. Guy Nicholas Says:

    Thanks for the code!
    One issue I had with it was that the null terminator is put one character too early. I think it should be:
    buf[rdsz] = 0; /* add null-terminator */

  5. Guy Nicholas Says:

    One other note, I am running with NDK 12.1 and the __android_log_write call does not appear to add a \n to its output.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: