简体   繁体   中英

printf, logcat and \n

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?

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).

对于后代, 就是我所做的:将记录的字符串存储在缓冲区中,并在缓冲区中有换行符时在换行符之前打印该部分。

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.

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

String message = "Hello";
// Do Something
message += " World!";
Log.v("Example", message);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM