3

So locally when in dev through xcode or compiled with SPM the console logs appear as expected.

i.e with SPM locally everything is fine

swift build --configuration release 
.build/release/Myapp # prints to console

but when I am running the executable through a docker container running on ECS (linux I suppose), I don't see the logs generated by my Swift code but I do see stderr being printed by 3rd party libraries (i.e. libssl is printing errors) as well as shell logs when starting the application

For example:

Dockerfile

FROM swift
WORKDIR /app

COPY Package.swift ./
COPY Sources ./Sources
COPY Tests ./Tests
RUN swift package clean
RUN swift build --configuration release
RUN chmod +x start.sh
CMD ["start.sh"] # just a wrapper to see if "echo" works

in start.sh

# prints as expected
echo "hi this will print"
# nothing in the executable will print though
.build/release/MyApp

Avba
  • 14,822
  • 20
  • 92
  • 192

3 Answers3

4

Had the same issue, I filed a radar, and Apple answered:

When piped to another process print is buffered, so no characters appear until the buffer is filled up. (When piped to the terminal we only buffer until we hit a newline.)

You can get the behavior you want by calling setbuf(stdout, nil) once at startup:

 import Darwin
 setbuf(stdout, nil)
Snowy_1803
  • 656
  • 2
  • 8
  • 24
3

I'm not sure why it isn't showing up in standard output, but you can do this:

import Foundation
internal struct FileHandleOutputStream: TextOutputStream {
    private let fileHandle: FileHandle
    let encoding: String.Encoding

    init(_ fileHandle: FileHandle, encoding: String.Encoding = .utf8) {
        self.fileHandle = fileHandle
        self.encoding = encoding
    }

    mutating func write(_ string: String) {
        if let data = string.data(using: encoding) {
            fileHandle.write(data)
        }
    }
}
internal var STDERR = FileHandleOutputStream(.standardError)
internal var STDOUT = FileHandleOutputStream(.standardOutput)

which allows you to do:

print("error message", to: &STDERR)

to print to standard error.

I know this is just a workaround, but it may be better than nothing.

Sam
  • 2,350
  • 1
  • 11
  • 22
  • 1
    Had a same issue without any Dockers. Simply running `swift run ...` printed all as expected, but with `swift run ... > out.txt 2> err.txt` everything stopped working. `FileHandleOutputStream` did the job – Tim Feb 06 '20 at 15:35
0

With this code, you gonna override all the prints so it will work on Linux and Mac Xcode. I used the response from @sam and added the override print

import Foundation

internal var STDERR = FileHandleOutputStream(.standardError)
internal var STDOUT = FileHandleOutputStream(.standardOutput)

internal struct FileHandleOutputStream: TextOutputStream {
    private let fileHandle: FileHandle
    let encoding: String.Encoding

    init(_ fileHandle: FileHandle, encoding: String.Encoding = .utf8) {
        self.fileHandle = fileHandle
        self.encoding = encoding
    }

    mutating func write(_ string: String) {
        if let data = string.data(using: encoding) {
            fileHandle.write(data)
        }
    }
}

///
/// Override of the print so it can work with Linux and MAC
/// https://stackoverflow.com/questions/39026752/swift-extending-functionality-of-print-function
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    
    let output = items.map { "\($0)" }.joined(separator: separator)
    
    #if os(Linux)
        print(output, to: &STDERR)
    #else
        Swift.print(output, terminator: terminator)
    #endif
}
Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35