From 032ad16316815100e252e737aa8f2e14ae0a464e Mon Sep 17 00:00:00 2001 From: Ytai Ben-Tsvi Date: Tue, 25 Feb 2020 16:45:18 -0800 Subject: [PATCH] Pretty-print parcelables when logging Change-Id: I9f0a440db092317a48f7ed10210c2adeae9705e6 Merged-In: I9f0a440db092317a48f7ed10210c2adeae9705e6 Bug: 150325756 --- .../ObjectPrinter.java | 249 ++++++++++++++++++ .../SoundTriggerMiddlewareLogging.java | 58 ++-- 2 files changed, 289 insertions(+), 18 deletions(-) create mode 100644 services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java new file mode 100644 index 0000000000000..7f047f8821226 --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2020 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.server.soundtrigger_middleware; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Map; + +/** + * A collection of pretty-print utilities for data objects. + */ +class ObjectPrinter { + /** Default maximum elements to print in a collection. */ + static public final int kDefaultMaxCollectionLength = 16; + + /** + * Simple version of {@link #print(Object, boolean, int)} that prints an object, without + * recursing into sub-objects. + * + * @param obj The object to print. + * @return A string representing the object. + */ + static String print(@Nullable Object obj) { + return print(obj, false, kDefaultMaxCollectionLength); + } + + /** + * Pretty-prints an object. + * + * @param obj The object to print. + * @param deep Whether to pretty-print sub-objects (if false, just prints them + * with {@link Object#toString()}). + * @param maxCollectionLength Whenever encountering collections, maximum number of elements to + * print. + * @return A string representing the object. + */ + static String print(@Nullable Object obj, boolean deep, int maxCollectionLength) { + StringBuilder builder = new StringBuilder(); + print(builder, obj, deep, maxCollectionLength); + return builder.toString(); + } + + /** + * This version is suitable for use inside a toString() override of an object, e.g.: + *

+     *     class MyObject {
+     *         ...
+     *         @Override
+     *         String toString() {
+     *             return ObjectPrinter.printPublicFields(this, ...);
+     *         }
+     *     }
+     * 
+ * + * @param obj The object to print. + * @param deep Whether to pretty-print sub-objects (if false, just prints them + * with {@link Object#toString()}). + * @param maxCollectionLength Whenever encountering collections, maximum number of elements to + * print. + */ + static String printPublicFields(@Nullable Object obj, boolean deep, int maxCollectionLength) { + StringBuilder builder = new StringBuilder(); + printPublicFields(builder, obj, deep, maxCollectionLength); + return builder.toString(); + } + + /** + * A version of {@link #print(Object, boolean, int)} that uses a {@link StringBuilder}. + * + * @param builder StringBuilder to print into. + * @param obj The object to print. + * @param deep Whether to pretty-print sub-objects (if false, just prints them + * with {@link Object#toString()}). + * @param maxCollectionLength Whenever encountering collections, maximum number of elements to + * print. + */ + static void print(@NonNull StringBuilder builder, @Nullable Object obj, boolean deep, + int maxCollectionLength) { + try { + if (obj == null) { + builder.append("null"); + return; + } + if (obj instanceof Boolean) { + builder.append(obj.toString()); + return; + } + if (obj instanceof Number) { + builder.append(obj.toString()); + return; + } + if (obj instanceof Character) { + builder.append('\''); + builder.append(obj.toString()); + builder.append('\''); + return; + } + if (obj instanceof String) { + builder.append('"'); + builder.append(obj.toString()); + builder.append('"'); + return; + } + + Class cls = obj.getClass(); + + if (Collection.class.isAssignableFrom(cls)) { + Collection collection = (Collection) obj; + builder.append("[ "); + int length = collection.size(); + boolean isLong = false; + int i = 0; + for (Object child : collection) { + if (i > 0) { + builder.append(", "); + } + if (i >= maxCollectionLength) { + isLong = true; + break; + } + print(builder, child, deep, maxCollectionLength); + ++i; + } + if (isLong) { + builder.append("... (+"); + builder.append(length - maxCollectionLength); + builder.append(" entries)"); + } + builder.append(" ]"); + return; + } + + if (Map.class.isAssignableFrom(cls)) { + Map map = (Map) obj; + builder.append("< "); + int length = map.size(); + boolean isLong = false; + int i = 0; + for (Map.Entry child : map.entrySet()) { + if (i > 0) { + builder.append(", "); + } + if (i >= maxCollectionLength) { + isLong = true; + break; + } + print(builder, child.getKey(), deep, maxCollectionLength); + builder.append(": "); + print(builder, child.getValue(), deep, maxCollectionLength); + ++i; + } + if (isLong) { + builder.append("... (+"); + builder.append(length - maxCollectionLength); + builder.append(" entries)"); + } + builder.append(" >"); + return; + } + + if (cls.isArray()) { + builder.append("[ "); + int length = Array.getLength(obj); + boolean isLong = false; + for (int i = 0; i < length; ++i) { + if (i > 0) { + builder.append(", "); + } + if (i >= maxCollectionLength) { + isLong = true; + break; + } + print(builder, Array.get(obj, i), deep, maxCollectionLength); + } + if (isLong) { + builder.append("... (+"); + builder.append(length - maxCollectionLength); + builder.append(" entries)"); + } + builder.append(" ]"); + return; + } + + if (!deep) { + builder.append(obj.toString()); + return; + } + printPublicFields(builder, obj, deep, maxCollectionLength); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * A version of {@link #printPublicFields(Object, boolean, int)} that uses a {@link + * StringBuilder}. + * + * @param obj The object to print. + * @param deep Whether to pretty-print sub-objects (if false, just prints them + * with {@link Object#toString()}). + * @param maxCollectionLength Whenever encountering collections, maximum number of elements to + * print. + */ + static void printPublicFields(@NonNull StringBuilder builder, @Nullable Object obj, + boolean deep, + int maxCollectionLength) { + try { + Class cls = obj.getClass(); + builder.append("{ "); + + boolean first = true; + for (Field fld : cls.getDeclaredFields()) { + int mod = fld.getModifiers(); + if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.STATIC) == 0) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(fld.getName()); + builder.append(": "); + print(builder, fld.get(obj), deep, maxCollectionLength); + } + } + builder.append(" }"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index 7cb7bf4db7ea5..fa78cb0931c27 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -17,7 +17,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; -import android.content.Context; +import android.annotation.Nullable; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -30,6 +30,7 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.os.Binder; import android.os.IBinder; +import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; @@ -38,7 +39,6 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; -import java.util.Objects; /** * An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and @@ -365,43 +365,65 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareSer } } - static private String formatArgs(Object[] args) { - String result = Arrays.toString(args); - // Strip the square brackets. - return result.substring(1, result.length() - 1); + private static String printArgs(@NonNull Object[] args) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < args.length; ++i) { + if (i > 0) { + result.append(", "); + } + printObject(result, args[i]); + } + return result.toString(); } - private void logReturnWithObject(Object object, String methodName, Object retVal, - Object[] args) { + private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) { + if (obj instanceof Parcelable) { + ObjectPrinter.print(builder, obj, true, 16); + } else { + builder.append(obj.toString()); + } + } + + private static String printObject(@Nullable Object obj) { + StringBuilder builder = new StringBuilder(); + printObject(builder, obj); + return builder.toString(); + } + + private void logReturnWithObject(@NonNull Object object, String methodName, + @Nullable Object retVal, + @NonNull Object[] args) { final String message = String.format("%s[this=%s, caller=%d/%d](%s) -> %s", methodName, - Objects.toString(object), + object, Binder.getCallingUid(), Binder.getCallingPid(), - formatArgs(args), - Objects.toString(retVal)); + printArgs(args), + printObject(retVal)); Log.i(TAG, message); appendMessage(message); } - private void logVoidReturnWithObject(Object object, String methodName, Object[] args) { + private void logVoidReturnWithObject(@NonNull Object object, @NonNull String methodName, + @NonNull Object[] args) { final String message = String.format("%s[this=%s, caller=%d/%d](%s)", methodName, - Objects.toString(object), + object, Binder.getCallingUid(), Binder.getCallingPid(), - formatArgs(args)); + printArgs(args)); Log.i(TAG, message); appendMessage(message); } - private void logExceptionWithObject(Object object, String methodName, Exception ex, + private void logExceptionWithObject(@NonNull Object object, @NonNull String methodName, + @NonNull Exception ex, Object[] args) { final String message = String.format("%s[this=%s, caller=%d/%d](%s) threw", methodName, - Objects.toString(object), + object, Binder.getCallingUid(), Binder.getCallingPid(), - formatArgs(args)); + printArgs(args)); Log.e(TAG, message, ex); appendMessage(message + " " + ex.toString()); } - private void appendMessage(String message) { + private void appendMessage(@NonNull String message) { Event event = new Event(message); synchronized (mLastEvents) { if (mLastEvents.size() > NUM_EVENTS_TO_DUMP) {