swift-log icon indicating copy to clipboard operation
swift-log copied to clipboard

Follow up: Showcase how a more complex logging backend library can bootstrap / keep its shared state

Open Ponyboy47 opened this issue 5 years ago • 4 comments

Title and end goal of ticket changed to add some examples, see discussion below for detail (edit: ktoso)


I was looking into creating a FileLogHandler but I am unsure of a safe and convenient way to go about doing that due to issues inherent with logging to files vs something like stdout with the current limitations of this framework.

For example, there is no available throwable factory overload and nowhere is it documented that a logger requires an initializer that takes a single String argument. Looking through the code it becomes much easier to see that you need some kind of an initializer that takes a single string argument. Why is there no official protocol requirement for the initializer if this is clearly a requirement?

I want to make a FileLogHandler, but with the current standard functionality, everyone would just be accustomed to passing in a label. I could just log in a common location like /var/log on linux, but people should be able to specify a custom file location if they so choose.

The Logger initializer that takes a LogHandler argument is internal so I cannot use that and the restrictions on the factory signature are too restrictive to allow this. I could make the String parameter the path to the log file, but since it is not throwable or nullable I cannot do any sort of validation that the path exists or is writable by the current process. Do I just silently fail? Should I just print a message?

Why are there no throwable/nullable overloads for the factory? or why is there no ability to use a custom factory signature? The fileprivate restrictions make it more difficult to provide my own handy extensions to achieve my desired functionality.

Is there something I'm missing here or was there just massive oversight when designing this? From what I can tell it would be extremely difficult to set up a logger that sends logs to an external service requiring connection parameters and there's no way you could validate those parameters during initialization of the handler.

Ponyboy47 avatar Jul 02 '19 18:07 Ponyboy47

Hi @Ponyboy47, I think the main thing to realize here is that this API is not meant to be "your only api", it is a common ground for libraries that don't care where they log, they want to log, and have some logical identifier about what logger they're using. You can think of this as a "frontend" or "SPI" (service provider interface), rather than "the API". For example, if there's some database library -- it cannot and must not know about your specific logging backend library, it can only use the shared types -- to log, and assume that users have configured the loggers - that "configured" part would actually be done using your library, not these shared types -- as then it is in the hands of the end user, writing an application/system.

This is explained in https://github.com/apple/swift-log#what-is-an-api-package

I want to make a FileLogHandler, but with the current standard functionality, everyone would just be accustomed to passing in a label. I could just log in a common location like /var/log on linux, but people should be able to specify a custom file location if they so choose.

Indeed, people should be able to configure your backend, and they should do so when initializing the backend:

class MyLoggerBackend {

    let targetFile: File

    init (targetFilePath: String) {
        // open the file
    }

    func makeLogHandler(for label: String) -> LogHandler {
        // make a handler that appends to the log file, or does something smart like a rolling file appender etc etc
        // return that log handler
        return MyInternalAppender(...)
    }
}

class Main {
    func run () {
        let backend = MyLoggerBackend(targetFilePath: "/tmp/example.log") // throw if you want
        LoggingSystem.bootstrap { label in backend.makeLogHandler(for: label) }
    }
}

or similar, depending on how and where you need to keep state.

Libraries MAY also opt to document people to do MyNiceFileLoggingSystem.bootstrap(config) and you'd document that "please boostrap like this" if you want to allow passing in more config this way etc.

This is unrelated to libraries which only need to care about "there will be some logging backend, and whoever ends up using this library, will set it up"


Is there something I'm missing here or was there just massive oversight when designing this? From what I can tell it would be extremely difficult to set up a logger that sends logs to an external service requiring connection parameters and there's no way you could validate those parameters during initialization of the handler.

On another note here, if this is only about initialization... then to be honest a fatalError() may be a better idea there than a throw. How would you expect people to recover from such throw? Esp. in the file example, if we cannot open the file for logging... i'd rather really crash rather than potentially run the system without properly (as i had expected) configured logging.

Hope this helps. Do you think we'd need more examples about this in the README or elsewhere?

ktoso avatar Jul 03 '19 03:07 ktoso

Thanks for the excellent answer @ktoso! This was most helpful. It would be nice if there were more examples of how to set up more complex LogHandlers like this in the README as I'm sure I won't be the only person with questions like this.

I tried looking through code from this repo and also from the syslog handler by ianpartridge but I was very confused about how this API could be used to take a more complex LogHandler and bootstrap it or create a Logger from it since everything out there right now just uses the basic .init(label: String) which wouldn't suit my needs.

You also make a good point that a fatalError() would be better than throwing. Thanks for your help and tips!

Ponyboy47 avatar Jul 03 '19 14:07 Ponyboy47

Thanks :) Makes sense to follow up here with an example to the repo. Open for grabs if someone wants to, or I'll try to make some time for it; Ok if I'd rename the ticket to be about adding such an example, @Ponyboy47?

Please do ping if you have any other questions and/or would like to list your library as implementation of the API so others can discover it more easily! 🤗

ktoso avatar Jul 03 '19 14:07 ktoso

Feel free to rename the ticket :) And I'll definitely be posting back once I've gotten my library fleshed out and ready for usage! Thanks again

Ponyboy47 avatar Jul 03 '19 14:07 Ponyboy47