# \[Feedback\] Server Logging API (with revisions)

**URL:** <https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375>\
**Category:** Proposals\
**Created:** [January 9, 2019, 6:48pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375 "2019-01-09T18:48:11Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 9, 2019, 6:48pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/1 "2019-01-09T18:48:11Z")

</div>

# Proposal Review: SSWG-0001 (Server Logging API)

After the great [discussion thread](https://forums.swift.org/t/server-logging-api/18834), we are proposing this as a final revision of this proposal and enter the proposal review phase which will run until the 20th January 2019.

We have integrated most of the feedback from the discussion thread so even if you have read the previous version, you will find some changes that you hopefully agree with. To highlight a few of the major changes:

- Logging metadata is now structures and now supports nested dictionaries and list values, in addition to strings of course.
- only locally relevant metadata can now be passed to the individual log methods.
- ship a multiplex logging solution with the API package

The feedback model will be very similar to the one known from Swift Evolution. The community is asked to provide feedback in the way outlined below and after the review period finishes, the SSWG will -- based on the community feedback -- decide whether to promote the proposal to the [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) maturity level or not.

### What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the evolution of the server-side Swift ecosystem.

When reviewing a proposal, here are some questions to consider:

- What is your evaluation of the proposal?

- Is the problem being addressed significant enough?

- Does this proposal fit well with the feel and direction of Swift on Server?

- If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

- How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for contributing to the Swift Server Work Group!

### What happens if the proposal gets accepted?

If this proposal gets accepted, the official repository will be created and the code (minus examples, the proposal text, etc) will be submitted. The repository will then become usable as a SwiftPM package and a version (likely `0.1.0`) will be tagged. The development (in form of pull requests) will continue as a regular open-source project.

* * *

# Proposal Review: SSWG-0001 (Server Logging API)

(_EDIT: @johannesweiss : 2019-01-14: fixed typos/small errors/removed broken links, thanks @anandabits, @royhsu & @ktoso_)

After the great [discussion thread](https://forums.swift.org/t/server-logging-api/18834), we are proposing this as a final revision of this proposal and enter the proposal review phase which will run until the 20th January 2019.

We have integrated most of the feedback from the discussion thread so even if you have read the previous version, you will find some changes that you hopefully agree with. To highlight a few of the major changes:

- Logging metadata is now structures and now supports nested dictionaries and list values, in addition to strings of course.
- only locally relevant metadata can now be passed to the individual log methods.
- ship a multiplex logging solution with the API package

The feedback model will be very similar to the one known from Swift Evolution. The community is asked to provide feedback in the way outlined below and after the review period finishes, the SSWG will -- based on the community feedback -- decide whether to promote the proposal to the [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) maturity level or not.

### What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the evolution of the server-side Swift ecosystem.

When reviewing a proposal, here are some questions to consider:

- What is your evaluation of the proposal?

- Is the problem being addressed significant enough?

- Does this proposal fit well with the feel and direction of Swift on Server?

- If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

- How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for contributing to the Swift Server Work Group!

### What happens if the proposal gets accepted?

If this proposal gets accepted, the official repository will be created and the code (minus examples, the proposal text, etc) will be submitted. The repository will then become usable as a SwiftPM package and a version (likely `0.1.0`) will be tagged. The development (in form of pull requests) will continue as a regular open-source project.

* * *

# Server Logging API

- Proposal: SSWG-0001
- Authors: [Johannes Weiss](https://github.com/weissi) & [Tomer Doron](https://github.com/tomerd)
- Preferred initial maturity level: [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram)
- Name: `swift-server-logging-api`
- Sponsor: Apple
- Status: Active review (9th...20th January, 2019)
- Implementation: [https://github.com/weissi/swift-server-logging-api-proposal](https://github.com/weissi/swift-server-logging-api-proposal), if accepted, a fresh repository will be created under [Apple · GitHub](http://github.com/apple)
- External dependencies: _none_
- License: if accepted, it will be released under the [Apache 2](https://www.apache.org/licenses/LICENSE-2.0.html) license
- Pitch: [Server: Pitches/Logging](https://forums.swift.org/t/logging/16027)
- Description: A flexible API package that aims to become the standard logging API which Swift packages can use to log. The formatting and delivery/persistence of the log messages is handled by other packages and configurable by the individual applications without requiring users of the API package to change.

## Introduction

Almost all production server software needs logging that works with a variety of packages. So far, there have been a number of different ecosystems (e.g. Vapor, Kitura, Perfect, ...) that came up with their own solutions for logging, tracing, metrics, etc.

The SSWG however aims to provide a number of packages that can be shared across within the whole Swift on Server ecosystem so we need some amount of standardisation. Because different applications have different requirements on what logging should exactly do, we are proposing to establish a server-side Swift logging API that can be implemented by various logging backends (called `LogHandler`).  
`LogHandler`s are responsible to format the log messages and to deliver/persist them. The delivery might simply go to `stdout`, might be saved to disk or a database, or might be sent off to another machine to aggregate logs for multiple services. The implementation of concrete `LogHandler`s is out of scope of this proposal and also doesn't need to be standardised across all applications. What matters is a standard API libraries can use without needing to know where the log will end up eventually.

## Motivation

As outlined above we should standardise on an API that if well adopted and applications should allow users to mix and match libraries from different vendors while still maintaining a consistent logging.  
The aim is to support all widely used logging models such as:

- one global logger, ie. one application-wise global that's always accessible
- a scoped logger for example one per class/sub-system
- a local logger that is always explicitly passed around where the logger itself can be a value type

There are also a number of features that most agreed we will need to support, most importantly:

- log levels
- attaching structured metadata (such as a request ID) to the logger and individual log messages
- being able to 'make up a logger out of thin air'; because we don't have one true way of dependency injection and it's better to log in a slightly different configuration than just reverting to `print(...)`

On top of the hard requirements the aim is to make the logging calls as fast as possible if nothing will be logged. If a log message is emitted, it will be mostly up to the `LogHandler`s to do so in a fast enough way. How fast 'fast enough' is depends on the requirements, some will optimise for never losing a log message and others might optimise for the lowest possible latency even if that might not guarantee log message delivery.

## Proposed solution

Overall, we propose three different core, top-level types:

1. `LogHandler`, a `protocol` which defines the interface a logging backend needs to implement in order to be compatible.
2. `Logging`, the logging system itself, used to configure the `LogHandler` to be used as well as to retrieve a `Logger`.
3. `Logger`, the core type which is used to log messages.

To get started with this API, a user only needs to become familiar with the `Logger` type which has a few straightforward methods that can be used to log messages as a certain log level, for example `logger.info("Hello world!")`. There's one step a user needs to perform before being able to log: The have to have a `Logger` instance. This instance can be either retrieved from the logging system itself using

```
let logger = Logging.make("my-app")

```

or through some different way provided by the framework they use or a more specialised logger package which might offer a global instance.

### The `Logger`

We propose one `struct Logger` which has a supports a number of different methods to possibly emit a log message. Namely `trace`, `debug`, `info`, `warning`, and `error`. To send a log a message you pick a log level and invoke the `Logger` method with the same name, for example

```
logger.error("Houston, we've had a problem")

```

We already briefly touched on this: Where do we get `logger: Logger` from? This question has two answers: Either the environment (could be a global variable, could be a function parameter, could be a class property, ...) provides `logger` or if not, it is always possible to obtain a logger from the logging system itself:

```
let logger = Logging.make("com.example.app")
logger.warning("uh oh, this is unexpected")

```

To get the best logging experience and performance, it is advisable to use `.make` to pass `Logger`s around or store them in a global/instance variable rather than `.make`ing new `Logger`s whenever one needs to log a message.

## Detailed design

The full API of `Logger` is visible below except for one detail that is removed for readability: All the logging methods have parameters with default values indicating the file, function, and line of the log message.

```swift
public struct Logger {
    public var logLevel: Logging.Level

    public func log(level: Logging.Level, message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil, error: Error? = nil)

    public func trace(_ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil)
    
    public func debug(_ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil)

    public func info(_ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil)
    
    public func warning(_ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil, error: Error? = nil)
    
    public func error(_ message: @autoclosure () -> String, metadata: @autoclosure () -> Logging.Metadata? = nil, error: Error? = nil)
    
    public subscript(metadataKey metadataKey: String) -> Logging.Metadata.Value? { get set }
    
    public var metadata: Logging.Metadata? { get set }
}

```

The `logLevel` property as well as the `trace`, `debug`, `info`, `warning`, and `error` methods are probably self-explanatory. But the information that can be passed alongside a log message deserves some special mentions:

- `message`, a `String` that is the log message itself and the only required argument.
- `metadata`, a dictionary of metadata information attached to only this log message. Please see below for a more detailed discussion of logging metadata.
- `error`, an optional `Error` that can be sent along with `warning` and `error` messages indicating a possible error that led to this failure.

Both, `message` and `metadata` are `@autoclosure` parameters which means that if the message does not end up being logged (because it's below the currently configured log level) no unnecessary processing is done rendering the `String` or creating the metadata information.

Instead of picking one of the `trace`, `debug`, `info`, etc methods it is also possible to use the `log` method passing the desired log level as a parameter.

### Logging metadata

In production environments that are under heavy load it's often a great help (and many would say required) that certain metadata can be attached to every log message. Instead of seeing

```swift
info: user 'Taylor' logged in
info: user 'Swift' logged in
warn: could not establish database connection: no route to host

```

where it might be unclear if the warning message belong to the session with user 'Taylor' or user 'Swift' or maybe to none of the above, the following would be much clearer:

```swift
info: user 'Taylor' logged in [request_UUID: 9D315532-FA5C-4E11-88E9-520C877F58B5]
info: user 'Swift' logged in [request_UUID: 35CC2687-CD1E-45A3-80B7-CCCE278797E6]
warn: could not establish database connection: no route to host [request_UUID: 9D315532-FA5C-4E11-88E9-520C877F58B5]

```

now it's fairly straightforward to identify that the database issue was in the request where we were dealing with user 'Taylor' because the request ID matches. The question is: How can we decorate all our log messages with the 'request UUID' or other information that we may need to correlate the messages? The easy option is:

```
log.info("user \(userID) logged in [request_UUID: \(currentRequestUUID)]")

```

and similarly for all log messages. But it quickly becomes tedious appending `[request_UUID: \(currentRequestUUID)]` to every single log message. The other option is this:

```
logger[metadataKey: "request_UUID"] = currentRequestUUID

```

and from then on a simple

```
logger.info("user \(userID) logged in")

```

is enough because the logger has been decorated with the request UUID already and from now on carries this information around.

In other cases however, it might be useful to attach some metadata to only one message and that can be achieved by using the `metadata` parameter from above:

```
logger.trace("user \(userID) logged in",
             metadata: ["extra_user_info": [ "favourite_colour": userFaveColour,
                                             "auth_method": "password" ]])

```

The above invocation will log the message alongside metadata merged from the `logger`'s metadata and the metadata provided with the `logger.trace` call.

The logging metadata is of type `typealias Logging.Metadata = [String: Metadata.Value]` where `Metadata.Value` is the following `enum` allowing nested structures rather than just `String`s as values.

```
public enum MetadataValue {
    case string(String)
    indirect case dictionary(Metadata)
    indirect case array([Metadata.Value])
}

```

Users usually don't need to interact with the `Metadata.Value` type directly as it conforms to the `ExpressibleByStringLiteral`, `ExpressibleByStringInterpolation`, `ExpressibleByDictionaryLiteral`, and `ExpressibleByArrayLiteral` protocols and can therefore be constructed using the string, array, and dictionary literals.

Examples:

```
logger.info("you can attach strings, lists, and nested dictionaries",
            metadata: ["key_1": "and a string value",
                       "key_2": ["and", "a", "list", "value"],
                       "key_3": ["where": ["we": ["pretend", ["it": ["is", "all", "Objective C", "again"]]]]]])
logger[metadataKey: "keys-are-strings"] = ["but", "values", ["are": "more"]]
logger.warning("ok, we've seen enough now.")

```

### Custom `LogHandler`s

Just like metadata, custom `LogHandler`s are an advanced feature and users will typically just choose a pre-existing package that formats and persists/delivers the log messages in an appropriate way. Said that, the proposed package here is an API package and it will become much more useful if the community create a number of useful `LogHandler`s to say format the log messages with colour or ship them to Splunk/ELK.

We have already seen before that `Logging.make` is what gives us a fresh logger but that raises the question what kind of logging backend will I actually get when calling `Logging.make`? The answer: It's configurable _per application_. The application -- likely in its main function -- sets up the logging backend it wishes the whole application to use. Libraries should never change the logging implementation as that should owned by the application. Setting up the `LogHandler` to be used is straightforward:

```
Logging.bootstrap(MyFavouriteLoggingImplementation.init)

```

This instructs the `Logging` system to install `MyFavouriteLoggingImplementation` as the `LogHandler` to use. This should only be done once at the start of day and is usually left alone thereafter.

Next, we should discuss how one would implement `MyFavouriteLoggingImplementation`. It's enough to conform to the following protocol:

```swift
public protocol LogHandler {
    func log(level: Logging.Level, message: String, metadata: Logging.Metadata?, error: Error?, file: StaticString, function: StaticString, line: UInt)

    subscript(metadataKey _: String) -> Logging.Metadata.Value? { get set }

    var metadata: Logging.Metadata { get set }

    var logLevel: Logging.Level { get set }
}

```

The implementation of the `log` function itself is rather straightforward: If `log` is invoked, `Logger` itself already decided that given the current `logLevel`, `message` should be logged. In other words, `LogHandler` does not even need to compare `level` to the currently configured level. That makes the shortest possible `LogHandler` implementation really quite short:

```swift
public struct ShortestPossibleLogHandler: LogHandler {
    public var logLevel: Logging.Level = .info
    public var metadata: Logging.Metadata = [:]
    
    public init(_ id: String) {}
    
    public func log(level: Logging.Level, message: String, metadata: Logging.Metadata?, error: Error?, file: StaticString, function: StaticString, line: UInt) {
        print(message) // ignores all metadata, not recommended
    }
    
    public subscript(metadataKey key: String) -> Metadata.Value? {
        get { return self.metadata[key] }
        set { self.metadata[key] = newValue }
    }
}

```

which can be installed using

```
Logging.bootstrap(ShortestPossibleLogHandler.init)

```

### Supported logging models

This API intends to support a number programming models:

1. Explicit logger passing, ie. handing loggers around as value-typed variables explicitly to everywhere log messages get emitted from.
2. One global logger, ie. having one global that is _the_ logger.
3. One logger per sub-system, ie. having a separate logger per sub-system which might be a file, a class, a module, etc.

Because there are fundamental differences with those models it is not mandated whether the `LogHandler` holds the logging configuration (log level and metadata) as a value or as a reference. Both systems make sense and it depends on the architecture of the application and the requirements to decide what is more appropriate.

Certain systems will also want to store the logging metadata in a thread/queue-local variable, some may even want try to automatically forward the metadata across thread switches together with the control flow. In the Java-world this model is called MDC, your mileage in Swift may vary and again hugely depends on the architecture of the system.

I believe designing a [MDC (mapped diagnostic context)](https://logback.qos.ch/manual/mdc.html) solution is out of scope for this proposal but the proposed API can work with such a system (see [examples](https://github.com/weissi/swift-server-logging-api-proposal/blob/50a8c8fdaceef62f1035d02ce0c8c5aa62252ff0/Tests/LoggingTests/MDCTest.swift)).

### Multiple log destinations

Finally, the API package will offer a solution to log to multiple `LogHandler`s at the same time through the `MultiplexLogging` facility. Let's assume you have two `LogHandler` implementations `MyConsoleLogger` & `MyFileLogger` and you wish to delegate the log messages to both of them, then the following one-time initialisation of the logging system will take care of this:

```
let multiLogging = MultiplexLogging([MyConsoleLogger().make, MyFileLogger().make])
Logging.bootstrap(multiLogging.make)

```

---

<div class="post-metadata">

**Author:** ![tanner0101](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tanner0101/32/675_2.png) [@tanner0101](https://forums.swift.org/u/tanner0101)\
**Post date:** [January 9, 2019, 8:29pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/2 "2019-01-09T20:29:17Z")

</div>

Awesome proposal. Can't wait to give this package a try. 👍

---

<div class="post-metadata">

**Author:** ![jdmcd](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jdmcd/32/22850_2.png) [@jdmcd](https://forums.swift.org/u/jdmcd)\
**Post date:** [January 9, 2019, 8:50pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/3 "2019-01-09T20:50:37Z")

</div>

I think this looks great! Feels like a complete logging implementation. +1

---

<div class="post-metadata">

**Author:** ![artemredkin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/artemredkin/32/7066_2.png) [@artemredkin](https://forums.swift.org/u/artemredkin)\
**Post date:** [January 9, 2019, 10:28pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/4 "2019-01-09T22:28:18Z")

</div>

Looks great! +1

---

<div class="post-metadata">

**Author:** ![gwynne](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwynne/32/24337_2.png) [@gwynne](https://forums.swift.org/u/gwynne)\
**Post date:** [January 9, 2019, 11:46pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/5 "2019-01-09T23:46:58Z")

</div>

Looks amazing, ship it! 😄

---

<div class="post-metadata">

**Author:** ![royhsu](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/royhsu/32/4589_2.png) [@royhsu](https://forums.swift.org/u/royhsu)\
**Post date:** [January 10, 2019, 3:06am UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/6 "2019-01-10T03:06:09Z")

</div>

Looks great!

Just a small feedback, would it be nicer to use array instead of list in the MetadataValue enum cases?  
It looks much consistent to me because Swift uses Array as the type name.

```swift
public enum MetadataValue {
    case string(String)
    indirect case dictionary(Metadata)
    indirect case array([Metadata.Value])
}

```

---

<div class="post-metadata">

**Author:** ![anandabits](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/anandabits/32/355_2.png) [@anandabits](https://forums.swift.org/u/anandabits)\
**Post date:** [January 10, 2019, 4:14pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/7 "2019-01-10T16:14:31Z")

</div>

Thanks for working on this API! I was way too busy to provide feedback when the first draft was shared so I apologize that this is coming late in the process.

> [@johannesweiss](#):
>
> one global logger, ie. one application-wise global that's always accessible

I really don't think a library should be introducing global logger or the ability to create on on the fly. If an application wants to use scoped or local loggers it is more difficult to enforce that policy when these facilities are present and trivial to use. I recommend rethinking the approach to supporting global and ad-hoc loggers such that you make it relatively easy for an application to configure that if desired while not requiring every application using this API to make these available to all application code.

> [@johannesweiss](#):
>
> The `logLevel` property as well as the `trace` , `debug` , `info` , `warning` , and `error` methods are probably self-explanatory.

The `logLevel` property appears to be present to configure the level at which the logger will emit messages. Is that correct? It seems like a really bad idea to expose this as a mutable property on a logger that is passed around, and a _really_ bad idea to expose this on a global logger. Every application I have worked on wants to configure log levels centrally (per logging context) and only allow other code to produce messages (without being able to modify the level at which the logger emits those messages).

> [@johannesweiss](#):
>
> public var metadata: Logging.Metadata? { get set }

It isn't clear to me why this dictionary is optional. I think there should be pretty strong motivation for making it optional.

> [@johannesweiss](#):
>
> public subscript(metadataKey metadataKey: String) -\> Logging.Metadata.Value? { get set }

It isn't clear to me why we need both the subscript _and_ a mutable property. Given the `metadataKey` label on the logger it is actually shorter to just use the dictionary directly:

```swift
logger[metadataKey: "foo"] = "something"
logger.metadata["foo"] = "something"

```

> [@johannesweiss](#):
>
> The logging metadata is of type `typealias Logging.Metadata = [String: Metadata.Value]` where `Metadata.Value` is the following `enum` allowing nested structures rather than just `String` s as values.
> 
> ```swift
> public enum MetadataValue {
> case string(String)
> indirect case dictionary(Metadata)
> indirect case list([Metadata.Value])
> }
> 
> ```

Why are these enum cases `indirect`? The don't need to be since the recursive storage is already heap allocated (by the collections).

I think this design is missing support for a crucial kind of metadata, at least at the individual entry level. The logging system in the library I work on right now supports including dumps of contextually relevant values along with the log entry using a `CustomLogStringConvertible` protocol. (I think something like this was discussed in the previous thread)

```swift
protocol CustomLogStringConvertible {
    var logDescription: String { get }
}

```

Individual types are expected to ensure that the `logDescription` does not contain any sensitive data. If you include this protocol in the library itself then the `MetadataValue` enum can include a `case custom(CustomLogStringConvertible)`. The call site syntax we use is as follows:

```swift
logger.debug("preparing to start live network request...", request, otherRelevantValue)
logger.debug("something went wrong with my request...", request, error)

```

We use variadics here which do not support `@autoclosure`. This isn't a problem because the intent is to capture contextually relevant values _that already exist_. We don't access the `logDescription` unless the log message is actually emitted. A more conservative design could use N overloads to get the same syntax or could use `@autoclosure` and require an array at the call site:

```swift
logger.debug("preparing to start live network request...", request, otherRelevantValue)
logger.debug("something went wrong with my request...", request, error)

```

Your `Logging.Metadata` design currently requires all metadata to have keys. Our system initially required keys for the contextual values but that turned out to be redundant as the `logDescription` output is self-describing.

One way to combine these approaches would be to define a `Logging.messageContextMetadataKey: String` constant and provide overloads which package up the "keyless" entry metadata into a `MetadataValue.list` that is stored in that constant key. The other approach would be to just allow individual log entries to receive a `MetadataValue` instead of requiring a keyed `Logging.Metadata` (which is already supported by the `MetadataValue` enum and would still be available when desired).

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [January 11, 2019, 4:21am UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/8 "2019-01-11T04:21:06Z")

</div>

> [@johannesweiss](#):
>
> ### Supported logging models
> 
> This API intends to support a number programming models:
> 
> 1. explicit logger passing (see [`ExplicitLoggerPassingExample.swift`](https://github.com/weissi/swift-server-logging-api-proposal/blob/master/Sources/Examples/ExplicitLoggerPassingExample.swift))
> 2. one global logger (see [`OneGlobalLoggerExample.swift`](https://github.com/weissi/swift-server-logging-api-proposal/blob/master/Sources/Examples/OneGlobalLoggerExample.swift))
> 3. one logger per sub-system (see [`LoggerPerSubsystemExample.swift`](https://github.com/weissi/swift-server-logging-api-proposal/blob/master/Sources/Examples/LoggerPerSubsystemExample.swift))

The links to the examples hit a 404 since the examples have moved a little bit.  
The examples are all in [https://github.com/weissi/swift-server-logging-api-proposal/tree/master/Sources/ExampleUsage](https://github.com/weissi/swift-server-logging-api-proposal/tree/master/Sources/ExampleUsage)

I'll reply with a review / answering the requested questions shortly 🙂

---

<div class="post-metadata">

**Author:** ![dhoepfl](https://avatars.discourse-cdn.com/v4/letter/d/b5ac83/32.png) [@dhoepfl](https://forums.swift.org/u/dhoepfl)\
**Post date:** [January 11, 2019, 1:15pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/9 "2019-01-11T13:15:26Z")

</div>

+1  
Nice, simple API.

Let’s make it complicated 😊 :

## Unfiltered Log Function

I’d like to see a log function that logs at a given log level, ignoring the log level configured for the `Logger` instance.

```swift
func unfiltered(level: LogLevel, message: String, metadata: Logging:Metadata? = nil, error: Error? = nil)

```

(possible names: `log`, `forced`, `unfiltered`, ...)

### Rational

Experience tought me that version information is the most important information in the log (but there are environments where they run a zero error policy, so one cannot use that log level). It is hard to ensure the version logging takes place before the log level is set, e.g. if versioned modules get dynamically loaded.

## Hierarchical Loggers

`Logger` should include `func make(_ label: String) -> Logger`.

### Rational

Basic logger handlers could just forward that call to `Logging.make` but advanced loggers might store and show the chain of loggers in the log (and inherit/limit log levels).

You still can create root loggers (the "thin air case") but if you know your parent, you should have a way to make use of it.

---

<div class="post-metadata">

**Author:** ![anandabits](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/anandabits/32/355_2.png) [@anandabits](https://forums.swift.org/u/anandabits)\
**Post date:** [January 11, 2019, 3:58pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/10 "2019-01-11T15:58:09Z")

</div>

> [@dhoepfl](#):
>
> `Logger` should include `func make(_ label: String) -> Logger` .

I forgot to mention this in my post yesterday. I use derived loggers to create a new logging context when initializing a subsystem and strongly recommend including this capability. You should also be able to pass additional `Logging.Metadata` that the derived logger will use. Ideally the derived logger would not expose the initial metadata to users of the derived logger and would not allow them to override the initial metadata, therefore always including the initial metadata in log messages emitted by the derived logger.

This brings to mind a couple questions about the behavior of the `Logger` struct more generally. Specifically, does the metadata get passed by value? If I pass a logger to a function and that function writes to metadata will that modified metadata be visible to me when the function returns (and used in my subsequent log statements)? I certainly hope not, but it isn't clear.

Usually metadata is relevant only in a specific context. Mutable metadata will encourage people to set metadata and then remove it (possibly using `defer`) but there is no guarantee they do. An alternative design would be to _only_ expose the ability to derive new loggers and not expose metadata directly at all. Instead, when a new logging context (with new metadata) is required you just derive a new logger and the original logger (and all of its metadata) remains unmodified. Unless there is really strong motivation behind the need for mutable metadata I think this design should be seriously considered.

---

<div class="post-metadata">

**Author:** ![neallester](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/neallester/32/4528_2.png) [@neallester](https://forums.swift.org/u/neallester)\
**Post date:** [January 11, 2019, 4:52pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/11 "2019-01-11T16:52:57Z")

</div>

> [@dhoepfl](#):
>
> `Logger` should include `func make(_ label: String) -> Logger` .

Along the same line, it should be possible to provide metadata at bootstrap time which is then included with every Logger dispensed.

---

<div class="post-metadata">

**Author:** ![Ponyboy47](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ponyboy47/32/2605_2.png) [@Ponyboy47](https://forums.swift.org/u/Ponyboy47)\
**Post date:** [January 11, 2019, 9:09pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/12 "2019-01-11T21:09:16Z")

</div>

Would it make sense to include an `init`/`make` that accepts parameters for things like `level` and `metadata`?

Yes it is easy to create your logger and then modify `level`/`metadata` yourself, but why not just do it at initialization?

ie:

```swift
let logger = Logging.make("com.example.app", level: .warn, metadata: requestID)

```

instead of:

```swift
let logger = Logging.make("com.example.app")
logger.level = .warn
logger.metadata = requestID

```

Also, did you consider making `Logging.bootstrap` accept a variadic? So rather than needing to explicitly create a `MultiplexLogging` object, you can just `Logging.bootstrap(Logger1.make, Logger2.make)`

---

<div class="post-metadata">

**Author:** ![Ponyboy47](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ponyboy47/32/2605_2.png) [@Ponyboy47](https://forums.swift.org/u/Ponyboy47)\
**Post date:** [January 11, 2019, 9:33pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/13 "2019-01-11T21:33:10Z")

</div>

Many other languages provide the ability to support custom logging levels. I see that the `Logging.Level` enum uses an `Int` raw type, would anyone else think it worthwhile to support custom logging levels?

ie:

```swift
public enum Level {
    public var rawValue: Int {
        switch self {
            case .custom(let level): return level
            case .trace: return 0
            case .debug: return 10
            case .info: return 20
            case .warning: return 30
            case .error: return 40
        }
    }
    case trace, debug, info, warning, error
    case custom(Int)
}

```

Or:

```swift
public struct Level: RawRepresentable, ExpressibleByIntegerLiteral {
    public let rawValue: Int

    public static let trace: Level = 0
    public static let debug: Level = 10
    public static let info: Level = 20
    public static let warning: Level = 30
    public static let error: Level = 40
    public static func custom(_ level: Int) -> Level {
        return Level(rawValue: level)
    }

    public init(integerLiteral value: Int) {
        self.init(rawValue: value)
    }

    public init(rawValue: Int) {
        self.rawValue = rawValue
    }
}

```

Pros:

- Developers can make their own logging levels for increase control over their logs
- `struct` version can easily conform to `Comparable` also

Cons:

- Added complexity the `Level` code
- The `struct` version is not quite as easy to `switch` over as the `enum`
- The `enum` version now has to compute the `rawValue` every time for its `Comparable` conformance

---

<div class="post-metadata">

**Author:** ![anandabits](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/anandabits/32/355_2.png) [@anandabits](https://forums.swift.org/u/anandabits)\
**Post date:** [January 11, 2019, 10:00pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/14 "2019-01-11T22:00:21Z")

</div>

Another option is to make `Logger` generic over a custom `Comparable` log level type that has factories for the standard levels (i.e. `debug`, `error`, etc)

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 11:39am UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/15 "2019-01-14T11:39:42Z")

</div>

> [@royhsu](#):
>
> Just a small feedback, would it be nicer to use array instead of list in the MetadataValue enum cases?  
> It looks much consistent to me because Swift uses Array as the type name.
> 
> ```swift
> public enum MetadataValue {
> case string(String)
> indirect case dictionary(Metadata)
> indirect case array([Metadata.Value])
> }
> 
> ```

Thank you! Yes, that was an oversight. I'll make a small PR later fixing small things like this.

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 11:44am UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/16 "2019-01-14T11:44:41Z")

</div>

> [@ktoso](#):
>
> The links to the examples hit a 404 since the examples have moved a little bit.  
> The examples are all in [https://github.com/weissi/swift-server-logging-api-proposal/tree/master/Sources/ExampleUsage](https://github.com/weissi/swift-server-logging-api-proposal/tree/master/Sources/ExampleUsage)
> 
> I'll reply with a review / answering the requested questions shortly 🙂

Thank you!

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 12:29pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/17 "2019-01-14T12:29:05Z")

</div>

> [@anandabits](#):
>
> Thanks for working on this API! I was way too busy to provide feedback when the first draft was shared so I apologize that this is coming late in the process.

Thanks for still sending feedback. We had two discussion threads before ([here](https://forums.swift.org/t/logging/16027) and [there](https://forums.swift.org/t/discussion-server-logging-api/18834)). Tomer and I compiled an API out of that which should allow all existing solutions to migrate to this. This is not the final version of the implementation but the rough shape of _this proposal_ is more or less final and the outcome of previous discussion threads. There are a things I'm personally not very happy with - some of which you mention in your post - but I believe logging is so fundamental that we need to come up with an API that everybody can use and adopt without too much friction. So asking everybody to re-architect would not work I believe. Therefore, we propose the current version to the community and the SSWG will decide whether to move along with this proposal or not.

But: If accepted into the SSWG's process, the resulting package will be a regular open-source SwiftPM package and the code will evolve over time. So implementation issues and details can be addressed whilst the proposed package is going through the Sandbox/Incubation stages of the SSWG process.

> [@anandabits](#):
>
> > one global logger, ie. one application-wise global that's always accessible
> 
> I really don't think a library should be introducing global logger or the ability to create on on the fly. If an application wants to use scoped or local loggers it is more difficult to enforce that policy when these facilities are present and trivial to use. I recommend rethinking the approach to supporting global and ad-hoc loggers such that you make it relatively easy for an application to configure that if desired while not requiring every application using this API to make these available to all application code.

I agree with your sentiment and share your opinion. However, after talking to many people and going through two [discussion](https://forums.swift.org/t/logging/16027) [threads](https://forums.swift.org/t/discussion-server-logging-api/18834) it doesn't appear like we can force the ecosystem into some logging model without risking the API package not being widely adopted which is the main important point of an API package. One of the popular [packages](https://github.com/IBM-Swift/LoggerAPI/blob/master/Sources/LoggerAPI/Logger.swift) for example only offers a global logging facility. Other people want one logger per class and configure those with different logging levels so the ability of being able to create loggers is something we need at this stage I believe.

> [@anandabits](#):
>
> > The `logLevel` property as well as the `trace` , `debug` , `info` , `warning` , and `error` methods are probably self-explanatory.
> 
> The `logLevel` property appears to be present to configure the level at which the logger will emit messages. Is that correct? It seems like a really bad idea to expose this as a mutable property on a logger that is passed around, and a _really_ bad idea to expose this on a global logger. Every application I have worked on wants to configure log levels centrally (per logging context) and only allow other code to produce messages (without being able to modify the level at which the logger emits those messages).

Well, for a value-typed `LogHandler`s that are explicitly passed around I think this is all good. For reference-typed `LogHandler`s, especially global ones I do agree with your sentiment. I know we're squaring the circle here and trying to get many different ways of logging into one API but we can't ignore what's there right now and the requirements people have. We need to rely on libraries not messing with the log level etc. My hope is that over time best practices will be established and the ecosystem itself evolves and we can evolve the APIs to capture the best practices more precisely.

> [@anandabits](#):
>
> > public var metadata: Logging.Metadata? { get set }
> 
> It isn't clear to me why this dictionary is optional. I think there should be pretty strong motivation for making it optional.
> 
> ![](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/40/684_1.png) johannesweiss:

It's not, that's a typo in the markdown only. I'll fix later today along with other typos. In the first proposal it was indeed optional because we were toying with the idea of allowing `LogHandler`s to not support metadata at all. But I think that was a bad idea and therefore we removed it.

> [@anandabits](#):
>
> > public subscript(metadataKey metadataKey: String) -\> Logging.Metadata.Value? { get set }
> 
> It isn't clear to me why we need both the subscript _and_ a mutable property. Given the `metadataKey` label on the logger it is actually shorter to just use the dictionary directly:

The dictionary is meant as the primary way of storing metadata in your program, ie. the `LogHandler` may store it however it wants (maybe a dictionary, maybe a tree, whatever). The property that exposes the whole metadata as a dictionary and it's there to support MDC (mapped metadata context) where the context can be saved/restored to/from a thread-local variable. Again, personally not a fan of that but it's a common request that you can use a thread-local to store the metadata and when crossing threads you'd take the metadata out, ship it over to another thread and install it there...

> [@anandabits](#):
>
> Why are these enum cases `indirect` ? The don't need to be since the recursive storage is already heap allocated (by the collections)

Thanks, just by accident, will fix in code.

> [@anandabits](#):
>
> ```swift
> protocol CustomLogStringConvertible {
> var logDescription: String { get }
> }
> 
> ```
> 
> Individual types are expected to ensure that the `logDescription` does not contain any sensitive data.

I think that might turn out to be a good idea. If this proposal is accepted, I'd be more than interested to work with you on getting that into the codebase before we leave the Sanbox stage. At some point obviously we'll have a public API so breaking changes will be more problematic. But in the early Sandbox stage we won't immediately declare version 1.0.0, so I'd say this is a more than acceptable change. This feels inline with the spirit of the proposal, so we don't need to propose a whole new version for that.

> [@anandabits](#):
>
> Your `Logging.Metadata` design currently requires all metadata to have keys. Our system initially required keys for the contextual values but that turned out to be redundant as the `logDescription` output is self-describing.
> 
> One way to combine these approaches would be to define a `Logging.messageContextMetadataKey: String` constant and provide overloads which package up the "keyless" entry metadata into a `MetadataValue.list` that is stored in that constant key. The other approach would be to just allow individual log entries to receive a `MetadataValue` instead of requiring a keyed `Logging.Metadata` (which is already supported by the `MetadataValue` enum and would still be available when desired).

That is also something I'd like to talk about should this proposal be accepted.

> [@anandabits](#):
>
> This brings to mind a couple questions about the behavior of the `Logger` struct more generally. Specifically, does the metadata get passed by value?

It depends on the `LogHandler`, squaring the circle...

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 12:31pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/18 "2019-01-14T12:31:34Z")

</div>

> [@dhoepfl](#):
>
> ## Unfiltered Log Function
> 
> I’d like to see a log function that logs at a given log level, ignoring the log level configured for the `Logger` instance.

I consider this a small enough change that I would encourage a PR if this proposal gets accepted.

> [@dhoepfl](#):
>
> ## Hierarchical Loggers
> 
> `Logger` should include `func make(_ label: String) -> Logger` .

This is borderline but I think we could make this a PR or if it's too much of a change propose this as a follow-up change. What do you think?

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 12:32pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/19 "2019-01-14T12:32:35Z")

</div>

> [@neallester](#):
>
> > `Logger` should include `func make(_ label: String) -> Logger` .
> 
> Along the same line, it should be possible to provide metadata at bootstrap time which is then included with every Logger dispensed.

I think this is a worthwhile small addition that can be proposed as a PR should this proposal get accepted as it's within the spirit of this proposal.

---

<div class="post-metadata">

**Author:** ![johannesweiss](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/johannesweiss/32/38183_2.png) [@johannesweiss](https://forums.swift.org/u/johannesweiss)\
**Post date:** [January 14, 2019, 12:33pm UTC](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375/20 "2019-01-14T12:33:51Z")

</div>

> [@Ponyboy47](#):
>
> Many other languages provide the ability to support custom logging levels. I see that the `Logging.Level` enum uses an `Int` raw type, would anyone else think it worthwhile to support custom logging levels?

I think this can make sense but also has serious downsides, the ones you mentioned and more opportunity for abuse. We have deliberately decided not to provide this functionality.

[Next page](https://forums.swift.org/t/feedback-server-logging-api-with-revisions/19375.md?page=2)
