diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 3053961f49..fe2bdd672b 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -223,7 +223,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { responseByteStream = responseBody.byteStream(); } catch (IOException e) { throw new HttpDataSourceException( - "Unable to connect to " + dataSpec.uri, e, dataSpec, HttpDataSourceException.TYPE_OPEN); + "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN); } int responseCode = response.code(); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java index a29460b84c..e5e6f88d4d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Log.java @@ -21,6 +21,7 @@ import androidx.annotation.Nullable; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.net.UnknownHostException; /** Wrapper around {@link android.util.Log} which allows to set the log level. */ public final class Log { @@ -69,7 +70,8 @@ public final class Log { } /** - * Sets whether stack traces of {@link Throwable}s will be logged to logcat. + * Sets whether stack traces of {@link Throwable}s will be logged to logcat. Stack trace logging + * is enabled by default. * * @param logStackTraces Whether stack traces will be logged. */ @@ -86,11 +88,7 @@ public final class Log { /** @see android.util.Log#d(String, String, Throwable) */ public static void d(String tag, String message, @Nullable Throwable throwable) { - if (!logStackTraces) { - d(tag, appendThrowableMessage(message, throwable)); - } else if (logLevel == LOG_LEVEL_ALL) { - android.util.Log.d(tag, message, throwable); - } + d(tag, appendThrowableString(message, throwable)); } /** @see android.util.Log#i(String, String) */ @@ -102,11 +100,7 @@ public final class Log { /** @see android.util.Log#i(String, String, Throwable) */ public static void i(String tag, String message, @Nullable Throwable throwable) { - if (!logStackTraces) { - i(tag, appendThrowableMessage(message, throwable)); - } else if (logLevel <= LOG_LEVEL_INFO) { - android.util.Log.i(tag, message, throwable); - } + i(tag, appendThrowableString(message, throwable)); } /** @see android.util.Log#w(String, String) */ @@ -118,11 +112,7 @@ public final class Log { /** @see android.util.Log#w(String, String, Throwable) */ public static void w(String tag, String message, @Nullable Throwable throwable) { - if (!logStackTraces) { - w(tag, appendThrowableMessage(message, throwable)); - } else if (logLevel <= LOG_LEVEL_WARNING) { - android.util.Log.w(tag, message, throwable); - } + w(tag, appendThrowableString(message, throwable)); } /** @see android.util.Log#e(String, String) */ @@ -134,18 +124,54 @@ public final class Log { /** @see android.util.Log#e(String, String, Throwable) */ public static void e(String tag, String message, @Nullable Throwable throwable) { - if (!logStackTraces) { - e(tag, appendThrowableMessage(message, throwable)); - } else if (logLevel <= LOG_LEVEL_ERROR) { - android.util.Log.e(tag, message, throwable); + e(tag, appendThrowableString(message, throwable)); + } + + /** + * Returns a string representation of a {@link Throwable} suitable for logging, taking into + * account whether {@link #setLogStackTraces(boolean)} stack trace logging} is enabled. + * + *
Stack trace logging may be unconditionally suppressed for some expected failure modes (e.g., + * {@link Throwable Throwables} that are expected if the device doesn't have network connectivity) + * to avoid log spam. + * + * @param throwable The {@link Throwable}. + * @return The string representation of the {@link Throwable}. + */ + @Nullable + public static String getThrowableString(@Nullable Throwable throwable) { + if (throwable == null) { + return null; + } else if (isCausedByUnknownHostException(throwable)) { + // UnknownHostException implies the device doesn't have network connectivity. + // UnknownHostException.getMessage() may return a string that's more verbose than desired for + // logging an expected failure mode. Conversely, android.util.Log.getStackTraceString has + // special handling to return the empty string, which can result in logging that doesn't + // indicate the failure mode at all. Hence we special case this exception to always return a + // concise but useful message. + return "UnknownHostException (no network)"; + } else if (!logStackTraces) { + return throwable.getMessage(); + } else { + return android.util.Log.getStackTraceString(throwable).trim().replace("\t", " "); } } - private static String appendThrowableMessage(String message, @Nullable Throwable throwable) { - if (throwable == null) { - return message; + private static String appendThrowableString(String message, @Nullable Throwable throwable) { + @Nullable String throwableString = getThrowableString(throwable); + if (!TextUtils.isEmpty(throwableString)) { + message += "\n " + throwableString.replace("\n", "\n ") + '\n'; } - String throwableMessage = throwable.getMessage(); - return TextUtils.isEmpty(throwableMessage) ? message : message + " - " + throwableMessage; + return message; + } + + private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) { + while (throwable != null) { + if (throwable instanceof UnknownHostException) { + return true; + } + throwable = throwable.getCause(); + } + return false; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 56351116df..9f81ca56a2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -477,7 +477,7 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo = playbackInfo.copyWithPlaybackError(e); maybeNotifyPlaybackInfoChanged(); } catch (IOException e) { - Log.e(TAG, "Source error.", e); + Log.e(TAG, "Source error", e); stopInternal( /* forceResetRenderers= */ false, /* resetPositionAndState= */ false, @@ -485,7 +485,7 @@ import java.util.concurrent.atomic.AtomicBoolean; playbackInfo = playbackInfo.copyWithPlaybackError(ExoPlaybackException.createForSource(e)); maybeNotifyPlaybackInfoChanged(); } catch (RuntimeException | OutOfMemoryError e) { - Log.e(TAG, "Internal runtime error.", e); + Log.e(TAG, "Internal runtime error", e); ExoPlaybackException error = e instanceof OutOfMemoryError ? ExoPlaybackException.createForOutOfMemoryError((OutOfMemoryError) e) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index f2cf344b9f..17f8427dd1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -279,8 +279,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou try { connection = makeConnection(dataSpec); } catch (IOException e) { - throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, - dataSpec, HttpDataSourceException.TYPE_OPEN); + throw new HttpDataSourceException( + "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN); } String responseMessage; @@ -289,8 +289,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou responseMessage = connection.getResponseMessage(); } catch (IOException e) { closeConnectionQuietly(); - throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, - dataSpec, HttpDataSourceException.TYPE_OPEN); + throw new HttpDataSourceException( + "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN); } // Check for a valid response code. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 414b715e85..a73c42baa3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.util; import android.os.SystemClock; +import android.text.TextUtils; import android.view.Surface; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -209,7 +210,7 @@ public class EventLogger implements AnalyticsListener { logd(eventTime, "tracks", "[]"); return; } - logd("tracks [" + getEventTimeString(eventTime) + ", "); + logd("tracks [" + getEventTimeString(eventTime)); // Log tracks associated to renderers. int rendererCount = mappedTrackInfo.getRendererCount(); for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) { @@ -293,7 +294,7 @@ public class EventLogger implements AnalyticsListener { @Override public void onMetadata(EventTime eventTime, Metadata metadata) { - logd("metadata [" + getEventTimeString(eventTime) + ", "); + logd("metadata [" + getEventTimeString(eventTime)); printMetadata(metadata, " "); logd("]"); } @@ -485,27 +486,26 @@ public class EventLogger implements AnalyticsListener { } /** - * Logs an error message and exception. + * Logs an error message. * * @param msg The message to log. - * @param tr The exception to log. */ - protected void loge(String msg, @Nullable Throwable tr) { - Log.e(tag, msg, tr); + protected void loge(String msg) { + Log.e(tag, msg); } // Internal methods private void logd(EventTime eventTime, String eventName) { - logd(getEventString(eventTime, eventName)); + logd(getEventString(eventTime, eventName, /* eventDescription= */ null, /* throwable= */ null)); } private void logd(EventTime eventTime, String eventName, String eventDescription) { - logd(getEventString(eventTime, eventName, eventDescription)); + logd(getEventString(eventTime, eventName, eventDescription, /* throwable= */ null)); } private void loge(EventTime eventTime, String eventName, @Nullable Throwable throwable) { - loge(getEventString(eventTime, eventName), throwable); + loge(getEventString(eventTime, eventName, /* eventDescription= */ null, throwable)); } private void loge( @@ -513,7 +513,7 @@ public class EventLogger implements AnalyticsListener { String eventName, String eventDescription, @Nullable Throwable throwable) { - loge(getEventString(eventTime, eventName, eventDescription), throwable); + loge(getEventString(eventTime, eventName, eventDescription, throwable)); } private void printInternalError(EventTime eventTime, String type, Exception e) { @@ -526,12 +526,21 @@ public class EventLogger implements AnalyticsListener { } } - private String getEventString(EventTime eventTime, String eventName) { - return eventName + " [" + getEventTimeString(eventTime) + "]"; - } - - private String getEventString(EventTime eventTime, String eventName, String eventDescription) { - return eventName + " [" + getEventTimeString(eventTime) + ", " + eventDescription + "]"; + private String getEventString( + EventTime eventTime, + String eventName, + @Nullable String eventDescription, + @Nullable Throwable throwable) { + String eventString = eventName + " [" + getEventTimeString(eventTime); + if (eventDescription != null) { + eventString += ", " + eventDescription; + } + @Nullable String throwableString = Log.getThrowableString(throwable); + if (!TextUtils.isEmpty(throwableString)) { + eventString += "\n " + throwableString.replace("\n", "\n ") + '\n'; + } + eventString += "]"; + return eventString; } private String getEventTimeString(EventTime eventTime) {