teaching machines

System.out and System.err on Android

Desktop Java programmers are used to using System.out and System.err to issue debugging print statements. As far as I can tell, System.out and System.err point to /dev/null on Android devices, so mobile app developers are advised to use android.util.Log instead. However, when Eclipse autogenerates code for things like exceptions and sysout and syserr, it assumes System.out and System.err are available. It’d be really nice if we could drop in a replacement for these two PrintStreams that just redirected their output to the Android log.

We can. I worked up a little PrintStream extension that stealthily sends my println and printf content to Log.d or Log.e or Log.*.

First, we need a typesafe mechanism for choosing the priority level of the log message. Android’s Log class defines a bunch of ints for each priority level: information, verbose information, debug statements, warnings, and errors. I didn’t want non-standard priorities being passed along, so I made an enum wrapper:

package org.twodee.android.utils.log;

import android.util.Log;

public enum Priority {
  INFO(Log.INFO),
  WARN(Log.WARN),
  DEBUG(Log.DEBUG),
  ERROR(Log.ERROR),
  VERBOSE(Log.VERBOSE);

  Priority(int androidID) {
    this.androidID = androidID;
  }

  public int androidID;
}

Now on to the meat. System.out can be altered via System.setOut, but the thing we switch to must be a PrintStream. So, I created a subclass of PrintStream that dumps its content into a privately managed byte array. When a newline gets written into that byte array, the contents up until that point get flushed out as a log message.

package org.twodee.android.utils.log;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import android.util.Log;

/**
 * A log stream that supports println, printf, and company. This class stands in
 * for System.out and System.err, but pushes each line of the output to
 * Android's logging facilities.
 */
public class LogPrintStream extends PrintStream {
  /**
   * Creates a new stream with the specified tag and priority.
   * 
   * @param tag
   * User-defined tag for log messages sent to this stream.
   * @param priority
   * Level of log messages sent to this stream.
   */
  public LogPrintStream(String tag, Priority priority) {
    super(new LogByteArrayOutputStream(tag, priority));
  }
}

/**
 * The class LogPrintStream is really just a wrapper. Messages sent to it get
 * dropped into this class's byte array. When a newline is sent, we issue a log
 * message for the array's contents up until the newline.
 */
class LogByteArrayOutputStream extends ByteArrayOutputStream {
  /** Tag for log messages */
  private String tag;

  /** Priority level for log messages */
  private Priority priority;

  /**
   * Creates a new stream with the specified tag and priority.
   * 
   * @param tag
   * User-defined tag for log messages sent to this stream.
   * @param priority
   * Level of log messages sent to this stream.
   */
  public LogByteArrayOutputStream(String tag, Priority priority) {
    this.tag = tag;
    this.priority = priority;
  }

  @Override
  public void write(int b) {
    // If we got a newline, let's issue out the buffer as it stands as a log
    // message. Otherwise, queue up the byte for a future log message.
    if (b == '\n') {
      emit();
    } else {
      super.write(b);
    }
  }

  @Override
  public void write(byte[] b, int offset, int length) {
    int start = offset;

    // Walk through the array, looking for newlines. If we hit one, commit
    // what we've seen so far in the passed array and emit the buffer as a
    // log message.
    for (int i = offset; i < offset + length; ++i) {
      if (b[i] == '\n') {
        super.write(b, start, i - start);
        emit();
        start = i + 1;
      }
    }

    // If the array didn't end with a newline, there'll be some bytes left
    // over. Let's just queue them up and wait for the next newline.
    if (start < offset + length) {
      super.write(b, start, offset + length - start);
    }
  }

  /**
   * Flush the byte array out to a log message and clear the buffer.
   */
  private void emit() {
    Log.println(priority.androidID, tag, toString());
    reset();
  }
}

Now, to use these classes, we just find an appropriate initialization routine and replace System.out and System.err with instances of our LogPrintStream. Your application’s or main Activity’s onCreate seems like a reasonable place to do this:

System.setOut(new LogPrintStream("MyApp", Priority.DEBUG));
System.setErr(new LogPrintStream("MyApp", Priority.ERROR));

System.out and System.err are static variables, so if you change them in one Activity, your other Activitys are also affected. Using multiple tags or multiple priorities within an app requires switching to different PrintStreams, which sounds annoying to manage in a thread-safe way.

I welcome additions and corrections.

Comments

Leave a Reply

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