-
Notifications
You must be signed in to change notification settings - Fork 277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose an API for configuring basic stdio log handling #61
Conversation
Can one of the admins verify this patch? |
1 similar comment
Can one of the admins verify this patch? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks a lot for spotting it and the PR! 👍
@swift-server-bot test this please |
we may want to finalize #60 before making it public? |
@allenhumphreys would you mind changing this PR following the suggestion in #60 (comment), ie create a public generic |
@tomerd I can give it a shot. I briefly looked into the correct way to do this in Swift the other day, |
Digging into the stdlib a little bit.. using something like:
with
might be better but I don't know why you can't |
@allenhumphreys wdyt about what @weissi suggested in https://github.com/apple/swift-log/pull/60/files#diff-259e1bfbf733f7f456500e470bf358cdR501 |
Oh good point, I hadn't seen that portion of it! Thank you! |
6f789c6
to
6cb3f5a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is shaping up nicely. couple of comments. add some tests too?
@tomerd Tests will have to come on a different day! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! That looks great.
@swift-server-bot test this please |
@allenhumphreys looks like tests are failing on linux. you can run them locally with |
Sources/Logging/Logging.swift
Outdated
internal struct StdoutLogHandler: LogHandler { | ||
public struct StdioLogHandler: LogHandler { | ||
|
||
public enum Stream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys just as a question if you think this is useful. We could also do the following here: Instead of an enum Stream
we could do
// this is just to prevent name clashes further down
#if os(macOS) || os(tvOS) || os(iOS) || os(watchOS)
let sysStderr = Darwin.stderr
let sysStdout = Darwin.stdout
#else
let sysStderr = Glibc.stderr
let sysStdout = Glibc.stdout
#endif
public struct Stream {
private let underlying: UnsafePointer<FILE>
internal init(underlying: UnsafePointer<FILE>) {
self.underlying = underlying
}
public static let stderr: Stream = .init(underlying: sysStderr)
public static let stdout: Stream = .init(underlying: sysStdout)
}
that way, the API is the same for the user: StdioLogHandler(label: "foo", stream: .stdout)
will still work but we have two extra benefits:
- in tests, we can use
StdioLogHandler(label: "foo", stream: .init(underlying: fopen(".....", "w+")))
- if we chose to allow other streams in the future, then we can add them without breaking public API. The problem with
enum
s is: Adding a case breaks the API because someone might have done an exhaustiveswitch
over it. Thestruct
+static let
solution from above alleviates that issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @weissi , this is good, but I'd like to propose going a little further.
- Make
Stream
wrapTextOutputStream
instead - Rename
StdioLogHandler
toStreamLogHandler
- Go ahead and expose the initializer for
Stream
It'd look something like this:
public struct StreamLogHandler: LogHandler {
public struct Stream: TextOutputStream {
private let underlying: TextOutputStream
public init(underlying: TextOutputStream) {
self.underlying = underlying
}
public mutating func write(_ string: String) {
var underlying = self.underlying
underlying.write(string)
}
public static let stderr: Stream = .init(underlying: FileOutputStream(file: sysStderr))
public static let stdout: Stream = .init(underlying: FileOutputStream(file: sysStdout))
}
private let lock = Lock()
public let stream: Stream
public init(label: String, stream: Stream = .stdout) {
self.stream = stream
}
...
...
}
FileOutputStream
would remain internal, but clearly available as a reference for consumers. This would allow people to immediately swap in their own streams. It eliminates the need to work with Pointers, or even the c standard library at all. Stream
ends up's up just being a box and a convenient way to get access to stderr
and stdout
in a way that hides the UnsafeMutablePointer<FILE>
stuff.
Thoughts? Too far?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys Thanks, that sounds like a great idea to me. I think you can remove the Lock
because if the user wants locking, they could implement that themselves, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good idea 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys Thanks, that sounds like a great idea to me. I think you can remove the Lock because if the user wants locking, they could implement that themselves, no?
Is that a safe default? in case a concurrent program would use the default loggers shipped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys ah, you can remove that lock. LogHandler
s are supposed to have value semantics for the configuration (metadata & logLevel) and indeed your handler has, so there's no need to lock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys thank you! Apologies, for some reason I thought you added the lock. I'd propose to just remove it because it's baggage from the past :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In light of discovering how Swift.print
functions, I tweaked the API again. I removed the wrapper type StreamLogHandler.Stream
and exposed StdioOutputStream
to allow easily switching between stderr
and stdio
. StdioOutputStream
is not publicly instantiable, though, so the usage is still restricted. I believe this API is now much more straightforward as it just uses TextOutputStream
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys perfect. I think it you make the output streams non-public, then we can merge this.
@tomerd @ktoso @weissi I have updated the implementation, the documentation I've proposed isn't complete yet, but I've laid out the general direction. I've left the locking behavior the same per my last comment on the thread. Thanks everyone for continuing to help evolve the direction of this change. |
3dfe984
to
1d4fe09
Compare
@swift-server-bot test this please |
Sources/Logging/Logging.swift
Outdated
/// A wrapper to facilitate `print`-ing to stderr and stdio that | ||
/// ensures access to the underlying `FILE` is locked to prevent | ||
/// cross-thread interleaving of output. | ||
public struct StdioOutputStream: TextOutputStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The StdioOutputStream
we should leave internal
or private
as that's a thing the stdlib should really provide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I have a similar feeling but keeping it private requires some kind of additional wrapper type or switch to power StreamLogHandler
's public initializer (fundamentally providing the consumer the ability to switch between stderr and stdout).
As I had it before StreamLogHandler.Stream
also conformed to TextOutputStream
.
Previously it was
public static let stderr: Stream = .init(underlying: FileOutputStream(file: systemStderr))
now this has the same level of access:
public static let stdout = StdioOutputStream(file: systemStdout)
but with less indirection.
Ultimately something has to be public in that initializer because one of the goals of this change is to allow easily redirected to stderr
. Alternatively, to keep the type private, I could have multiple specialized initializers or possibly static factory methods to provide this functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys if you overload the init
of StreamLogHandler
instead of using default arguments, it'll work. All the streams stuff can stay internal
/private
. So
public struct StreamLogHandler {
...
public init(label: String) {
self.init(stream: StdioOutputStream(stream: sysStdout))
}
public init(label: String, stream: TextOutputStream) {
...
}
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't leave a good name for an init that goes to stderr
, any suggestion good or not as good?
Just thinking out loud:
init(toStdoutWithLabel label: String)
init(toStderrWithLabel label: String)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@allenhumphreys Ahh, I see! I'd then go with
public struct StreamLogHandler {
public static func makeStdoutLogHandler(label: String) -> StreamLogHandler { ... }
public static func makeStderrLogHandler(label: String) -> StreamLogHandler { ... }
...
}
and then we can even leave the init
private
StdoutLogHandler
to the public964e833
to
bc40921
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks! I'm happy with this.
@swift-server-bot test this please |
@ktoso / @tomerd / @ianpartridge I'm happy exactly as is, would you mind having a quick look? Just because there were a lot of changes. |
Sources/Logging/Logging.swift
Outdated
|
||
public var logLevel: Logger.Level { | ||
/// Factory that makes a `StreamLogHandler` to directs its output to `stdout` | ||
public static func makeStdoutLogHandler(label: String) -> StreamLogHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming nit: i like the new factories but dont love the names. maybe makeStdout
and makeStderr
is enough. in any case not a big deal, so can go with this too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it is a bit repetitive in the full context StreamLogHandler.makeStdoutLogHandler
, happy to change. While you're considering names, I made up the StreamLogHandler
name to convey it's new expanded capabilities, but it could just as well be TextOutputStreamLogHandler
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming is hard. @weissi any preferences?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomerd not really but I agree with both of you that makeStdoutLogHandler
is a bit long and redundant. We could even do something like standardOutput
only. Then it would read
Logging.bootstrap(StreamLogHandler.standardOutput)
makeStdout
sounds like it's making a 'stdout' to me...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm and thank you @allenhumphreys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AWESOME, thanks, this looks great!
@swift-server-bot test this please |
<3 |
Expose
StreamLogHandler
to the public which replaces the previously internalStdoutLogHandlers
. Users can choose now the output stream, whilestdout
is the default.Motivation:
As discussed on #51 and #60
Modifications:
Rename
StdoutLogHandler
toStreamLogHandler
and provide 2 factory methodsstandardOutput(label:)
andstandardError(label:)
for using during bootstrapping.Result:
Users who adopt other logging backends during
LoggingSystem
bootstrapping will still be able to use the default log handler to get very basic console output to their choice ofstdout
orstderr
.Example: