Skip to content
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

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

Open
Ponyboy47 opened this issue Jul 2, 2019 · 4 comments
Labels
kind/support Adopter support requests.

Comments

@Ponyboy47
Copy link

Ponyboy47 commented Jul 2, 2019

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.

@ktoso
Copy link
Member

ktoso commented Jul 3, 2019

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 ktoso added the kind/support Adopter support requests. label Jul 3, 2019
@Ponyboy47
Copy link
Author

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!

@ktoso
Copy link
Member

ktoso commented Jul 3, 2019

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! 🤗

@Ponyboy47
Copy link
Author

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

@ktoso ktoso changed the title Limited usage of this as a general purpose log handler Showcase how a more complex logging backend library can bootstrap / keep its shared state Jul 3, 2019
@ktoso ktoso changed the title Showcase how a more complex logging backend library can bootstrap / keep its shared state Follow up: Showcase how a more complex logging backend library can bootstrap / keep its shared state Jul 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/support Adopter support requests.
Projects
None yet
Development

No branches or pull requests

2 participants