Yes. When you say metadata I reckon you mean metadata as used in this proposal + log level, right? In any case, I agree with your statement, it’s one of the deliberate tradeoffs of allowing things like ‘one true global log level’ and other things like being able to dynamically change one subsystems log level (at runtime) through configuration.
I would love to say: Logger owns log level + metadata and we just tell the logging backend what to log but it doesn’t seem quite flexible enough. 100% defined semantics, sounds great.
But if we’re too opinionated we might not be able to support a certain important project (say one of the big frameworks) which would severely hinder adoption I think. Over time we can learn and converge to a good model.
Yes of course, but any log handler that actually does something useful is going to have reference semantics internally. It doesn’t matter that you only store a constant reference to it. If it’s performing IO of any kind reference semantics are involved somewhere. That is the point I am making.
It’s clear that you have accepted the hybrid semantics as a necessary compromise. That’s fine, but let’s not pretend Logger has value semantics across the board even when it is backed by a handler implemented as you recommend.
If people want a single global log level across all loggers that can be set using any logger then sure.
One thing to keep in mind is that people don’t just have positive requirements. People also have negative requirements, i.e. things they don’t want to be possible in their codebase. The compromises in this design seem to all have been made to accomodate the former at the expense of the latter. We don’t need to rehash the discussion upthread again, but I think it’s worth trying to keep both perspectives in mind.
For example: A Logger’s log level is whatever its LogHandler returns. So it could just return whatever’s in some global private var rogueLoggingPackageGlobalLogLevel: LogLevel. Even if we changed the API to be let newLogger = oldLogger.setLogLevel(.debug), there’s zero guarantee that oldLogger isn’t also changed because in the current proposal, the log level is controlled by the implementor of the selected LogHandler.
And yes, we could change this and have Logger (which is from the proposed API package) own the log level and therefore the semantics for a change of log level but then we probably won’t be able to express all the ways people want to manage log levels.
I think this comment hints at the source of my confusion. Above, when you described the requirement
did you accidentally leave out the additional requirement "and it must be possible to change that single log level for all loggers simultaneously while the system is executing"? If so, I do see how that additional requirement clearly rules out immutable semantics for logger log level (I'm not convinced the necessary compromises are worth it but certainly don't want to re-litigate that question. Clearly, however, application developers deserve as fine grained control over log level as possible. Something which library writers should keep in mind).
It doesn't really explain why the same is true for metadata. Unlike log level, metadata is more "additive".
I've been reading this thread with a lot of interested and I'd like to add my 2 cents:
I don't think the fact there is global state is reason for making it visible in the API. The API could be nicer by getting rid of Logging and moving bootstrap and make to Logger (bootstrap can't be moved to LogHandler because its a protocol), like @xwu suggested. See my proposed modified below.
In the current iteration of the API, the value semantics of Logger are entirely dependent on the value semantics of LogHandler. And I think that's bad for two reasons:
The value semantics of LogHandler are not guaranteed, and that's very unusual for Swift: you can give value semantics to classes, but it's very unusual for a structnot to have value semantics.
It's not at all obvious to consumers of the API (it took me some time to grasp it) that the value semantics of Logger dynamically depends on the value semantics of LogHandler, especially as Logger's handler property and initialiser are not public, and therefore not visible in any code completion or documentation: all users see is a simple struct with methods on it.
Conversely, it's not al all obvious to LogHandler library providers that the value semantics of the type they provide will affect the value semantics of Loggers.
One remaining question I have is the intention/purpose of the label on Logger (or the entire Logging framework for that matter).
In my opinion it is valuable to have a (maybe optional?) label for debugging purposes (a bit like giving a DispatchQueue a name), or - if not optional - a "guaranteed metadata" that can be used to identify where a log message came from in a LogHandler.
This however is not implemented in the proposal.
The Logger takes a label (in an initializer in an extension) but doesn't store it (it just uses it for the factory to produce LogHandlers). It is completely unclear to the user, why the Logging system requires a String in the bootstrap method, and for what purpose.
And those LogHandlers don't even have the concept of labels which in turn makes the label somewhat useless.
What is the intention here? What should the label do? What's its intended purpose?
The intention was just that a LogHandler can use the label if it chooses too. But I agree we could have a property to be able to read the label back on Logger which would take it from a to-be-created property on LogHandler.
Would you provide an example use case for this label property? In general wouldn't the owner of a logger already know the label which was used to create the logger (since the owner would have provided the label to the factory)?
I guess it can be useful in debugging for example (just like with dispatch queues).
I have used it to designate the subsystem where I create the logger (e.g. let logger Logger(label: "com.app.apiclient")) but I understand that this could also be handled with metadata.
My main point was that since the framework clearly has a notion of labels in the Logging API, there should be a way to read it back out.
Just to give an update here that the official repo is on track and we'll open that very soon which will then open the development of the OSS project. I'll post another link here very soon.
One other thing that I should have posted here is that there's a separate discussion about the log levels for os_log and this logging API going on in the forums. The logging API will adopt the outcome of that, it very much looks like it'll be the syslog log levels.
just like proposed for osLog, to leverage the best performance and have more unification we switched the log messages' type from String to Logger.Message which is a struct that can be created using any string literal + string interpolation. Don't worry, it looks & feels just like a String (logger.info("hello world"))
Thank you so much for all the contributions! Now more than ever: please keep your awesome contributions coming, this is only the start of swift-log and swift-log is only the start of a logging that works across the whole ecosystem.