Skip to content

Commit aee2c63

Browse files
Improve debug logging and add tests that it is not evaluated when disabled (#26)
### Motivation The transport code has some debug logging, which is compile-time omitted in release builds, and only enabled in tests. A number of tests produce an enormous amount of output (e.g. the streaming tests that pull 10GB through 10MB buffers), which are unwieldy in CI and locally. Additionally, we found that the logging produced by these tests had interleaved output, both because of the mixed use of `print` and `debug` in tests and the lack of locking between multiple threads Finally, even when running in debug mode with debug logging _disabled_, the function arguments to the `debug` function were still evaluated unnecessarily. ### Modifications - Update the `debug` function to log to standard error (cf. standard out). - Pass an autoclosure as argument to `debug` so that it is only evaluated when logging is enabled. - Add a lock around the use of `FileHandle.standardError`. - Replace all stray uses of `print` in tests with `debug`. ### Result - No debug logging should appear in tests by default (you can enable them locally when reproducing failures). - When enabled, log lines should not be all jumbled up. - When disabled, no string interpolation is performed. ### Test Plan - Added tests that check that the log message is not interpolated when disabled.
1 parent b1d5cfa commit aee2c63

File tree

7 files changed

+86
-51
lines changed

7 files changed

+86
-51
lines changed

Sources/OpenAPIURLSession/URLSessionTransport.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Foundation
2020
@preconcurrency import struct Foundation.URLComponents
2121
@preconcurrency import struct Foundation.Data
2222
@preconcurrency import protocol Foundation.LocalizedError
23+
@preconcurrency import class Foundation.FileHandle
2324
#if canImport(FoundationNetworking)
2425
@preconcurrency import struct FoundationNetworking.URLRequest
2526
@preconcurrency import class FoundationNetworking.URLSession
@@ -230,10 +231,17 @@ var debugLoggingEnabled: Bool {
230231
get { _debugLoggingEnabled.withLockedValue { $0 } }
231232
set { _debugLoggingEnabled.withLockedValue { $0 = newValue } }
232233
}
233-
func debug(_ items: Any..., separator: String = " ", terminator: String = "\n") {
234+
private let _standardErrorLock = LockStorage.create(value: FileHandle.standardError)
235+
func debug(_ message: @autoclosure () -> String, function: String = #function, file: String = #file, line: UInt = #line)
236+
{
234237
assert(
235238
{
236-
if debugLoggingEnabled { print(items, separator: separator, terminator: terminator) }
239+
if debugLoggingEnabled {
240+
_standardErrorLock.withLockedValue {
241+
let logLine = "[\(function) \(file.split(separator: "/").last!):\(line)] \(message())\n"
242+
$0.write(Data((logLine).utf8))
243+
}
244+
}
237245
return true
238246
}()
239247
)

Tests/OpenAPIURLSessionTests/AsyncBackpressuredStreamTests/AsyncBackpressuredStreamTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ final class AsyncBackpressuredStreamTests: XCTestCase {
5151
group.addTask {
5252
while true {
5353
backPressureEventContinuation.yield(())
54-
print("Yielding")
54+
debug("Yielding")
5555
try await source.asyncWrite(contentsOf: [1])
5656
}
5757
}
@@ -64,12 +64,12 @@ final class AsyncBackpressuredStreamTests: XCTestCase {
6464
await backPressureEventIterator.next()
6565
await backPressureEventIterator.next()
6666

67-
print("Waited 4 times")
67+
debug("Waited 4 times")
6868

6969
_ = try await iterator.next()
7070
_ = try await iterator.next()
7171
_ = try await iterator.next()
72-
print("Consumed three")
72+
debug("Consumed three")
7373

7474
await backPressureEventIterator.next()
7575
await backPressureEventIterator.next()
@@ -91,12 +91,12 @@ final class AsyncBackpressuredStreamTests: XCTestCase {
9191
group.addTask {
9292
@Sendable func yield() {
9393
backPressureEventContinuation.yield(())
94-
print("Yielding")
94+
debug("Yielding")
9595
source.write(contentsOf: [1]) { result in
9696
switch result {
9797
case .success: yield()
9898

99-
case .failure: print("Stopping to yield")
99+
case .failure: debug("Stopping to yield")
100100
}
101101
}
102102
}
@@ -112,12 +112,12 @@ final class AsyncBackpressuredStreamTests: XCTestCase {
112112
await backPressureEventIterator.next()
113113
await backPressureEventIterator.next()
114114

115-
print("Waited 4 times")
115+
debug("Waited 4 times")
116116

117117
_ = try await iterator.next()
118118
_ = try await iterator.next()
119119
_ = try await iterator.next()
120-
print("Consumed three")
120+
debug("Consumed three")
121121

122122
await backPressureEventIterator.next()
123123
await backPressureEventIterator.next()

Tests/OpenAPIURLSessionTests/NIOAsyncHTTP1TestServer.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import NIOCore
1515
import NIOPosix
1616
import NIOHTTP1
17+
@testable import OpenAPIURLSession
1718

1819
final class AsyncTestHTTP1Server {
1920

@@ -58,10 +59,10 @@ final class AsyncTestHTTP1Server {
5859
for try await connectionChannel in inbound {
5960
group.addTask {
6061
do {
61-
print("Sevrer handling new connection")
62+
debug("Sevrer handling new connection")
6263
try await connectionHandler(connectionChannel)
63-
print("Server done handling connection")
64-
} catch { print("Server error handling connection: \(error)") }
64+
debug("Server done handling connection")
65+
} catch { debug("Server error handling connection: \(error)") }
6566
}
6667
}
6768
}

Tests/OpenAPIURLSessionTests/URLSessionBidirectionalStreamingTests/HTTPBodyOutputStreamTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import XCTest
1919

2020
// swift-format-ignore: AllPublicDeclarationsHaveDocumentation
2121
class HTTPBodyOutputStreamBridgeTests: XCTestCase {
22-
static override func setUp() { OpenAPIURLSession.debugLoggingEnabled = true }
22+
static override func setUp() { OpenAPIURLSession.debugLoggingEnabled = false }
2323

2424
func testHTTPBodyOutputStreamInputOutput() async throws {
2525
let chunkSize = 71

Tests/OpenAPIURLSessionTests/URLSessionBidirectionalStreamingTests/MockInputStreamDelegate.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#if canImport(Darwin)
1515

1616
import Foundation
17+
@testable import OpenAPIURLSession
1718

1819
/// Reads one byte at a time from the stream, regardless of how many bytes are available.
1920
///
@@ -39,7 +40,7 @@ final class MockInputStreamDelegate: NSObject, StreamDelegate {
3940
self.inputStream.open()
4041
}
4142

42-
deinit { print("Input stream delegate deinit") }
43+
deinit { debug("Input stream delegate deinit") }
4344

4445
private func readAndResumeContinuation() {
4546
dispatchPrecondition(condition: .onQueue(Self.streamQueue))
@@ -52,15 +53,15 @@ final class MockInputStreamDelegate: NSObject, StreamDelegate {
5253
}
5354
switch buffer.count {
5455
case -1:
55-
print("Input stream delegate error reading from stream: \(inputStream.streamError!)")
56+
debug("Input stream delegate error reading from stream: \(inputStream.streamError!)")
5657
inputStream.close()
5758
continuation.resume(throwing: inputStream.streamError!)
5859
case 0:
59-
print("Input stream delegate reached end of stream; will close stream")
60+
debug("Input stream delegate reached end of stream; will close stream")
6061
self.close()
6162
continuation.resume(returning: nil)
6263
case let numBytesRead where numBytesRead > 0:
63-
print("Input stream delegate read \(numBytesRead) bytes from stream: \(buffer)")
64+
debug("Input stream delegate read \(numBytesRead) bytes from stream: \(buffer)")
6465
continuation.resume(returning: buffer)
6566
default: preconditionFailure()
6667
}
@@ -85,12 +86,12 @@ final class MockInputStreamDelegate: NSObject, StreamDelegate {
8586
func close(withError error: (any Error)? = nil) {
8687
self.inputStream.close()
8788
Self.streamQueue.async { self.state = .closed(error) }
88-
print("Input stream delegate closed stream with error: \(String(describing: error))")
89+
debug("Input stream delegate closed stream with error: \(String(describing: error))")
8990
}
9091

9192
func stream(_ stream: Stream, handle event: Stream.Event) {
9293
dispatchPrecondition(condition: .onQueue(Self.streamQueue))
93-
print("Input stream delegate received event: \(event)")
94+
debug("Input stream delegate received event: \(event)")
9495
switch event {
9596
case .hasBytesAvailable:
9697
switch state {

Tests/OpenAPIURLSessionTests/URLSessionBidirectionalStreamingTests/URLSessionBidirectionalStreamingTests.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import XCTest
2323

2424
class URLSessionBidirectionalStreamingTests: XCTestCase {
2525
// swift-format-ignore: AllPublicDeclarationsHaveDocumentation
26-
static override func setUp() { OpenAPIURLSession.debugLoggingEnabled = true }
26+
static override func setUp() { OpenAPIURLSession.debugLoggingEnabled = false }
2727

2828
func testBidirectionalEcho_PerChunkRatchet_1BChunk_1Chunks_1BUploadBuffer_1BDownloadWatermark() async throws {
2929
try await testBidirectionalEchoPerChunkRatchet(
@@ -329,31 +329,31 @@ class URLSessionBidirectionalStreamingTests: XCTestCase {
329329
)
330330
)
331331
)
332-
print("Server sent response head")
332+
debug("Server sent response head")
333333
for i in 1...numResponseChunks {
334334
try await outbound.write(.body(ByteBuffer(bytes: responseChunk)))
335-
print("Server sent body chunk \(i)/\(numResponseChunks) of \(responseChunk.count)")
335+
debug("Server sent body chunk \(i)/\(numResponseChunks) of \(responseChunk.count)")
336336
}
337337
case .body: preconditionFailure()
338338
case .end:
339339
try await outbound.write(.end(nil))
340-
print("Server sent response end")
340+
debug("Server sent response end")
341341
}
342342
}
343343
}
344344
}
345-
print("Server running on 127.0.0.1:\(serverPort)")
345+
debug("Server running on 127.0.0.1:\(serverPort)")
346346

347347
// Send the request.
348-
print("Client starting request")
348+
debug("Client starting request")
349349
let (response, responseBody) = try await URLSession.shared.bidirectionalStreamingRequest(
350350
for: HTTPRequest(method: .get, scheme: nil, authority: nil, path: "/"),
351351
baseURL: URL(string: "http://127.0.0.1:\(serverPort)")!,
352352
requestBody: nil,
353353
requestStreamBufferSize: 16 * 1024 * 1024,
354354
responseStreamWatermarks: responseStreamWatermarks
355355
)
356-
print("Client received response head: \(response)")
356+
debug("Client received response head: \(response)")
357357
XCTAssertEqual(response.status, .ok)
358358

359359
switch verification {
@@ -362,10 +362,10 @@ class URLSessionBidirectionalStreamingTests: XCTestCase {
362362
var unprocessedBytes = ByteBuffer()
363363
var numProcessedChunks = 0
364364
for try await receivedResponseChunk in responseBody! {
365-
print("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
365+
debug("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
366366
unprocessedBytes.writeBytes(receivedResponseChunk)
367367
while unprocessedBytes.readableBytes >= responseChunk.count {
368-
print("Client reconstructing and verifying chunk \(numProcessedChunks+1)/\(numResponseChunks)")
368+
debug("Client reconstructing and verifying chunk \(numProcessedChunks+1)/\(numResponseChunks)")
369369
XCTAssertEqual(
370370
ArraySlice(unprocessedBytes.readBytes(length: responseChunk.count)!),
371371
responseChunk
@@ -379,14 +379,14 @@ class URLSessionBidirectionalStreamingTests: XCTestCase {
379379
case .count:
380380
var numBytesReceived = 0
381381
for try await receivedResponseChunk in responseBody! {
382-
print("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
382+
debug("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
383383
numBytesReceived += receivedResponseChunk.count
384384
}
385385
XCTAssertEqual(numBytesReceived, responseChunk.count * numResponseChunks)
386386
case .delay(let delay):
387387
for try await receivedResponseChunk in responseBody! {
388-
print("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
389-
print("Client doing fake work for \(delay)s")
388+
debug("Client received some response body bytes (numBytes: \(receivedResponseChunk.count))")
389+
debug("Client doing fake work for \(delay)s")
390390
try await Task.sleep(nanoseconds: UInt64(delay.nanoseconds))
391391
}
392392
}

0 commit comments

Comments
 (0)