Skip to content

Commit bd675a8

Browse files
authored
Merge pull request #5671 from square/jwilson.1230.debug_logging
Document debug logging.
2 parents b9e0422 + a815093 commit bd675a8

9 files changed

Lines changed: 307 additions & 139 deletions

File tree

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ Committer's Guides
3636
------------------
3737

3838
* [Concurrency][concurrency]
39+
* [Debug Logging][debug_logging]
3940
* [Releasing][releasing]
4041

4142
[cla]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
4243
[concurrency]: http://square.github.io/okhttp/concurrency/
44+
[debug_logging]: http://square.github.io/okhttp/debug_logging/
4345
[releasing]: http://square.github.io/okhttp/releasing/
4446
[security]: http://square.github.io/okhttp/security/
4547
[works_with_okhttp]: http://square.github.io/okhttp/works_with_okhttp/
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (C) 2019 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package okhttp.android.test
17+
18+
import android.util.Log
19+
import okhttp3.internal.concurrent.TaskRunner
20+
import okhttp3.internal.http2.Http2
21+
import java.util.concurrent.CopyOnWriteArraySet
22+
import java.util.logging.Handler
23+
import java.util.logging.Level
24+
import java.util.logging.LogRecord
25+
import java.util.logging.Logger
26+
import kotlin.reflect.KClass
27+
28+
object OkHttpDebugLogcat {
29+
// Keep references to loggers to prevent their configuration from being GC'd.
30+
private val configuredLoggers = CopyOnWriteArraySet<Logger>()
31+
32+
fun enableHttp2() = enable(Http2::class)
33+
34+
fun enableTaskRunner() = enable(TaskRunner::class)
35+
36+
private fun enable(loggerClass: KClass<*>) {
37+
val logger = Logger.getLogger(loggerClass.java.name)
38+
if (configuredLoggers.add(logger)) {
39+
logger.level = Level.FINE
40+
logger.addHandler(object : Handler() {
41+
override fun publish(record: LogRecord) {
42+
Log.i(loggerClass.simpleName, record.message, record.thrown)
43+
}
44+
45+
override fun flush() {
46+
}
47+
48+
override fun close() {
49+
}
50+
})
51+
}
52+
}
53+
}

docs/debug_logging.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Debug Logging
2+
=============
3+
4+
OkHttp has internal APIs to enable debug logging. It uses the `java.util.logging` API which can be
5+
tricky to configure. As a shortcut, you can paste [OkHttpDebugLogging.kt]. Then enable debug logging
6+
for whichever features you need:
7+
8+
```
9+
OkHttpDebugLogging.enableHttp2()
10+
OkHttpDebugLogging.enableTaskRunner()
11+
```
12+
13+
On Android, use [OkHttpDebugLogcat.kt] instead. It connects OkHttp logs to Logcat in Android Studio.
14+
15+
### HTTP/2 Frame Logging
16+
17+
This logs inbound (`<<`) and outbound (`>>`) frames for HTTP/2 connections.
18+
19+
```
20+
[2020-01-01 00:00:00] >> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
21+
[2020-01-01 00:00:00] >> 0x00000000 6 SETTINGS
22+
[2020-01-01 00:00:00] >> 0x00000000 4 WINDOW_UPDATE
23+
[2020-01-01 00:00:00] >> 0x00000003 47 HEADERS END_STREAM|END_HEADERS
24+
[2020-01-01 00:00:00] << 0x00000000 6 SETTINGS
25+
[2020-01-01 00:00:00] << 0x00000000 0 SETTINGS ACK
26+
[2020-01-01 00:00:00] << 0x00000000 4 WINDOW_UPDATE
27+
[2020-01-01 00:00:00] >> 0x00000000 0 SETTINGS ACK
28+
[2020-01-01 00:00:00] << 0x00000003 322 HEADERS END_HEADERS
29+
[2020-01-01 00:00:00] << 0x00000003 288 DATA
30+
[2020-01-01 00:00:00] << 0x00000003 0 DATA END_STREAM
31+
[2020-01-01 00:00:00] << 0x00000000 8 GOAWAY
32+
[2020-01-01 00:00:05] << 0x00000000 8 GOAWAY
33+
```
34+
35+
### Task Runner Logging
36+
37+
This logs task enqueues, starts, and finishes.
38+
39+
```
40+
[2020-01-01 00:00:00] Q10000 scheduled after 0 µs: OkHttp ConnectionPool
41+
[2020-01-01 00:00:00] Q10000 starting : OkHttp ConnectionPool
42+
[2020-01-01 00:00:00] Q10000 run again after 300 s : OkHttp ConnectionPool
43+
[2020-01-01 00:00:00] Q10000 finished run in 1 ms: OkHttp ConnectionPool
44+
[2020-01-01 00:00:00] Q10001 scheduled after 0 µs: OkHttp squareup.com applyAndAckSettings
45+
[2020-01-01 00:00:00] Q10001 starting : OkHttp squareup.com applyAndAckSettings
46+
[2020-01-01 00:00:00] Q10003 scheduled after 0 µs: OkHttp squareup.com onSettings
47+
[2020-01-01 00:00:00] Q10003 starting : OkHttp squareup.com onSettings
48+
[2020-01-01 00:00:00] Q10001 finished run in 3 ms: OkHttp squareup.com applyAndAckSettings
49+
[2020-01-01 00:00:00] Q10003 finished run in 528 µs: OkHttp squareup.com onSettings
50+
[2020-01-01 00:00:00] Q10000 scheduled after 0 µs: OkHttp ConnectionPool
51+
[2020-01-01 00:00:00] Q10000 starting : OkHttp ConnectionPool
52+
[2020-01-01 00:00:00] Q10000 run again after 300 s : OkHttp ConnectionPool
53+
[2020-01-01 00:00:00] Q10000 finished run in 739 µs: OkHttp ConnectionPool
54+
```
55+
56+
[OkHttpDebugLogging.kt]: https://github.com/square/okhttp/blob/master/okhttp-testing-support/src/main/java/okhttp3/OkHttpDebugLogging.kt
57+
[OkHttpDebugLogcat.kt]: https://github.com/square/okhttp/blob/master/android-test/src/androidTest/java/okhttp/android/test/OkHttpDebugLogcat.kt
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (C) 2019 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package okhttp3
17+
18+
import okhttp3.internal.concurrent.TaskRunner
19+
import okhttp3.internal.http2.Http2
20+
import java.util.concurrent.CopyOnWriteArraySet
21+
import java.util.logging.ConsoleHandler
22+
import java.util.logging.Level
23+
import java.util.logging.LogRecord
24+
import java.util.logging.Logger
25+
import java.util.logging.SimpleFormatter
26+
import kotlin.reflect.KClass
27+
28+
object OkHttpDebugLogging {
29+
// Keep references to loggers to prevent their configuration from being GC'd.
30+
private val configuredLoggers = CopyOnWriteArraySet<Logger>()
31+
32+
fun enableHttp2() = enable(Http2::class)
33+
34+
fun enableTaskRunner() = enable(TaskRunner::class)
35+
36+
private fun enable(loggerClass: KClass<*>) {
37+
val logger = Logger.getLogger(loggerClass.java.name)
38+
if (configuredLoggers.add(logger)) {
39+
logger.addHandler(ConsoleHandler().apply {
40+
level = Level.FINE
41+
formatter = object : SimpleFormatter() {
42+
override fun format(record: LogRecord) =
43+
String.format("[%1\$tF %1\$tT] %2\$s %n", record.millis, record.message)
44+
}
45+
})
46+
logger.level = Level.FINE
47+
}
48+
}
49+
}

okhttp/src/main/java/okhttp3/internal/concurrent/TaskLogger.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,30 +48,34 @@ internal inline fun <T> logElapsed(
4848
if (loggingEnabled) {
4949
val elapsedNs = queue.taskRunner.backend.nanoTime() - startNs
5050
if (completedNormally) {
51-
log(task, queue, "finished in ${formatDuration(elapsedNs)}")
51+
log(task, queue, "finished run in ${formatDuration(elapsedNs)}")
5252
} else {
53-
log(task, queue, "failed in ${formatDuration(elapsedNs)}")
53+
log(task, queue, "failed a run in ${formatDuration(elapsedNs)}")
5454
}
5555
}
5656
}
5757
}
5858

5959
private fun log(task: Task, queue: TaskQueue, message: String) {
60-
TaskRunner.logger.fine("${queue.name} $message: ${task.name}")
60+
TaskRunner.logger.fine("${queue.name} ${String.format("%-22s", message)}: ${task.name}")
6161
}
6262

6363
/**
64-
* Returns a duration in the nearest whole-number units like "999 µs" or "1 ms". This rounds 0.5
64+
* Returns a duration in the nearest whole-number units like "999 µs" or " 1 s ". This rounds 0.5
6565
* units away from 0 and 0.499 towards 0. The smallest unit this returns is "µs"; the largest unit
66-
* it returns is "s". For values in [-499..499] this returns "0 µs".
66+
* it returns is "s". For values in [-499..499] this returns " 0 µs".
67+
*
68+
* The returned string attempts to be column-aligned to 6 characters. For negative and large values
69+
* the returned string may be longer.
6770
*/
6871
fun formatDuration(ns: Long): String {
69-
return when {
70-
ns <= -999_500_000 -> "${(ns - 500_000_000) / 1_000_000_000} s"
72+
val s = when {
73+
ns <= -999_500_000 -> "${(ns - 500_000_000) / 1_000_000_000} s "
7174
ns <= -999_500 -> "${(ns - 500_000) / 1_000_000} ms"
7275
ns <= 0 -> "${(ns - 500) / 1_000} µs"
7376
ns < 999_500 -> "${(ns + 500) / 1_000} µs"
7477
ns < 999_500_000 -> "${(ns + 500_000) / 1_000_000} ms"
75-
else -> "${(ns + 500_000_000) / 1_000_000_000} s"
78+
else -> "${(ns + 500_000_000) / 1_000_000_000} s "
7679
}
80+
return String.format("%6s", s)
7781
}

okhttp/src/main/java/okhttp3/internal/concurrent/TaskQueue.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class TaskQueue internal constructor(
6868
throw RejectedExecutionException()
6969
}
7070

71-
if (scheduleAndDecide(task, delayNanos)) {
71+
if (scheduleAndDecide(task, delayNanos, recurrence = false)) {
7272
taskRunner.kickCoordinator(this)
7373
}
7474
}
@@ -122,7 +122,7 @@ class TaskQueue internal constructor(
122122

123123
// Don't delegate to schedule() because that enforces shutdown rules.
124124
val newTask = AwaitIdleTask()
125-
if (scheduleAndDecide(newTask, 0L)) {
125+
if (scheduleAndDecide(newTask, 0L, recurrence = false)) {
126126
taskRunner.kickCoordinator(this)
127127
}
128128
return newTask.latch
@@ -139,7 +139,7 @@ class TaskQueue internal constructor(
139139
}
140140

141141
/** Adds [task] to run in [delayNanos]. Returns true if the coordinator is impacted. */
142-
internal fun scheduleAndDecide(task: Task, delayNanos: Long): Boolean {
142+
internal fun scheduleAndDecide(task: Task, delayNanos: Long, recurrence: Boolean): Boolean {
143143
task.initQueue(this)
144144

145145
val now = taskRunner.backend.nanoTime()
@@ -155,7 +155,10 @@ class TaskQueue internal constructor(
155155
futureTasks.removeAt(existingIndex) // Already scheduled later: reschedule below!
156156
}
157157
task.nextExecuteNanoTime = executeNanoTime
158-
taskLog(task, this) { "scheduled after ${formatDuration(executeNanoTime - now)}" }
158+
taskLog(task, this) {
159+
if (recurrence) "run again after ${formatDuration(executeNanoTime - now)}"
160+
else "scheduled after ${formatDuration(executeNanoTime - now)}"
161+
}
159162

160163
// Insert in chronological order. Always compare deltas because nanoTime() is permitted to wrap.
161164
var insertAt = futureTasks.indexOfFirst { it.nextExecuteNanoTime - now > delayNanos }

okhttp/src/main/java/okhttp3/internal/concurrent/TaskRunner.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import java.util.logging.Logger
4141
class TaskRunner(
4242
val backend: Backend
4343
) {
44-
private var nextQueueName = 1
44+
private var nextQueueName = 10000
4545
private var coordinatorWaiting = false
4646
private var coordinatorWakeUpAt = 0L
4747

@@ -133,7 +133,7 @@ class TaskRunner(
133133
busyQueues.remove(queue)
134134

135135
if (delayNanos != -1L && !cancelActiveTask && !queue.shutdown) {
136-
queue.scheduleAndDecide(task, delayNanos)
136+
queue.scheduleAndDecide(task, delayNanos, recurrence = true)
137137
}
138138

139139
if (queue.futureTasks.isNotEmpty()) {

okhttp/src/test/java/okhttp3/internal/concurrent/TaskLoggerTest.kt

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,49 +21,49 @@ import org.junit.Test
2121
class TaskLoggerTest {
2222
@Test fun formatTime() {
2323
/* ktlint-disable */
24-
assertThat(formatDuration(-3_499_999_999L)).isEqualTo("-3 s")
25-
assertThat(formatDuration(-3_000_000_000L)).isEqualTo("-3 s")
26-
assertThat(formatDuration(-2_500_000_000L)).isEqualTo("-3 s")
27-
assertThat(formatDuration(-2_499_999_999L)).isEqualTo("-2 s")
28-
assertThat(formatDuration(-1_500_000_000L)).isEqualTo("-2 s")
29-
assertThat(formatDuration(-1_499_999_999L)).isEqualTo("-1 s")
30-
assertThat(formatDuration(-1_000_000_000L)).isEqualTo("-1 s")
31-
assertThat(formatDuration( -999_500_000L)).isEqualTo("-1 s")
24+
assertThat(formatDuration(-3_499_999_999L)).isEqualTo(" -3 s ")
25+
assertThat(formatDuration(-3_000_000_000L)).isEqualTo(" -3 s ")
26+
assertThat(formatDuration(-2_500_000_000L)).isEqualTo(" -3 s ")
27+
assertThat(formatDuration(-2_499_999_999L)).isEqualTo(" -2 s ")
28+
assertThat(formatDuration(-1_500_000_000L)).isEqualTo(" -2 s ")
29+
assertThat(formatDuration(-1_499_999_999L)).isEqualTo(" -1 s ")
30+
assertThat(formatDuration(-1_000_000_000L)).isEqualTo(" -1 s ")
31+
assertThat(formatDuration( -999_500_000L)).isEqualTo(" -1 s ")
3232
assertThat(formatDuration( -999_499_999L)).isEqualTo("-999 ms")
3333
assertThat(formatDuration( -998_500_000L)).isEqualTo("-999 ms")
3434
assertThat(formatDuration( -998_499_999L)).isEqualTo("-998 ms")
35-
assertThat(formatDuration( -1_499_999L)).isEqualTo("-1 ms")
36-
assertThat(formatDuration( -999_500L)).isEqualTo("-1 ms")
35+
assertThat(formatDuration( -1_499_999L)).isEqualTo(" -1 ms")
36+
assertThat(formatDuration( -999_500L)).isEqualTo(" -1 ms")
3737
assertThat(formatDuration( -999_499L)).isEqualTo("-999 µs")
3838
assertThat(formatDuration( -998_500L)).isEqualTo("-999 µs")
39-
assertThat(formatDuration( -1_500L)).isEqualTo("-2 µs")
40-
assertThat(formatDuration( -1_499L)).isEqualTo("-1 µs")
41-
assertThat(formatDuration( -1_000L)).isEqualTo("-1 µs")
42-
assertThat(formatDuration( -999L)).isEqualTo("-1 µs")
43-
assertThat(formatDuration( -500L)).isEqualTo("-1 µs")
44-
assertThat(formatDuration( -499L)).isEqualTo("0 µs")
39+
assertThat(formatDuration( -1_500L)).isEqualTo(" -2 µs")
40+
assertThat(formatDuration( -1_499L)).isEqualTo(" -1 µs")
41+
assertThat(formatDuration( -1_000L)).isEqualTo(" -1 µs")
42+
assertThat(formatDuration( -999L)).isEqualTo(" -1 µs")
43+
assertThat(formatDuration( -500L)).isEqualTo(" -1 µs")
44+
assertThat(formatDuration( -499L)).isEqualTo(" 0 µs")
4545

46-
assertThat(formatDuration( 3_499_999_999L)).isEqualTo("3 s")
47-
assertThat(formatDuration( 3_000_000_000L)).isEqualTo("3 s")
48-
assertThat(formatDuration( 2_500_000_000L)).isEqualTo("3 s")
49-
assertThat(formatDuration( 2_499_999_999L)).isEqualTo("2 s")
50-
assertThat(formatDuration( 1_500_000_000L)).isEqualTo("2 s")
51-
assertThat(formatDuration( 1_499_999_999L)).isEqualTo("1 s")
52-
assertThat(formatDuration( 1_000_000_000L)).isEqualTo("1 s")
53-
assertThat(formatDuration( 999_500_000L)).isEqualTo("1 s")
46+
assertThat(formatDuration( 3_499_999_999L)).isEqualTo(" 3 s ")
47+
assertThat(formatDuration( 3_000_000_000L)).isEqualTo(" 3 s ")
48+
assertThat(formatDuration( 2_500_000_000L)).isEqualTo(" 3 s ")
49+
assertThat(formatDuration( 2_499_999_999L)).isEqualTo(" 2 s ")
50+
assertThat(formatDuration( 1_500_000_000L)).isEqualTo(" 2 s ")
51+
assertThat(formatDuration( 1_499_999_999L)).isEqualTo(" 1 s ")
52+
assertThat(formatDuration( 1_000_000_000L)).isEqualTo(" 1 s ")
53+
assertThat(formatDuration( 999_500_000L)).isEqualTo(" 1 s ")
5454
assertThat(formatDuration( 999_499_999L)).isEqualTo("999 ms")
5555
assertThat(formatDuration( 998_500_000L)).isEqualTo("999 ms")
5656
assertThat(formatDuration( 998_499_999L)).isEqualTo("998 ms")
57-
assertThat(formatDuration( 1_499_999L)).isEqualTo("1 ms")
58-
assertThat(formatDuration( 999_500L)).isEqualTo("1 ms")
57+
assertThat(formatDuration( 1_499_999L)).isEqualTo(" 1 ms")
58+
assertThat(formatDuration( 999_500L)).isEqualTo(" 1 ms")
5959
assertThat(formatDuration( 999_499L)).isEqualTo("999 µs")
6060
assertThat(formatDuration( 998_500L)).isEqualTo("999 µs")
61-
assertThat(formatDuration( 1_500L)).isEqualTo("2 µs")
62-
assertThat(formatDuration( 1_499L)).isEqualTo("1 µs")
63-
assertThat(formatDuration( 1_000L)).isEqualTo("1 µs")
64-
assertThat(formatDuration( 999L)).isEqualTo("1 µs")
65-
assertThat(formatDuration( 500L)).isEqualTo("1 µs")
66-
assertThat(formatDuration( 499L)).isEqualTo("0 µs")
61+
assertThat(formatDuration( 1_500L)).isEqualTo(" 2 µs")
62+
assertThat(formatDuration( 1_499L)).isEqualTo(" 1 µs")
63+
assertThat(formatDuration( 1_000L)).isEqualTo(" 1 µs")
64+
assertThat(formatDuration( 999L)).isEqualTo(" 1 µs")
65+
assertThat(formatDuration( 500L)).isEqualTo(" 1 µs")
66+
assertThat(formatDuration( 499L)).isEqualTo(" 0 µs")
6767
/* ktlint-enable */
6868
}
6969
}

0 commit comments

Comments
 (0)