Skip to content

Commit

Permalink
add flush api to default stdout/stderr logger (#87)
Browse files Browse the repository at this point in the history
* add flush api to default stdout/stderr logger

motivation: default logger may not flush the streams on crush, so need to make flushing moe explicit

changes:
* add a constructor argument to control flushing behaviour, default to "everytime"
* add a "flush" method to allow user defined flush timing. this api is now internal but we could expose it if there is a need
* add tests

* fixup
  • Loading branch information
tomerd authored and weissi committed Aug 13, 2019
1 parent ebdfdcf commit e309184
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 5 deletions.
26 changes: 21 additions & 5 deletions Sources/Logging/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -531,19 +531,35 @@ public struct MultiplexLogHandler: LogHandler {
/// cross-thread interleaving of output.
internal struct StdioOutputStream: TextOutputStream {
internal let file: UnsafeMutablePointer<FILE>
internal let flushMode: FlushMode

internal func write(_ string: String) {
string.withCString { ptr in
flockfile(file)
flockfile(self.file)
defer {
funlockfile(file)
funlockfile(self.file)
}
_ = fputs(ptr, self.file)
if case .always = self.flushMode {
self.flush()
}
_ = fputs(ptr, file)
}
}

internal static let stderr = StdioOutputStream(file: systemStderr)
internal static let stdout = StdioOutputStream(file: systemStdout)
/// Flush the underlying stream.
/// This has no effect when using the `.always` flush mode, which is the default
internal func flush() {
_ = fflush(self.file)
}

internal static let stderr = StdioOutputStream(file: systemStderr, flushMode: .always)
internal static let stdout = StdioOutputStream(file: systemStdout, flushMode: .always)

/// Defines the flushing strategy for the underlying stream.
internal enum FlushMode {
case undefined
case always
}
}

// Prevent name clashes
Expand Down
1 change: 1 addition & 0 deletions Tests/LoggingTests/LoggingTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extension LoggingTest {
("testLogLevelCases", testLogLevelCases),
("testLogLevelOrdering", testLogLevelOrdering),
("testStreamLogHandlerWritesToAStream", testStreamLogHandlerWritesToAStream),
("testStdioOutputStreamFlush", testStdioOutputStreamFlush),
]
}
}
68 changes: 68 additions & 0 deletions Tests/LoggingTests/LoggingTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
@testable import Logging
import XCTest

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#else
import Glibc
#endif

class LoggingTest: XCTestCase {
func testAutoclosure() throws {
// bootstrap with our test logging impl
Expand Down Expand Up @@ -444,4 +450,66 @@ class LoggingTest: XCTestCase {
XCTAssertTrue(messageSucceeded ?? false)
XCTAssertEqual(interceptStream.strings.count, 1)
}

func testStdioOutputStreamFlush() {
// flush on every statement
self.withWriteReadFDsAndReadBuffer { writeFD, readFD, readBuffer in
let logStream = StdioOutputStream(file: writeFD, flushMode: .always)
LoggingSystem.bootstrapInternal { StreamLogHandler(label: $0, stream: logStream) }
Logger(label: "test").critical("test")

let size = read(readFD, readBuffer, 256)
XCTAssertGreaterThan(size, -1, "expected flush")

logStream.flush()
let size2 = read(readFD, readBuffer, 256)
XCTAssertEqual(size2, -1, "expected no flush")
}
// default flushing
self.withWriteReadFDsAndReadBuffer { writeFD, readFD, readBuffer in
let logStream = StdioOutputStream(file: writeFD, flushMode: .undefined)
LoggingSystem.bootstrapInternal { StreamLogHandler(label: $0, stream: logStream) }
Logger(label: "test").critical("test")

let size = read(readFD, readBuffer, 256)
XCTAssertEqual(size, -1, "expected no flush")

logStream.flush()
let size2 = read(readFD, readBuffer, 256)
XCTAssertGreaterThan(size2, -1, "expected flush")
}
}

func withWriteReadFDsAndReadBuffer(_ body: (UnsafeMutablePointer<FILE>, CInt, UnsafeMutablePointer<Int8>) -> Void) {
var fds: [Int32] = [-1, -1]
fds.withUnsafeMutableBufferPointer { ptr in
let err = pipe(ptr.baseAddress!)
XCTAssertEqual(err, 0, "pipe faild \(err)")
}

let writeFD = fdopen(fds[1], "w")
let writeBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: 256)
defer {
writeBuffer.deinitialize(count: 256)
writeBuffer.deallocate()
}

var err = setvbuf(writeFD, writeBuffer, _IOFBF, 256)
XCTAssertEqual(err, 0, "setvbuf faild \(err)")

let readFD = fds[0]
err = fcntl(readFD, F_SETFL, fcntl(readFD, F_GETFL) | O_NONBLOCK)
XCTAssertEqual(err, 0, "fcntl faild \(err)")

let readBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: 256)
defer {
readBuffer.deinitialize(count: 256)
readBuffer.deallocate()
}

// the actual test
body(writeFD!, readFD, readBuffer)

fds.forEach { close($0) }
}
}

0 comments on commit e309184

Please sign in to comment.