3

I have C code that uses prints with something clever like

printf("hello ");
// do something
printf(" world!\n");

which outputs

hello world!

I want to reuse that code with Android and iOS, but Log.d() and NSLog() effectively add a newline at the end of every string I pass them, so that the output of this code:

NSLog(@"hello ");
// do something
NSLog(@"world!\n");

comes out (more or less) as:

hello

world!

I'm willing to replace printf with some macro to make Log.d and NSLog emulate printf's handling of '\n'; any suggestions?

4

4 回答 4

1

One solution that might work is to define a global log function that doesn't flush its buffer until it finds a newline.

Here's a (very) simple version in java for android:

import java.lang.StringBuilder;

class CustomLogger {
  private static final StringBuilder buffer = new StringBuilder();

  public static void log(String message) {
    buffer.append(message);

    if(message.indexOf('\n') != -1) {
      Log.d('SomeTag', buffer);
      buffer.setLength(0);
    }
  }
}

...
CustomLogger.log("Hello, ");
// Stuff
CustomLogger.log("world!\n"); // Now the message gets logged

It's completely untested but you get the idea.
This particular script has some performance issues. It might be better to check if just the last character is a newline for example.


I just realized that you wanted this in C. It shouldn't be too hard to port though a standard lib wouldn't hurt (to get stuff like a string buffer).

于 2012-05-25T21:30:07.807 回答
0

For progeny, this is what I did: store logged strings in a buffer, and print the part before the newline whenever there is a newline in the buffer.

于 2012-05-28T13:53:59.090 回答
0

Yes, the NDK logcat is dumb about it. There are ways to redirect stderr/stdout to logcat, but there are drawbacks (either need to "adb shell setprop" which is only for rooted devices, or a dup() like technique but creating a thread just for that purpose is not a good idea on embedded devices IMHO though you can look further below for this technique).

So I did my own function/macros for that purpose. Here are snippets. In a debug.c, do this:

#include "debug.h"
#include <stdio.h>
#include <stdarg.h>

static const char LOG_TAG[] = "jni";

void android_log(android_LogPriority type, const char *fmt, ...)
{
    static char buf[1024];
    static char *bp = buf;

    va_list vl;
    va_start(vl, fmt);
    int available = sizeof(buf) - (bp - buf);
    int nout = vsnprintf(bp, available, fmt, vl);
    if (nout >= available) {
        __android_log_write(type, LOG_TAG, buf);
        __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "previous log line has been truncated!");
        bp = buf;
    } else {
        char *lastCR = strrchr(bp, '\n');
        bp += nout;
        if (lastCR) {
            *lastCR = '\0';
            __android_log_write(type, LOG_TAG, buf);

            char *rest = lastCR+1;
            int len = bp - rest; // strlen(rest)
            memmove(buf, rest, len+1); // no strcpy (may overlap)
            bp = buf + len;
        }
    }

    va_end(vl);
}

Then in debug.h do this:

#include <android/log.h>

void android_log(android_LogPriority type, const char *fmt, ...);
#define LOGI(...) android_log(ANDROID_LOG_INFO, __VA_ARGS__)
#define LOGW(...) android_log(ANDROID_LOG_WARN, __VA_ARGS__)
...

Now you just need to include debug.hpp and call LOGI() with a printf-like semantic buffered until a '\n' is encountered (or buffer is full).

This is not perfect though, as if the string generated from a call is longer than the buffer, it will be truncated and output. But frankly, 1024 chars should be enough in most cases (even less than this). Anyway, if this happens it will output a warning so you know about it.

Also note the vsnprintf() is not standard C (but it works in Android NDK). We could use vsprintf() instead (which is standard), but it is unsafe on its own.

======================================================================

Now for the dup() technique, you can look here (James Moore answer).

Then you can get rid of the function above and define your macro as:

#define LOG(...) fprintf(stderr, ...)

and you're done.

Advantages:

  1. C/C++ libraries often use stderr for their logs. Using dup is the only way to have their output in logcat without modifying their code (some big ones use hundreds of direct calls to fprintf(stderr, ...))
  2. stderr is standard C used since decades. All standard C library functions related to streams can be used with it. Same for C++, you can even use cerr with << operator. It works since under the hood, it still stderr.
  3. Very long lines not truncated (instead, their are split). A good reason to use a shorter buffer (256 in the example).

Disadvantages:

  1. A thread on its own (though it's an IO only thread, impact is close to nothing)
  2. No log priority value (INFO, WARN, ERROR, etc...) can be choosen during the call. It uses a default one (INFO), so DMMS will always show stderr lines in the same color.
于 2012-08-05T01:31:41.397 回答
-1

You could always just build the string one segment at a time:

String message = "Hello";
// Do Something
message += " World!";
Log.v("Example", message);
于 2012-05-25T20:04:04.057 回答