Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0c40fc6 authored by Andreas Gampe's avatar Andreas Gampe Committed by Android (Google) Code Review
Browse files

Merge "Frameworks/base: Change android.util.Log multiline logging"

parents e00da66c 8413db8c
Loading
Loading
Loading
Loading
+112 −8
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ package android.util;

import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.LineBreakBufferedWriter;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.UnknownHostException;

/**
@@ -126,7 +128,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int v(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
    }

    /**
@@ -147,7 +149,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int d(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
    }

    /**
@@ -168,7 +170,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int i(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
    }

    /**
@@ -189,7 +191,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int w(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
    }

    /**
@@ -219,7 +221,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int w(String tag, Throwable tr) {
        return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
    }

    /**
@@ -240,7 +242,7 @@ public final class Log {
     * @param tr An exception to log
     */
    public static int e(String tag, String msg, Throwable tr) {
        return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
        return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
    }

    /**
@@ -292,8 +294,7 @@ public final class Log {
        // Only mark this as ERROR, do not use ASSERT since that should be
        // reserved for cases where the system is guaranteed to abort.
        // The onTerribleFailure call does not always cause a crash.
        int bytes = println_native(logId, ERROR, tag, msg + '\n'
                + getStackTraceString(localStack ? what : tr));
        int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
        sWtfHandler.onTerribleFailure(tag, what, system);
        return bytes;
    }
@@ -365,4 +366,107 @@ public final class Log {

    /** @hide */ public static native int println_native(int bufID,
            int priority, String tag, String msg);

    /**
     * Return the maximum payload the log daemon accepts without truncation.
     * @return LOGGER_ENTRY_MAX_PAYLOAD.
     */
    private static native int logger_entry_max_payload_native();

    /**
     * Helper function for long messages. Uses the LineBreakBufferedWriter to break
     * up long messages and stacktraces along newlines, but tries to write in large
     * chunks. This is to avoid truncation.
     */
    private static int printlns(int bufID, int priority, String tag, String msg,
            Throwable tr) {
        ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
        // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
        // and the length of the tag.
        // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
        //       is too expensive to compute that ahead of time.
        int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
                - 2                                                // Two terminators.
                - (tag != null ? tag.length() : 0)                 // Tag length.
                - 32;                                              // Some slack.
        // At least assume you can print *some* characters (tag is not too large).
        bufferSize = Math.max(bufferSize, 100);

        LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);

        lbbw.println(msg);

        if (tr != null) {
            // This is to reduce the amount of log spew that apps do in the non-error
            // condition of the network being unavailable.
            Throwable t = tr;
            while (t != null) {
                if (t instanceof UnknownHostException) {
                    break;
                }
                t = t.getCause();
            }
            if (t == null) {
                tr.printStackTrace(lbbw);
            }
        }

        lbbw.flush();

        return logWriter.getWritten();
    }

    /**
     * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
     * a JNI call during logging.
     */
    static class NoPreloadHolder {
        public final static int LOGGER_ENTRY_MAX_PAYLOAD =
                logger_entry_max_payload_native();
    }

    /**
     * Helper class to write to the logcat. Different from LogWriter, this writes
     * the whole given buffer and does not break along newlines.
     */
    private static class ImmediateLogWriter extends Writer {

        private int bufID;
        private int priority;
        private String tag;

        private int written = 0;

        /**
         * Create a writer that immediately writes to the log, using the given
         * parameters.
         */
        public ImmediateLogWriter(int bufID, int priority, String tag) {
            this.bufID = bufID;
            this.priority = priority;
            this.tag = tag;
        }

        public int getWritten() {
            return written;
        }

        @Override
        public void write(char[] cbuf, int off, int len) {
            // Note: using String here has a bit of overhead as a Java object is created,
            //       but using the char[] directly is not easier, as it needs to be translated
            //       to a C char[] for logging.
            written += println_native(bufID, priority, tag, new String(cbuf, off, len));
        }

        @Override
        public void flush() {
            // Ignored.
        }

        @Override
        public void close() {
            // Ignored.
        }
    }
}
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.util;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;

/**
 * A writer that breaks up its output into chunks before writing to its out writer,
 * and which is linebreak aware, i.e., chunks will created along line breaks, if
 * possible.
 *
 * Note: this class is not thread-safe.
 */
public class LineBreakBufferedWriter extends PrintWriter {

    /**
     * A buffer to collect data until the buffer size is reached.
     *
     * Note: we manage a char[] ourselves to avoid an allocation when printing to the
     *       out writer. Otherwise a StringBuilder would have been simpler to use.
     */
    private char[] buffer;

    /**
     * The index of the first free element in the buffer.
     */
    private int bufferIndex;

    /**
     * The chunk size (=maximum buffer size) to use for this writer.
     */
    private final int bufferSize;


    /**
     * Index of the last newline character discovered in the buffer. The writer will try
     * to split there.
     */
    private int lastNewline = -1;

    /**
     * The line separator for println().
     */
    private final String lineSeparator;

    /**
     * Create a new linebreak-aware buffered writer with the given output and buffer
     * size. The initial capacity will be a default value.
     * @param out The writer to write to.
     * @param bufferSize The maximum buffer size.
     */
    public LineBreakBufferedWriter(Writer out, int bufferSize) {
        this(out, bufferSize, 16);  // 16 is the default size of a StringBuilder buffer.
    }

    /**
     * Create a new linebreak-aware buffered writer with the given output, buffer
     * size and initial capacity.
     * @param out The writer to write to.
     * @param bufferSize The maximum buffer size.
     * @param initialCapacity The initial capacity of the internal buffer.
     */
    public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) {
        super(out);
        this.buffer = new char[Math.min(initialCapacity, bufferSize)];
        this.bufferIndex = 0;
        this.bufferSize = bufferSize;
        this.lineSeparator = System.getProperty("line.separator");
    }

    /**
     * Flush the current buffer. This will ignore line breaks.
     */
    @Override
    public void flush() {
        writeBuffer(bufferIndex);
        bufferIndex = 0;
        super.flush();
    }

    @Override
    public void write(int c) {
        if (bufferIndex < bufferSize) {
            buffer[bufferIndex] = (char)c;
            bufferIndex++;
            if ((char)c == '\n') {
                lastNewline = bufferIndex;
            }
        } else {
            // This should be an uncommon case, we mostly expect char[] and String. So
            // let the chunking be handled by the char[] case.
            write(new char[] { (char)c }, 0 ,1);
        }
    }

    @Override
    public void println() {
        write(lineSeparator);
    }

    @Override
    public void write(char[] buf, int off, int len) {
        while (bufferIndex + len > bufferSize) {
            // Find the next newline in the buffer, see if that's below the limit.
            // Repeat.
            int nextNewLine = -1;
            int maxLength = bufferSize - bufferIndex;
            for (int i = 0; i < maxLength; i++) {
                if (buf[off + i] == '\n') {
                    if (bufferIndex + i < bufferSize) {
                        nextNewLine = i;
                    } else {
                        break;
                    }
                }
            }

            if (nextNewLine != -1) {
                // We can add some more data.
                appendToBuffer(buf, off, nextNewLine);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                lastNewline = -1;
                off += nextNewLine + 1;
                len -= nextNewLine + 1;
            } else if (lastNewline != -1) {
                // Use the last newline.
                writeBuffer(lastNewline);
                removeFromBuffer(lastNewline + 1);
                lastNewline = -1;
            } else {
                // OK, there was no newline, break at a full buffer.
                int rest = bufferSize - bufferIndex;
                appendToBuffer(buf, off, rest);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                off += rest;
                len -= rest;
            }
        }

        // Add to the buffer, this will fit.
        if (len > 0) {
            // Add the chars, find the last newline.
            appendToBuffer(buf, off, len);
            for (int i = len - 1; i >= 0; i--) {
                if (buf[off + i] == '\n') {
                    lastNewline = bufferIndex - len + i;
                    break;
                }
            }
        }
    }

    @Override
    public void write(String s, int off, int len) {
        while (bufferIndex + len > bufferSize) {
            // Find the next newline in the buffer, see if that's below the limit.
            // Repeat.
            int nextNewLine = -1;
            int maxLength = bufferSize - bufferIndex;
            for (int i = 0; i < maxLength; i++) {
                if (s.charAt(off + i) == '\n') {
                    if (bufferIndex + i < bufferSize) {
                        nextNewLine = i;
                    } else {
                        break;
                    }
                }
            }

            if (nextNewLine != -1) {
                // We can add some more data.
                appendToBuffer(s, off, nextNewLine);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                lastNewline = -1;
                off += nextNewLine + 1;
                len -= nextNewLine + 1;
            } else if (lastNewline != -1) {
                // Use the last newline.
                writeBuffer(lastNewline);
                removeFromBuffer(lastNewline + 1);
                lastNewline = -1;
            } else {
                // OK, there was no newline, break at a full buffer.
                int rest = bufferSize - bufferIndex;
                appendToBuffer(s, off, rest);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                off += rest;
                len -= rest;
            }
        }

        // Add to the buffer, this will fit.
        if (len > 0) {
            // Add the chars, find the last newline.
            appendToBuffer(s, off, len);
            for (int i = len - 1; i >= 0; i--) {
                if (s.charAt(off + i) == '\n') {
                    lastNewline = bufferIndex - len + i;
                    break;
                }
            }
        }
    }

    /**
     * Append the characters to the buffer. This will potentially resize the buffer,
     * and move the index along.
     * @param buf The char[] containing the data.
     * @param off The start index to copy from.
     * @param len The number of characters to copy.
     */
    private void appendToBuffer(char[] buf, int off, int len) {
        if (bufferIndex + len > buffer.length) {
            ensureCapacity(bufferIndex + len);
        }
        System.arraycopy(buf, off, buffer, bufferIndex, len);
        bufferIndex += len;
    }

    /**
     * Append the characters from the given string to the buffer. This will potentially
     * resize the buffer, and move the index along.
     * @param s The string supplying the characters.
     * @param off The start index to copy from.
     * @param len The number of characters to copy.
     */
    private void appendToBuffer(String s, int off, int len) {
        if (bufferIndex + len > buffer.length) {
            ensureCapacity(bufferIndex + len);
        }
        s.getChars(off, off + len, buffer, bufferIndex);
        bufferIndex += len;
    }

    /**
     * Resize the buffer. We use the usual double-the-size plus constant scheme for
     * amortized O(1) insert. Note: we expect small buffers, so this won't check for
     * overflow.
     * @param capacity The size to be ensured.
     */
    private void ensureCapacity(int capacity) {
        int newSize = buffer.length * 2 + 2;
        if (newSize < capacity) {
            newSize = capacity;
        }
        buffer = Arrays.copyOf(buffer, newSize);
    }

    /**
     * Remove the characters up to (and excluding) index i from the buffer. This will
     * not resize the buffer, but will update bufferIndex.
     * @param i The number of characters to remove from the front.
     */
    private void removeFromBuffer(int i) {
        int rest = bufferIndex - i;
        if (rest > 0) {
            System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest);
            bufferIndex = rest;
        } else {
            bufferIndex = 0;
        }
    }

    /**
     * Helper method, write the given part of the buffer, [start,length), to the output.
     * @param length The number of characters to flush.
     */
    private void writeBuffer(int length) {
        if (length > 0) {
            super.write(buffer, 0, length);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -184,6 +184,8 @@ LOCAL_C_INCLUDES += \
    $(call include-path-for, libhardware_legacy)/hardware_legacy \
    $(TOP)/frameworks/av/include \
    $(TOP)/frameworks/base/media/jni \
    $(TOP)/system/core/base/include \
    $(TOP)/system/core/include \
    $(TOP)/system/media/camera/include \
    $(TOP)/system/netd/include \
    external/pdfium/core/include/fpdfapi \
+13 −0
Original line number Diff line number Diff line
@@ -18,8 +18,10 @@
#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println"

#include <android-base/macros.h>
#include <assert.h>
#include <cutils/properties.h>
#include <log/logger.h>               // For LOGGER_ENTRY_MAX_PAYLOAD.
#include <utils/Log.h>
#include <utils/String8.h>

@@ -108,6 +110,16 @@ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
    return res;
}

/*
 * In class android.util.Log:
 *  private static native int logger_entry_max_payload_native()
 */
static jint android_util_Log_logger_entry_max_payload_native(JNIEnv* env ATTRIBUTE_UNUSED,
                                                             jobject clazz ATTRIBUTE_UNUSED)
{
    return static_cast<jint>(LOGGER_ENTRY_MAX_PAYLOAD);
}

/*
 * JNI registration.
 */
@@ -115,6 +127,7 @@ static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

int register_android_util_Log(JNIEnv* env)
+223 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.util;

import junit.framework.TestCase;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Tests for {@link IndentingPrintWriter}.
 */
public class LineBreakBufferedWriterTest extends TestCase {

    private ByteArrayOutputStream mStream;
    private RecordingWriter mWriter;

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        mWriter = new RecordingWriter();
    }

    public void testLessThanBufferSize() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 1000);

        lw.println("Hello");
        lw.println("World");
        lw.println("Test");
        lw.flush();

        assertOutput("Hello\nWorld\nTest\n");
    }

    public void testMoreThanBufferSizeNoLineBreaks() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal = "aaaaaaaaaaaaaaa";
        lw.print(literal);
        lw.print(literal);
        lw.flush();

        // Have to manually inspect output.
        List<String> result = mWriter.getStrings();
        // Expect two strings.
        assertEquals(2, result.size());
        // Expect the strings to sum up to the original input.
        assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length());
        // Strings should only be a.
        for (String s : result) {
            for (int i = 0; i < s.length(); i++) {
                assertEquals('a', s.charAt(i));
            }
        }
    }

    public void testMoreThanBufferSizeNoLineBreaksSingleString() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal = "aaaaaaaaaaaaaaa";
        lw.print(literal + literal);
        lw.flush();

        // Have to manually inspect output.
        List<String> result = mWriter.getStrings();
        // Expect two strings.
        assertEquals(2, result.size());
        // Expect the strings to sum up to the original input.
        assertEquals(2 * literal.length(), result.get(0).length() + result.get(1).length());
        // Strings should only be a.
        for (String s : result) {
            for (int i = 0; i < s.length(); i++) {
                assertEquals('a', s.charAt(i));
            }
        }
    }

    public void testMoreThanBufferSizeLineBreakBefore() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal1 = "aaaaaaaaaa\nbbbb";
        String literal2 = "cccccccccc";
        lw.print(literal1);
        lw.print(literal2);
        lw.flush();

        assertOutput("aaaaaaaaaa", "bbbbcccccccccc");
    }

    public void testMoreThanBufferSizeLineBreakBeforeSingleString() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal1 = "aaaaaaaaaa\nbbbb";
        String literal2 = "cccccccccc";
        lw.print(literal1 + literal2);
        lw.flush();

        assertOutput("aaaaaaaaaa", "bbbbcccccccccc");
    }

    public void testMoreThanBufferSizeLineBreakNew() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal1 = "aaaaaaaaaabbbbb";
        String literal2 = "c\nd\nddddddddd";
        lw.print(literal1);
        lw.print(literal2);
        lw.flush();

        assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd");
    }

    public void testMoreThanBufferSizeLineBreakBeforeAndNew() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal1 = "aaaaaaaaaa\nbbbbb";
        String literal2 = "c\nd\nddddddddd";
        lw.print(literal1);
        lw.print(literal2);
        lw.flush();

        assertOutput("aaaaaaaaaa\nbbbbbc\nd", "ddddddddd");
    }

    public void testMoreThanBufferSizeInt() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15);

        int literal1 = 1234567890;
        int literal2 = 987654321;
        lw.print(literal1);
        lw.print(literal2);
        lw.flush();

        assertOutput("123456789098765", "4321");
    }

    public void testMoreThanBufferSizeChar() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 15);

        for(int i = 0; i < 10; i++) {
            lw.print('$');
        }
        for(int i = 0; i < 10; i++) {
            lw.print('%');
        }
        lw.flush();

        assertOutput("$$$$$$$$$$%%%%%", "%%%%%");
    }

    public void testMoreThanBufferSizeLineBreakNewChars() {
        final LineBreakBufferedWriter lw = new LineBreakBufferedWriter(mWriter, 20);

        String literal1 = "aaaaaaaaaabbbbb";
        String literal2 = "c\nd\nddddddddd";
        lw.print(literal1.toCharArray());
        lw.print(literal2.toCharArray());
        lw.flush();

        assertOutput("aaaaaaaaaabbbbbc\nd", "ddddddddd");
    }

    private void assertOutput(String... golden) {
        List<String> goldList = createTestGolden(golden);
        assertEquals(goldList, mWriter.getStrings());
    }

    private static List<String> createTestGolden(String... args) {
        List<String> ret = new ArrayList<String>();
        for (String s : args) {
            ret.add(s);
        }
        return ret;
    }

    // A writer recording calls to write.
    private final static class RecordingWriter extends Writer {

        private List<String> strings = new ArrayList<String>();

        public RecordingWriter() {
        }

        public List<String> getStrings() {
            return strings;
        }

        @Override
        public void write(char[] cbuf, int off, int len) {
            strings.add(new String(cbuf, off, len));
        }

        @Override
        public void flush() {
            // Ignore.
        }

        @Override
        public void close() {
            // Ignore.
        }
    }
}