Skip to content
This repository has been archived by the owner on Apr 23, 2021. It is now read-only.

Final API proposal - Baggage + Context #34

Merged
merged 14 commits into from
Sep 30, 2020

Conversation

ktoso
Copy link
Collaborator

@ktoso ktoso commented Sep 14, 2020

This is a large PR, but in it I hope to finalize the API of this package.

It first and foremost acknowledges issues that we all felt already with the Carrier design -- it's too "wide open" and by being such, it actually hurts composability of libraries.


Short discussion:

In general we need to balance a number of needs here:

  • some people ALWAYS want to pass a Logger around
  • libraries generally "just" need a way to get a logger passed to them
  • many libraries, esp client libraries, need context / baggage information to propagate distributed tracing information
  • if those are used, a logger MUST be tied to and be passed along with such baggage
  • baggage MUST NOT contain a logger; baggage items are intended to be serialized and sent across process/network boundaries

As such I propose the following simplification of the APIs:

Modules & Types

The package: swift-context

The baggage module:

  • import Baggage
  • dependencies: none
    • main type: Baggage - the "carrier" of baggage items; it is used by instrumentation and tracing to instrument systems

The only user of this I really see being NIO, the team said it would not want to depend on SwiftLog.

And finally, tieing logging and context together in:

  • import BaggageContext
  • dependencies: SwiftLog
    • Context
    • A Context is EXACTLY Logger + Baggage, nothing more, nothing less

We also offer:

  • BaggageProtocol in module Baggage
    • any (framework) context MAY conform to this if it wants .traceID be accessible DIRECTLY on it's context
    • a baggage protocol MUST allow setting a baggage and its properties

and

  • ContextProtocol in module BaggageContext
    • allows access to .baggage but does NOT enforce allowing to mutate it
    • frameworks SHOULD conform to this type and MAY offer the baggage as mutable property if it makes sense for them
      • e.g. the framework's context is a struct and has no worries about concurrently mutating / sharing the baggage with others

There are NO other types. The EventLoop we really tried hard to find a place for but we quickly devolve into complexities of various libraries wanting to accept or not wanting to accept it etc. The "...Carriers" design falls flat #28 on delivering good compositionality, in general remaining true to the:

Liberties constrain, constraints liberate.

In this proposal we choose MORE constraints what a context and baggage is, and thus end up with a more composable API.

The short version is:

  • Library APIs SHOULD ACCEPT ContextProtocol
  • Frameworks SHOULD CONFORM to ContextProtocol
  • End users SHOULD keep passing along whichever context they are given

Optional:

  • The BaggageProtocol exists to allow certain types (framework contexts) get the .traceID properties via extensions defined in other libraries
  • The BaggageProtocol MUST NOT be required when accepting a context value

And finally:

  • The most simple APIs MAY simply accept a Baggage struct, if they require no logging.
  • Baggage MAY be stored ONLY in especial cases, such as NIO offering it on it's Context in order to pass it between handlers
    • Storing a baggage in such way DOES NOT imply having to conform to BaggageProtocol as these are separate concerns -- "passing around things" (the baggage struct) vs. "be a nice DSLy type where keys get autocompleted" (the protocol conformance)

As framework author e.g. "http server" or similar, where a widely used context object exists that holds many many values already (e.g. Vapor, Lambda Runtime)

  • At minimum conform your MyFrameworkContext to ContextProtocol and ensure the logger is implemented correctly (documentation on the property explains how)
  • If able to, and it suits the style of your framework: optionally conform MyFrameworkContext to BaggageProtocol
    • this is optional and needs careful consideration; it makes your context "seem as-if" it was a Baggage itself.
    • this means that when other libraries do extension BaggageProtocol { var traceID: ... {} } this traceID will be accessible on YOUR framework context directly: myFrameworkContext.traceID rather than myFrameworkContext.baggage.traceID
    • this has potential for naming conflicts but some libraries may like this style
    • TODO: should we remove this protocol or not? It allows other types that contain a baggage to act as if they were one, it's a flexible convenient trick, at the cost of potential minor confusion why these properties are available?
  • As a framework you are likely mostly dependent on "your" context, and as such pass it explicitly, using it's strict type
    • If parts of the API may have to be called into from other libraries which do not know about your framework types -- accept a ContextProtocol -- i.e. be "wide" in set of accepted contexts when possible

As library author of a library which does not have it's own "context" and/or is mostly a client e.g. HTTPClient:

  • Accept ContextProtocol in your API functions
    • following the parameter positioning explained in the README of this project
  • never "store" or "hide" contexts (!) - it will cause confusion and pain in the years moving forward as we build more tooling and other features around context passing.

As library/framework user

  • "keep passing along the context you were given"

    • most of the time you will be invoked by a framework (http server, some background task scheduler, other runtime) which already will have a context object
    • always simply "pass it along"
  • If unable to get a context, prefer using the .TODO("Not sure where to get a context from?") rather than the .background factory for the context

    • the TODO is explicit in your intent that you should have passed a context, but were unable to
    • the TODO context may be made to CRASH at runtime if you want to enforce your project always passes context when it is pushed to production, see comments for details
  • Define any additional baggage keys using private enum MyKey: Baggage.Key { ... } and the extension BaggageContext { var my: MyKey.Value { ... } pattern.

    • those values are intended to be passed through processess and nodes (!), if they are not meant to do so, you may want to set logger metadata instead.


The code is mostly "shuffling around" and naming, and making sure everything "fits".

The general "what is possible to express" remains the same and wider -- I made sure this will fit existing context types such as Vapor, soco (AWSSDK), NIO, HTTPClient, Redis. Yes, it does mean passing the event loop explicitly and I really can't find a truely good way to solve it. We end up in arbitrarily tying things together which should not be together if we try to do so.

Please have a look everyone: @adam-fowler @tomerd @Lukasa @slashmo @fabianfett @tanner0101 @pokryfka

@ktoso ktoso requested a review from slashmo September 14, 2020 12:39
@ktoso ktoso force-pushed the simple-is-good-proposal branch 2 times, most recently from cd44924 to 7be716f Compare September 14, 2020 12:43
Package.swift Show resolved Hide resolved
Package.swift Show resolved Hide resolved
Package.swift Show resolved Hide resolved
Sources/Baggage/Baggage.swift Outdated Show resolved Hide resolved
Sources/BaggageContext/Context.swift Outdated Show resolved Hide resolved
Sources/BaggageContext/Context.swift Outdated Show resolved Hide resolved
Copy link

@tanner0101 tanner0101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 overall this seems a lot better, thanks!

Sources/Baggage/Baggage.swift Outdated Show resolved Hide resolved
Sources/BaggageContext/Context.swift Outdated Show resolved Hide resolved
Sources/Baggage/Baggage.swift Outdated Show resolved Hide resolved
@tomerd
Copy link

tomerd commented Sep 14, 2020

over much nicer and clearer. some clarification question and feedback inline.

@ktoso ktoso changed the title Final API proposal: Less is More / Simple is Better Final API proposal - Baggage + Context Sep 14, 2020
///
/// This allows frameworks and library authors to offer APIs which compose more easily.
/// Please refer to the "Reference Implementation" notes on each of the requirements to know how to implement this protocol correctly.
public protocol Context {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bringing this to the main PR...

What's the thought on making this more tailored to the intended semantics of naming this more like TracingContext, LoggingContext, or something similar?

A lot of the "confusion" I've had is that Context is very generic, and we're writing in several places that this is not a general execution context object and more specifically a "execution metadata" context with baggage metadata being passed around.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the "confusion" I've had is that Context is very generic, and we're writing in several places that this is not a general execution context object and more specifically a "execution metadata" context with baggage metadata being passed around.
@Mordil

I agree ☝️ I'd also like to throw InstrumentationContext into the mix, as it will be used for many kinds of instrumentation, not only tracing.

LoggingContext, in my opinion, sounds more like something internal to swift-log.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I do like InstrumentationContext quite a lot

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @ktoso

Copy link
Collaborator Author

@ktoso ktoso Sep 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey yeah, thanks for the thought - it's a valid concern. I really don't know what a different good name would be other than BaggageContext.

  • LoggingContext is problematic IMHO but if people really love it we could... it does after all concern it self a lot about binding the baggage to logging.
  • InstrumentationContext pulls in a word into this library that is not from this library -- we can't do that IMHO. We have to be very strict about the words we use and the word instrument is not defined in this library, and never will be really. It just so happens that instrumentation is what uses this, but this is valuable even if instruments didn't exist...
  • TracingContext is a very specific thing and I really don't want to tie this to tracing per se. It also is the exact name in a few tracing APIs which makes people think it's the same (but it's not)
  • Some tracing APIs have called this or similar things PropagationContext but actually they're now moved back and just call it Baggage (huh)
  • Baggage is our "container" so... this really can only be Context or BaggageContext IMHO.

Since we want to have this "basically everywhere" a least long name is preferable thus Context...

If I may, I'd propose either sticking with Context or BaggageContext, with a mild/strong preference to Context as we're likely to secure a proper namespace for it as swift-context, and there are teams internally in Apple which know this concept as "swift context" as well as it relating directly to what people in Go use such things for -- and that the general term is known as "context propagation".

// cc @tomerd @Lukasa @slashmo

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the concern about calling it general-purpose, but having it be the context used for propagation. I think the confusion lies in there being different interpretations of "Context". With this library, we only want to expose a context used for propagation, not one (mis-)used e.g. for dependency injection. I'm +1 on Baggage and BaggageContext as type names as they have prior art in the tracing, which relies on context propagation.

module swift-baggage-context / import SwiftBaggageContext
@ktoso

What I'd suggest for the module name instead of prefixing with Swift is to make it extra clear what this is about and call it ContextPropagation. The package/repo name could then be swift-context-propagation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chiming in Adam, the package naming is also important indeed. I would like to avoid "plural names" though. There technically exists precedence for Swift... prefixed modules because SwiftProtobuf does so, but yes you're right it's not a pattern we follow.

I very much like @slashmo proposal where the package name is more "functional" rather than just repeating the same words again, we brainstormed this a bit more and I would like to propose the following which I think addresses all concerns:

  • .package("https://github.com/apple/swift-context-propagation.git", from: "1.0.0")
    • module name: import ContextPropagation
    • includes protocol BaggageContext { Baggage Logger } and DefaultContext, and friends (the log handler etc)
    • depends on SwiftLog
  • .package("https://github.com/apple/swift-context-propagation-baggage.git", from: "1.0.0")
    • libraries which do not want to depend on logging can use this if they still need to carry a baggage somehow (e.g. NIO this way is able to not have to depend on logging, but offer context.baggage itself)
    • module name: import ContextPropagationBaggage
    • includes struct Baggage and the related key types etc
    • zero dependencies

Please note that repository names are subject to input from Apple teams and management etc, so I can't commit to those, but those would seem a very good balanced tradeoff. Not squatting the general term "context" and not looking weird "swift-baggage" which is a bit "what does this repo even do?" for the uninitiated. It also solves our module name != main type requirement mentioned up-thread here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

via @Mordil

Are we still sure there needs to be two repos instead of just one?

Yes, I've quadruple checked this today and consulted with Cory as well.

If we don't, then there's an artificial link between a "i dont want to pull logging" projects that would fetch and enforce a version requirement even though they don't use logging in any module.

Imagining such package:

    dependencies: [
        .package(url: the repo),
    ],
    targets: [
        .target(name: "SotoCore", dependencies: [
            .product(name: "Baggage", package: "swift-context"), // "i dont want logging"
        ]),
    ] 

we're STILL both fetching and including in version resolution the logging package:

-> % swift package update
Fetching https://github.com/ktoso/gsoc-swift-baggage-context.git
Fetching https://github.com/apple/swift-log.git
Cloning https://github.com/apple/swift-log.git
Resolving https://github.com/apple/swift-log.git at 1.4.0
Cloning https://github.com/ktoso/gsoc-swift-baggage-context.git
Resolving https://github.com/ktoso/gsoc-swift-baggage-context.git at simple-is-good-proposal

A project which does not use logging at any point (e.g. nio but there can be others), should not force that artificial link between logging and the package. It does not matter that such package does not depend on the module that actually depends on logging for these parts; it helps that logging would not be in the binary result, but for purposes of package management it makes no difference and the logging package still is considered.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that’s a little disappoint that SwiftPM does that. I thought it didn’t consider dependencies not used in the target graph.

Ok then. I’m still on board 💯

Copy link
Collaborator Author

@ktoso ktoso Sep 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get those ✅ rolling in 🏁 (I hope?)
(Feel free to confirm a review even if not repo owner/comitter)

@Mordil
Copy link

Mordil commented Sep 17, 2020

Other than my latest comment, I think I'm on board with this PR 💯

@ktoso ktoso force-pushed the simple-is-good-proposal branch from 9b2d9a1 to 38374be Compare September 18, 2020 09:11
@ktoso
Copy link
Collaborator Author

ktoso commented Sep 18, 2020

Thank you for your time and reviews @Mordil !

@ktoso
Copy link
Collaborator Author

ktoso commented Sep 18, 2020

Thank you everyone on this PR and privately and internally for the feedback, I really feel we're on the last few adjustments to the API and we'll be able to call if final!

Changes in this batch:

  • Based on more discussions background is now topLevel, and I really like it; it is "weird enough" to look bad if used in line randomly (it's supposed to look bad) and conveys enough good meaning: 38374be
  • I adjusted how DefaultContext (and any Context conformance) deals with updating the metadata

This has large (good for applications which log a lot) performance implications which can be seen here (and in the benchmarks added in f0f91b7):

Screen Shot 2020-09-18 at 18 17 27

Next up:

  • I look a bit more at the SwiftUI APIs and see if the subscript we expose can be improved a bit... It's a "dont touch this unless you're a developer of a key" so we can't use _ there... but it's not for general use. I don't know if some fancy ideas like we do with Attributes in Swift Tracing and keypaths like SwiftUI does can help here but I'll investigate.
    • i fear it might not work out because it might need Swift 5.2+ features

@ktoso
Copy link
Collaborator Author

ktoso commented Sep 23, 2020

All things applied...

I also looked at @adam-fowler's adoption inside Soco and other projects and am confident we've arrived at something very good.

We're aiming to settle down the discussions this week and being the new repos shortly.
Thank you everyone for your patience and reviews, hopefully we're "done" here soon.

rather than each time, which would lead to terrible logging performance
if many log statements are made. Generally, there are fewer baggage
changes than there are log statements so this tradeoff makes sense.

The logger modification amortizes over time if it is not changed again
and again.
yet keeps the "huh?" effect of "this likely is wrong" if it appears in
the middle of a codebase
@ktoso ktoso force-pushed the simple-is-good-proposal branch from 33bc17c to 2e7a9ec Compare September 23, 2020 15:50
Copy link
Owner

@slashmo slashmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the countless number of iterations on this 👌

To bring more attention to the last remaining bit, I'm also quoting @ktoso comment regarding naming and package structure. In my opinion, this is a good compromise.

(Requesting changes because this, or an alternative naming scheme, has not been added yet)

  • .package("https://github.com/apple/swift-context-propagation.git", from: "1.0.0")
    • module name: import ContextPropagation
    • includes protocol BaggageContext { Baggage Logger } and DefaultContext, and friends (the log handler etc)
    • depends on SwiftLog
  • .package("https://github.com/apple/swift-context-propagation-baggage.git", from: "1.0.0")
    • libraries which do not want to depend on logging can use this if they still need to carry a baggage somehow (e.g. NIO this way is able to not have to depend on logging, but offer context.baggage itself)
    • module name: import ContextPropagationBaggage
    • includes struct Baggage and the related key types etc
    • zero dependencies

Sources/Baggage/BaggageKey.swift Show resolved Hide resolved
Sources/Baggage/Baggage.swift Outdated Show resolved Hide resolved
Sources/BaggageContext/Context.swift Show resolved Hide resolved
Sources/BaggageContext/Context.swift Show resolved Hide resolved
@ktoso
Copy link
Collaborator Author

ktoso commented Sep 24, 2020

I'm waiting for some feedback from the SPM team about the package structure, once I get that I think we're ready to go here.
Planning to land this end of this week.

@ktoso
Copy link
Collaborator Author

ktoso commented Sep 30, 2020

Okey folks, so it seems we'll need two repositories.

The exact package and module naming we have to run through some reviews...
Until then I'd like to merge this so we're out of the limbo on the types themselves :-)

Copy link
Owner

@slashmo slashmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👌

@slashmo slashmo merged commit dbcbed9 into slashmo:main Sep 30, 2020
@slashmo slashmo added this to the 0.5.0 milestone Sep 30, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
8 participants