├── .editorconfig ├── .github ├── release.yml └── workflows │ ├── main.yml │ ├── pull_request.yml │ └── pull_request_label.yml ├── .gitignore ├── .licenseignore ├── .spi.yml ├── .swift-format ├── Benchmarks ├── .gitignore ├── Benchmarks │ └── ServiceContextBenchmarks │ │ ├── Benchmarks.swift │ │ ├── PassAroundStaticStrings.swift │ │ └── Shared.swift ├── Package.swift └── Thresholds │ ├── 5.10 │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json │ ├── 5.9 │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json │ ├── 6.0 │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json │ ├── 6.1 │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json │ ├── nightly-main │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json │ └── nightly-next │ ├── TracingBenchmarks.PassAroundStaticStringsLarge.p90.json │ └── TracingBenchmarks.PassAroundStaticStringsSmall.p90.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── Sources ├── InstrumentationBaggage │ ├── CompatibilityShims.swift │ └── Docs.docc │ │ └── index.md └── ServiceContextModule │ ├── Docs.docc │ └── index.md │ ├── ServiceContext.swift │ └── ServiceContextKey.swift ├── Tests └── ServiceContextTests │ └── ServiceContextTests.swift └── dev └── git.commit.template /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: SemVer Major 4 | labels: 5 | - ⚠️ semver/major 6 | - title: SemVer Minor 7 | labels: 8 | - 🆕 semver/minor 9 | - title: SemVer Patch 10 | labels: 11 | - 🔨 semver/patch 12 | - title: Other Changes 13 | labels: 14 | - semver/none 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | schedule: 7 | - cron: "0 8,20 * * *" 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit tests 12 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 13 | with: 14 | linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 15 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 16 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 17 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 18 | linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 19 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 20 | 21 | benchmarks: 22 | name: Benchmarks 23 | # Workaround https://github.com/nektos/act/issues/1875 24 | uses: apple/swift-nio/.github/workflows/benchmarks.yml@main 25 | with: 26 | benchmark_package_path: "Benchmarks" 27 | 28 | static-sdk: 29 | name: Static SDK 30 | # Workaround https://github.com/nektos/act/issues/1875 31 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 32 | 33 | macos-tests: 34 | name: macOS tests 35 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 36 | with: 37 | runner_pool: nightly 38 | build_scheme: swift-service-context-Package 39 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | soundness: 9 | name: Soundness 10 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 11 | with: 12 | license_header_check_project_name: "Swift Service Context" 13 | 14 | unit-tests: 15 | name: Unit tests 16 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 17 | with: 18 | linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 19 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 20 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 21 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 22 | linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 23 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 24 | 25 | benchmarks: 26 | name: Benchmarks 27 | uses: apple/swift-nio/.github/workflows/benchmarks.yml@main 28 | with: 29 | benchmark_package_path: "Benchmarks" 30 | 31 | cxx-interop: 32 | name: Cxx interop 33 | uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main 34 | 35 | static-sdk: 36 | name: Static SDK 37 | # Workaround https://github.com/nektos/act/issues/1875 38 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 39 | 40 | macos-tests: 41 | name: macOS tests 42 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 43 | with: 44 | runner_pool: general 45 | build_scheme: swift-service-context-Package 46 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | name: PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, reopened, synchronize] 6 | 7 | jobs: 8 | semver-label-check: 9 | name: Semantic version label check 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 1 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | persist-credentials: false 17 | - name: Check for Semantic Version label 18 | uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Package.resolved 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | .swiftpm 8 | .vscode/ 9 | .history 10 | -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | **/.gitignore 3 | .licenseignore 4 | .gitattributes 5 | .git-blame-ignore-revs 6 | .mailfilter 7 | .mailmap 8 | .spi.yml 9 | .swift-format 10 | .editorconfig 11 | .github/* 12 | *.md 13 | *.txt 14 | *.yml 15 | *.yaml 16 | *.json 17 | Package.swift 18 | **/Package.swift 19 | Package@-*.swift 20 | **/Package@-*.swift 21 | Package.resolved 22 | **/Package.resolved 23 | Makefile 24 | *.modulemap 25 | **/*.modulemap 26 | **/*.docc/* 27 | *.xcprivacy 28 | **/*.xcprivacy 29 | *.symlink 30 | **/*.symlink 31 | Dockerfile 32 | **/Dockerfile 33 | Snippets/* 34 | dev/git.commit.template 35 | .unacceptablelanguageignore 36 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [ServiceContextModule] 5 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "indentation" : { 4 | "spaces" : 4 5 | }, 6 | "tabWidth" : 4, 7 | "fileScopedDeclarationPrivacy" : { 8 | "accessLevel" : "private" 9 | }, 10 | "spacesAroundRangeFormationOperators" : false, 11 | "indentConditionalCompilationBlocks" : false, 12 | "indentSwitchCaseLabels" : false, 13 | "lineBreakAroundMultilineExpressionChainComponents" : false, 14 | "lineBreakBeforeControlFlowKeywords" : false, 15 | "lineBreakBeforeEachArgument" : true, 16 | "lineBreakBeforeEachGenericRequirement" : true, 17 | "lineLength" : 120, 18 | "maximumBlankLines" : 1, 19 | "respectsExistingLineBreaks" : true, 20 | "prioritizeKeepingFunctionOutputTogether" : true, 21 | "noAssignmentInExpressions" : { 22 | "allowedFunctions" : [ 23 | "XCTAssertNoThrow", 24 | "XCTAssertThrowsError" 25 | ] 26 | }, 27 | "rules" : { 28 | "AllPublicDeclarationsHaveDocumentation" : false, 29 | "AlwaysUseLiteralForEmptyCollectionInit" : false, 30 | "AlwaysUseLowerCamelCase" : false, 31 | "AmbiguousTrailingClosureOverload" : true, 32 | "BeginDocumentationCommentWithOneLineSummary" : false, 33 | "DoNotUseSemicolons" : true, 34 | "DontRepeatTypeInStaticProperties" : true, 35 | "FileScopedDeclarationPrivacy" : true, 36 | "FullyIndirectEnum" : true, 37 | "GroupNumericLiterals" : true, 38 | "IdentifiersMustBeASCII" : true, 39 | "NeverForceUnwrap" : false, 40 | "NeverUseForceTry" : false, 41 | "NeverUseImplicitlyUnwrappedOptionals" : false, 42 | "NoAccessLevelOnExtensionDeclaration" : true, 43 | "NoAssignmentInExpressions" : true, 44 | "NoBlockComments" : true, 45 | "NoCasesWithOnlyFallthrough" : true, 46 | "NoEmptyTrailingClosureParentheses" : true, 47 | "NoLabelsInCasePatterns" : true, 48 | "NoLeadingUnderscores" : false, 49 | "NoParensAroundConditions" : true, 50 | "NoVoidReturnOnFunctionSignature" : true, 51 | "OmitExplicitReturns" : true, 52 | "OneCasePerLine" : true, 53 | "OneVariableDeclarationPerLine" : true, 54 | "OnlyOneTrailingClosureArgument" : true, 55 | "OrderedImports" : true, 56 | "ReplaceForEachWithForLoop" : true, 57 | "ReturnVoidInsteadOfEmptyTuple" : true, 58 | "UseEarlyExits" : false, 59 | "UseExplicitNilCheckInConditions" : false, 60 | "UseLetInEveryBoundCaseVariable" : false, 61 | "UseShorthandTypeNames" : true, 62 | "UseSingleLinePropertyGetter" : false, 63 | "UseSynthesizedInitializer" : false, 64 | "UseTripleSlashForDocumentationComments" : true, 65 | "UseWhereClausesInForLoops" : false, 66 | "ValidateDocumentationComments" : false 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/ServiceContextBenchmarks/Benchmarks.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import Benchmark 16 | 17 | let benchmarks = { 18 | let defaultMetrics: [BenchmarkMetric] = [ 19 | .mallocCountTotal 20 | ] 21 | 22 | Benchmark( 23 | "PassAroundStaticStringsSmall", 24 | configuration: .init( 25 | metrics: defaultMetrics, 26 | scalingFactor: .mega, 27 | maxDuration: .seconds(10_000_000), 28 | maxIterations: 10 29 | ) 30 | ) { benchmark in 31 | runPassAroundStaticStringsSmall( 32 | iterations: benchmark.scaledIterations.upperBound 33 | ) 34 | } 35 | 36 | Benchmark( 37 | "PassAroundStaticStringsLarge", 38 | configuration: .init( 39 | metrics: defaultMetrics, 40 | scalingFactor: .mega, 41 | maxDuration: .seconds(10_000_000), 42 | maxIterations: 10 43 | ) 44 | ) { benchmark in 45 | runPassAroundStaticStringsLarge( 46 | iterations: benchmark.scaledIterations.upperBound 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Benchmarks/Benchmarks/ServiceContextBenchmarks/PassAroundStaticStrings.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | //===----------------------------------------------------------------------===// 16 | // 17 | // This source file is part of the Swift Distributed Tracing open source project 18 | // 19 | // Copyright (c) 2024 Apple Inc. and the Swift Distributed Tracing project 20 | // authors 21 | // Licensed under Apache License v2.0 22 | // 23 | // See LICENSE.txt for license information 24 | // 25 | // SPDX-License-Identifier: Apache-2.0 26 | // 27 | //===----------------------------------------------------------------------===// 28 | 29 | import Benchmark 30 | import ServiceContextModule 31 | 32 | func runPassAroundStaticStringsSmall(iterations: Int) { 33 | var context = ServiceContext.topLevel 34 | // static allocated strings 35 | context[StringKey1.self] = "one" 36 | context[StringKey2.self] = "two" 37 | context[StringKey3.self] = "three" 38 | 39 | for _ in 0.. Int { 34 | take2(context: context) 35 | } 36 | 37 | @inline(never) 38 | func take2(context: ServiceContext) -> Int { 39 | take3(context: context) 40 | } 41 | 42 | @inline(never) 43 | func take3(context: ServiceContext) -> Int { 44 | take4(context: context) 45 | } 46 | 47 | @inline(never) 48 | func take4(context: ServiceContext) -> Int { 49 | 42 50 | } 51 | 52 | enum StringKey1: ServiceContextKey { 53 | typealias Value = String 54 | } 55 | 56 | enum StringKey2: ServiceContextKey { 57 | typealias Value = String 58 | } 59 | 60 | enum StringKey3: ServiceContextKey { 61 | typealias Value = String 62 | } 63 | -------------------------------------------------------------------------------- /Benchmarks/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | //===----------------------------------------------------------------------===// 3 | // 4 | // This source file is part of the SwiftCertificates open source project 5 | // 6 | // Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors 7 | // Licensed under Apache License v2.0 8 | // 9 | // See LICENSE.txt for license information 10 | // See CONTRIBUTORS.txt for the list of SwiftCertificates project authors 11 | // 12 | // SPDX-License-Identifier: Apache-2.0 13 | // 14 | //===----------------------------------------------------------------------===// 15 | 16 | import PackageDescription 17 | 18 | let package = Package( 19 | name: "benchmarks", 20 | platforms: [ 21 | .macOS("14") 22 | ], 23 | dependencies: [ 24 | .package(path: "../"), 25 | .package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.22.0"), 26 | ], 27 | targets: [ 28 | .executableTarget( 29 | name: "TracingBenchmarks", 30 | dependencies: [ 31 | .product(name: "Benchmark", package: "package-benchmark"), 32 | .product(name: "ServiceContextModule", package: "swift-service-context"), 33 | ], 34 | path: "Benchmarks/ServiceContextBenchmarks", 35 | plugins: [ 36 | .plugin(name: "BenchmarkPlugin", package: "package-benchmark") 37 | ] 38 | ) 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /Benchmarks/Thresholds/5.10/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/5.10/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/5.9/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/5.9/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/6.0/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/6.0/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/6.1/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/6.1/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/nightly-main/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/nightly-main/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/nightly-next/TracingBenchmarks.PassAroundStaticStringsLarge.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 4 3 | } -------------------------------------------------------------------------------- /Benchmarks/Thresholds/nightly-next/TracingBenchmarks.PassAroundStaticStringsSmall.p90.json: -------------------------------------------------------------------------------- 1 | { 2 | "mallocCountTotal" : 2 3 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The code of conduct for this project can be found at https://swift.org/code-of-conduct. 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | By submitting a pull request, you represent that you have the right to license 4 | your contribution to Apple and the community, and agree by submitting the patch 5 | that your contributions are licensed under the Apache 2.0 license (see 6 | `LICENSE.txt`). 7 | 8 | ## How to submit a bug report 9 | 10 | Please ensure to specify the following: 11 | 12 | * Swift Service Context commit hash 13 | * Contextual information (e.g. what you were trying to achieve with Swift Service Context) 14 | * Simplest possible steps to reproduce 15 | * More complex the steps are, lower the priority will be. 16 | * A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description. 17 | * Anything that might be relevant in your opinion, such as: 18 | * Swift version or the output of `swift --version` 19 | * OS version and the output of `uname -a` 20 | * Network configuration 21 | 22 | ### Example 23 | 24 | ``` 25 | Commit hash: b17a8a9f0f814c01a56977680cb68d8a779c951f 26 | Context: 27 | While testing my application that uses with Swift Service Context, I noticed that ... 28 | Steps to reproduce: 29 | 1. ... 30 | 2. ... 31 | 3. ... 32 | 4. ... 33 | $ swift --version 34 | Swift version 4.0.2 (swift-4.0.2-RELEASE) 35 | Target: x86_64-unknown-linux-gnu 36 | Operating system: Ubuntu Linux 16.04 64-bit 37 | $ uname -a 38 | Linux beefy.machine 4.4.0-101-generic #124-Ubuntu SMP Fri Nov 10 18:29:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 39 | My system has IPv6 disabled. 40 | ``` 41 | 42 | ## Writing a Patch 43 | 44 | A good patch is: 45 | 46 | 1. Concise, and contains as few changes as needed to achieve the end result. 47 | 2. Tested, ensuring that any tests provided failed before the patch and pass after it. 48 | 3. Documented, adding API documentation as needed to cover new functions and properties. 49 | 4. Accompanied by a great commit message, using our commit message template. 50 | 51 | ### Run CI checks locally 52 | 53 | You can run the Github Actions workflows locally using [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally). 54 | 55 | ## How to contribute your work 56 | 57 | Please open a pull request at https://github.com/apple/swift-service-context. Make sure the CI passes, and then wait for code review. 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The Swift Service Context Project 3 | ===================== 4 | 5 | Please visit the Swift Service Context web site for more information: 6 | 7 | * https://github.com/apple/swift-asn1 8 | 9 | Copyright 2024 The Swift Service Context Project 10 | 11 | The Swift Service Context Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | Also, please refer to each LICENSE.txt file, which is located in 24 | the 'license' directory of the distribution file, for the license terms of the 25 | components that this product depends on. 26 | 27 | --- 28 | 29 | This product contains derivations of benchmarking code from Swift Distributed Tracing. 30 | 31 | * LICENSE (Apache License 2.0): 32 | * https://www.apache.org/licenses/LICENSE-2.0 33 | * HOMEPAGE: 34 | * https://github.com/apple/swift-distributed-tracing 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swift-service-context", 7 | products: [ 8 | .library( 9 | name: "ServiceContextModule", 10 | targets: [ 11 | "ServiceContextModule" 12 | ] 13 | ), 14 | 15 | // Deprecated/legacy module 16 | .library( 17 | name: "InstrumentationBaggage", 18 | targets: [ 19 | "InstrumentationBaggage" 20 | ] 21 | ), 22 | ], 23 | targets: [ 24 | .target(name: "ServiceContextModule"), 25 | 26 | // Deprecated/legacy module 27 | .target( 28 | name: "InstrumentationBaggage", 29 | dependencies: [ 30 | .target(name: "ServiceContextModule") 31 | ] 32 | ), 33 | 34 | // ==== -------------------------------------------------------------------------------------------------------- 35 | // MARK: Tests 36 | 37 | .testTarget( 38 | name: "ServiceContextTests", 39 | dependencies: [ 40 | .target(name: "ServiceContextModule") 41 | ] 42 | ), 43 | ] 44 | ) 45 | 46 | for target in package.targets { 47 | var settings = target.swiftSettings ?? [] 48 | settings.append(.enableExperimentalFeature("StrictConcurrency=complete")) 49 | target.swiftSettings = settings 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Service Context 2 | 3 | [![Swift 5.7](https://img.shields.io/badge/Swift-5.7-ED523F.svg?style=flat)](https://swift.org/download/) 4 | [![Swift 5.8](https://img.shields.io/badge/Swift-5.8-ED523F.svg?style=flat)](https://swift.org/download/) 5 | [![Swift 5.9](https://img.shields.io/badge/Swift-5.9-ED523F.svg?style=flat)](https://swift.org/download/) 6 | [![Swift 5.10](https://img.shields.io/badge/Swift-5.10-ED523F.svg?style=flat)](https://swift.org/download/) 7 | [![Swift 6.0](https://img.shields.io/badge/Swift-6.0-ED523F.svg?style=flat)](https://swift.org/download/) 8 | 9 | `ServiceContext` is a minimal (zero-dependency) context propagation container, intended to "carry" items for purposes of cross-cutting tools to be built on top of it. 10 | 11 | It is modeled after the concepts explained in [W3C Baggage](https://w3c.github.io/baggage/) and 12 | in the spirit of [Tracing Plane](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf) 's "Baggage Context" type, 13 | although by itself it does not define a specific serialization format. 14 | 15 | See https://github.com/apple/swift-distributed-tracing for actual instrument types and implementations which can be used to 16 | deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the 17 | [SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing). 18 | 19 | ## Overview 20 | 21 | `ServiceContext` serves as currency type for carrying around additional contextual information between Swift tasks and functions. 22 | 23 | One generally starts from a "top level" (empty) or the "current" (`ServiceContext.current`) context and then adds values to it. 24 | 25 | The context is a value type and is propagated using task-local values so it can be safely used from concurrent contexts like this: 26 | 27 | ```swift 28 | var context = ServiceContext.topLevel 29 | context[FirstTestKey.self] = 42 30 | 31 | func exampleFunction() async -> Int { 32 | guard let context = ServiceContext.current else { 33 | return 0 34 | } 35 | guard let value = context[FirstTestKey.self] else { 36 | return 0 37 | } 38 | print("test = \(value)") // test = 42 39 | return value 40 | } 41 | 42 | let c = await ServiceContext.withValue(context) { 43 | await exampleFunction() 44 | } 45 | assert(c == 42) 46 | ``` 47 | 48 | `ServiceContext` is a fundamental building block for how distributed tracing propagates trace identifiers. 49 | 50 | ## Dependency 51 | 52 | In order to depend on this library you can use the Swift Package Manager, and add the following dependency to your `Package.swift`: 53 | 54 | ```swift 55 | dependencies: [ 56 | .package( 57 | url: "https://github.com/apple/swift-service-context.git", 58 | from: "1.0.0" 59 | ) 60 | ] 61 | ``` 62 | 63 | and depend on the module in your target: 64 | 65 | ```swift 66 | targets: [ 67 | .target( 68 | name: "MyAwesomeApp", 69 | dependencies: [ 70 | .product( 71 | name: "ServiceContextModule", 72 | package: "swift-service-context" 73 | ), 74 | ] 75 | ), 76 | // ... 77 | ] 78 | ``` 79 | -------------------------------------------------------------------------------- /Sources/InstrumentationBaggage/CompatibilityShims.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2020-2022 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | @_exported import ServiceContextModule 16 | 17 | @available(*, deprecated, message: "Use 'ServiceContext' from 'ServiceContextModule' instead.") 18 | public typealias Baggage = ServiceContext 19 | @available(*, deprecated, message: "Use 'ServiceContext' from 'ServiceContextModule' instead.") 20 | public typealias BaggageKey = ServiceContextKey 21 | @available(*, deprecated, message: "Use 'ServiceContext' from 'ServiceContextModule' instead.") 22 | public typealias AnyBaggageKey = AnyServiceContextKey 23 | -------------------------------------------------------------------------------- /Sources/InstrumentationBaggage/Docs.docc/index.md: -------------------------------------------------------------------------------- 1 | # ``InstrumentationBaggage`` 2 | 3 | This type was renamed in the final API to `ServiceContext`, please use the `ServiceContextModule` module to obtain it. 4 | 5 | ## Overview 6 | 7 | ``ServiceContext`` is the legacy and deprecated name of this type, please use `ServiceContext` in your APIs. 8 | 9 | Baggage, while being a term-of-art for distributed tracing, was not a suitable name for this type because 10 | it was getting used in various situations which are not strictly distributed tracing related. 11 | 12 | Please use the `ServiceContextModule` module instead. -------------------------------------------------------------------------------- /Sources/ServiceContextModule/Docs.docc/index.md: -------------------------------------------------------------------------------- 1 | # ``ServiceContextModule`` 2 | 3 | Common currency type for type-safe and Swift concurrency aware context propagation. 4 | 5 | ## Overview 6 | 7 | ``ServiceContext`` is a minimal (zero-dependency) context propagation container, intended to "carry" context items 8 | for purposes of cross-cutting tools to be built on top of it. 9 | 10 | It is modeled after the concepts explained in [W3C Baggage](https://w3c.github.io/baggage/) and the 11 | in the spirit of [Tracing Plane](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s "Baggage Context" type, 12 | although by itself it does not define a specific serialization format. 13 | 14 | See https://github.com/apple/swift-distributed-tracing for actual instrument types and implementations which can be used to 15 | deploy various cross-cutting instruments all reusing the same context type. More information can be found in the 16 | [SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing). 17 | 18 | > Automatic propagation through task-locals by using `ServiceContext.current` is supported in Swift >= 5.5 19 | 20 | ## Getting started 21 | 22 | In order to depend on this library you can use the Swift Package Manager, and add the following dependency to your `Package.swift`: 23 | 24 | ```swift 25 | dependencies: [ 26 | .package( 27 | url: "https://github.com/apple/swift-service-context.git", 28 | from: "0.2.0" 29 | ) 30 | ] 31 | ``` 32 | 33 | and depend on the module in your target: 34 | 35 | ```swift 36 | targets: [ 37 | .target( 38 | name: "MyAwesomeApp", 39 | dependencies: [ 40 | .product( 41 | name: "ServiceContextModule", 42 | package: "swift-service-context" 43 | ), 44 | ] 45 | ), 46 | // ... 47 | ] 48 | ``` 49 | 50 | ## Usage 51 | 52 | Please refer to in-depth discussion and documentation in the [Swift Distributed Tracing](https://github.com/apple/swift-distributed-tracing) repository. 53 | -------------------------------------------------------------------------------- /Sources/ServiceContextModule/ServiceContext.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2020-2022 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// A `ServiceContext` is a heterogeneous storage type with value semantics for keyed values in a type-safe fashion. 16 | /// 17 | /// Its values are uniquely identified via ``ServiceContextKey``s (by type identity). These keys also dictate the type of 18 | /// value allowed for a specific key-value pair through their associated type `Value`. 19 | /// 20 | /// ## Defining keys and accessing values 21 | /// ServiceContext keys are defined as types, most commonly case-less enums (as no actual instances are required) 22 | /// which conform to the ``ServiceContextKey`` protocol: 23 | /// 24 | /// private enum TestIDKey: ServiceContextKey { 25 | /// typealias Value = String 26 | /// } 27 | /// 28 | /// While defining a key, one should also immediately declare an extension on `ServiceContext` to allow convenient and discoverable ways to interact 29 | /// with the context item. The extension should take the form of: 30 | /// 31 | /// extension ServiceContext { 32 | /// var testID: String? { 33 | /// get { 34 | /// self[TestIDKey.self] 35 | /// } set { 36 | /// self[TestIDKey.self] = newValue 37 | /// } 38 | /// } 39 | /// } 40 | /// 41 | /// For consistency, it is recommended to name key types with the `...Key` suffix (e.g. `SomethingKey`) and the property 42 | /// used to access a value identifier by such key the prefix of the key (e.g. `something`). Please also observe the usual 43 | /// Swift naming conventions, e.g. prefer `ID` to `Id` etc. 44 | /// 45 | /// ## Usage 46 | /// Using a context container is fairly straight forward, as it boils down to using the prepared computed properties: 47 | /// 48 | /// var context = ServiceContext.topLevel 49 | /// // set a new value 50 | /// context.testID = "abc" 51 | /// // retrieve a stored value 52 | /// let testID = context.testID ?? "default" 53 | /// // remove a stored value 54 | /// context.testIDKey = nil 55 | /// 56 | /// Note that normally a context should not be "created" ad-hoc by user code, but rather it should be passed to it from 57 | /// a runtime. A `ServiceContext` may already be available to you through ServiceContext.$current when using structured concurrency. 58 | /// Otherwise, for example when working in an HTTP server framework, it is most likely that the context is already passed 59 | /// directly or indirectly (e.g. in a `FrameworkContext`). 60 | /// 61 | /// ### Accessing all values 62 | /// 63 | /// The only way to access "all" values in a context is by using the `forEach` function. 64 | /// `ServiceContext` does not expose more functions on purpose to prevent abuse and treating it as too much of an 65 | /// arbitrary value smuggling container, but only make it convenient for tracing and instrumentation systems which need 66 | /// to access either specific or all items carried inside a context. 67 | public struct ServiceContext: Sendable { 68 | private var _storage = [AnyServiceContextKey: Sendable]() 69 | 70 | /// Internal on purpose, please use ``ServiceContext/TODO(_:function:file:line:)`` or ``ServiceContext/topLevel`` to create an "empty" context, 71 | /// which carries more meaning to other developers why an empty context was used. 72 | init() {} 73 | } 74 | 75 | // MARK: - Creating ServiceContext 76 | 77 | extension ServiceContext { 78 | /// Creates a new empty "top level" context, generally used as an "initial" context to immediately be populated with 79 | /// some values by a framework or runtime. Another use case is for tasks starting in the "background" (e.g. on a timer), 80 | /// which don't have a "request context" per se that they can pick up, and as such they have to create a "top level" 81 | /// context for their work. 82 | /// 83 | /// ## Usage in frameworks and libraries 84 | /// This function is really only intended to be used by frameworks and libraries, at the "top-level" where a request's, 85 | /// message's or task's processing is initiated. For example, a framework handling requests, should create an empty 86 | /// context when handling a request only to immediately populate it with useful trace information extracted from e.g. 87 | /// request headers. 88 | /// 89 | /// ## Usage in applications 90 | /// Application code should never have to create an empty context during the processing lifetime of any request, 91 | /// and only should create context if some processing is performed in the background - thus the naming of this property. 92 | /// 93 | /// Usually, a framework such as an HTTP server or similar "request handler" would already provide users 94 | /// with a context to be passed along through subsequent calls, either implicitly through the task-local `ServiceContext.$current` 95 | /// or explicitly as part of some kind of "FrameworkContext". 96 | /// 97 | /// If unsure where to obtain a context from, prefer using `.TODO("Not sure where I should get a context from here?")` 98 | /// in order to inform other developers that the lack of context passing was not done on purpose, but rather because either 99 | /// not being sure where to obtain a context from, or other framework limitations -- e.g. the outer framework not being 100 | /// context aware just yet. 101 | public static var topLevel: ServiceContext { 102 | ServiceContext() 103 | } 104 | } 105 | 106 | extension ServiceContext { 107 | /// A context intended as a placeholder until a real value can be passed through a function call. 108 | /// 109 | /// It should ONLY be used while prototyping or when the passing of the proper context is not yet possible, 110 | /// e.g. because an external library did not pass it correctly and has to be fixed before the proper context 111 | /// can be obtained where the TO-DO is currently used. 112 | /// 113 | /// ## Crashing on TO-DO context creation 114 | /// You may set the `SERVICE_CONTEXT_CRASH_TODOS` variable while compiling a project in order to make calls to this function crash 115 | /// with a fatal error, indicating where a to-do context was used. This comes in handy when wanting to ensure that 116 | /// a project never ends up using code which initially was written as "was lazy, did not pass context", yet the 117 | /// project requires context passing to be done correctly throughout the application. Similar checks can be performed 118 | /// at compile time easily using linters (not yet implemented), since it is always valid enough to detect a to-do context 119 | /// being passed as illegal and warn or error when spotted. 120 | /// 121 | /// ## Example 122 | /// 123 | /// let context = ServiceContext.TODO("The framework XYZ should be modified to pass us a context here, and we'd pass it along")) 124 | /// 125 | /// - Parameters: 126 | /// - reason: Informational reason for developers, why a placeholder context was used instead of a proper one, 127 | /// - function: The function to which the TODO refers. 128 | /// - file: The file to which the TODO refers. 129 | /// - line: The line to which the TODO refers. 130 | /// - Returns: Empty "to-do" context which should be eventually replaced with a carried through one, or `topLevel`. 131 | public static func TODO( 132 | _ reason: StaticString? = "", 133 | function: String = #function, 134 | file: String = #file, 135 | line: UInt = #line 136 | ) -> ServiceContext { 137 | var context = ServiceContext() 138 | #if BAGGAGE_CRASH_TODOS 139 | fatalError("BAGGAGE_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)") 140 | #elseif SERVICE_CONTEXT_CRASH_TODOS 141 | fatalError("SERVICE_CONTEXT_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)") 142 | #else 143 | context[TODOKey.self] = .init(file: file, line: line) 144 | return context 145 | #endif 146 | } 147 | 148 | private enum TODOKey: ServiceContextKey { 149 | typealias Value = TODOLocation 150 | static var nameOverride: String? { 151 | "todo" 152 | } 153 | } 154 | } 155 | 156 | /// Carried automatically by a "to do" context. 157 | /// It can be used to track where a context originated and which "to do" context must be fixed into a real one to avoid this. 158 | public struct TODOLocation: Sendable { 159 | /// Source file location where the to-do ``ServiceContext`` was created 160 | public let file: String 161 | /// Source line location where the to-do ``ServiceContext`` was created 162 | public let line: UInt 163 | } 164 | 165 | // MARK: - Interacting with ServiceContext 166 | 167 | extension ServiceContext { 168 | /// Provides type-safe access to the context's values. 169 | /// This API should ONLY be used inside of accessor implementations. 170 | /// 171 | /// End users should use "accessors" the key's author MUST define rather than using this subscript, following this pattern: 172 | /// 173 | /// internal enum TestID: ServiceContext.Key { 174 | /// typealias Value = TestID 175 | /// } 176 | /// 177 | /// extension ServiceContext { 178 | /// public internal(set) var testID: TestID? { 179 | /// get { 180 | /// self[TestIDKey.self] 181 | /// } 182 | /// set { 183 | /// self[TestIDKey.self] = newValue 184 | /// } 185 | /// } 186 | /// } 187 | /// 188 | /// This is in order to enforce a consistent style across projects and also allow for fine grained control over 189 | /// who may set and who may get such property. Just access control to the Key type itself lacks such fidelity. 190 | /// 191 | /// Note that specific context and context types MAY (and usually do), offer also a way to set context values, 192 | /// however in the most general case it is not required, as some frameworks may only be able to offer reading. 193 | public subscript(_ key: Key.Type) -> Key.Value? { 194 | get { 195 | guard let value = self._storage[AnyServiceContextKey(key)] else { return nil } 196 | // safe to force-cast as this subscript is the only way to set a value. 197 | return (value as! Key.Value) 198 | } 199 | set { 200 | self._storage[AnyServiceContextKey(key)] = newValue 201 | } 202 | } 203 | } 204 | 205 | extension ServiceContext { 206 | /// The number of items in the context. 207 | public var count: Int { 208 | self._storage.count 209 | } 210 | 211 | /// A Boolean value that indicates whether the context is empty. 212 | public var isEmpty: Bool { 213 | self._storage.isEmpty 214 | } 215 | 216 | /// Iterate through all items in this `ServiceContext` by invoking the given closure for each item. 217 | /// 218 | /// The order of those invocations is NOT guaranteed and should not be relied on. 219 | /// 220 | /// - Parameter body: The closure to be invoked for each item stored in this `ServiceContext`, 221 | /// passing the type-erased key and the associated value. 222 | @preconcurrency 223 | public func forEach(_ body: (AnyServiceContextKey, any Sendable) throws -> Void) rethrows { 224 | // swift-format-ignore: ReplaceForEachWithForLoop 225 | try self._storage.forEach { key, value in 226 | try body(key, value) 227 | } 228 | } 229 | } 230 | 231 | // MARK: - Propagating ServiceContext 232 | 233 | @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) 234 | extension ServiceContext { 235 | /// A `ServiceContext` is automatically propagated through task-local storage. This API enables binding a top-level `ServiceContext` and 236 | /// implicitly passes it to child tasks when using structured concurrency. 237 | @TaskLocal public static var current: ServiceContext? 238 | 239 | /// Convenience API to bind the task-local ``ServiceContext/current`` to the passed `value`, and execute the passed `operation`. 240 | /// 241 | /// To access the task-local value, use `ServiceContext.current`. 242 | /// 243 | /// SeeAlso: [Swift Task Locals](https://developer.apple.com/documentation/swift/tasklocal) 244 | public static func withValue(_ value: ServiceContext?, operation: () throws -> T) rethrows -> T { 245 | try ServiceContext.$current.withValue(value, operation: operation) 246 | } 247 | 248 | #if compiler(>=6.0) 249 | /// Convenience API to bind the task-local ``ServiceContext/current`` to the passed `value`, and execute the passed `operation`. 250 | /// 251 | /// To access the task-local value, use `ServiceContext.current`. 252 | /// 253 | /// SeeAlso: [Swift Task Locals](https://developer.apple.com/documentation/swift/tasklocal) 254 | public static func withValue( 255 | _ value: ServiceContext?, 256 | isolation: isolated (any Actor)? = #isolation, 257 | operation: () async throws -> T 258 | ) async rethrows -> T { 259 | try await ServiceContext.$current.withValue(value, operation: operation) 260 | } 261 | 262 | @available(*, deprecated, message: "Use the method with the isolation parameter instead.") 263 | // Deprecated trick to avoid executor hop here; 6.0 introduces the proper replacement: #isolation 264 | @_disfavoredOverload 265 | public static func withValue(_ value: ServiceContext?, operation: () async throws -> T) async rethrows -> T { 266 | try await ServiceContext.$current.withValue(value, operation: operation) 267 | } 268 | #else 269 | @_unsafeInheritExecutor 270 | public static func withValue(_ value: ServiceContext?, operation: () async throws -> T) async rethrows -> T { 271 | try await ServiceContext.$current.withValue(value, operation: operation) 272 | } 273 | #endif 274 | } 275 | -------------------------------------------------------------------------------- /Sources/ServiceContextModule/ServiceContextKey.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2020-2022 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// Context keys provide type-safe access to ``ServiceContext``s by declaring the type of value they "key" at compile-time. 16 | /// To give your `ServiceContextKey` an explicit name, override the ``ServiceContextKey/nameOverride-6shk1`` property. 17 | /// 18 | /// In general, any `ServiceContextKey` should be `internal` or `private` to the part of a system using it. 19 | /// 20 | /// All access to context items should be performed through an accessor computed property defined as shown below: 21 | /// 22 | /// /// The Key type should be internal (or private). 23 | /// enum TestIDKey: ServiceContextKey { 24 | /// typealias Value = String 25 | /// static var nameOverride: String? { "test-id" } 26 | /// } 27 | /// 28 | /// extension ServiceContext { 29 | /// /// This is some useful property documentation. 30 | /// public internal(set) var testID: String? { 31 | /// get { 32 | /// self[TestIDKey.self] 33 | /// } 34 | /// set { 35 | /// self[TestIDKey.self] = newValue 36 | /// } 37 | /// } 38 | /// } 39 | /// 40 | /// This pattern allows library authors fine-grained control over which values may be set, and which only get by end-users. 41 | public protocol ServiceContextKey: Sendable { 42 | /// The type of value uniquely identified by this key. 43 | associatedtype Value: Sendable 44 | 45 | /// The human-readable name of this key. 46 | /// This name will be used instead of the type name when a value is printed. 47 | /// 48 | /// It MAY also be picked up by an instrument (from Swift Tracing) which serializes context items and e.g. used as 49 | /// header name for carried metadata. Though generally speaking header names are NOT required to use the nameOverride, 50 | /// and MAY use their well known names for header names etc, as it depends on the specific transport and instrument used. 51 | /// 52 | /// For example, a context key representing the W3C "trace-state" header may want to return "trace-state" here, 53 | /// in order to achieve a consistent look and feel of this context item throughout logging and tracing systems. 54 | /// 55 | /// Defaults to `nil`. 56 | static var nameOverride: String? { get } 57 | } 58 | 59 | extension ServiceContextKey { 60 | public static var nameOverride: String? { nil } 61 | } 62 | 63 | /// A type-erased ``ServiceContextKey`` used when iterating through the ``ServiceContext`` using its `forEach` method. 64 | public struct AnyServiceContextKey: Sendable { 65 | /// The key's type erased to `Any.Type`. 66 | public let keyType: Any.Type 67 | 68 | private let _nameOverride: String? 69 | 70 | /// A human-readable String representation of the underlying key. 71 | /// If no explicit name has been set on the wrapped key the type name is used. 72 | public var name: String { 73 | self._nameOverride ?? String(describing: self.keyType.self) 74 | } 75 | 76 | init(_ keyType: Key.Type) { 77 | self.keyType = keyType 78 | self._nameOverride = keyType.nameOverride 79 | } 80 | } 81 | 82 | extension AnyServiceContextKey: Hashable { 83 | public static func == (lhs: AnyServiceContextKey, rhs: AnyServiceContextKey) -> Bool { 84 | ObjectIdentifier(lhs.keyType) == ObjectIdentifier(rhs.keyType) 85 | } 86 | 87 | public func hash(into hasher: inout Hasher) { 88 | hasher.combine(ObjectIdentifier(self.keyType)) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/ServiceContextTests/ServiceContextTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Service Context open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the Swift Service Context project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of Swift Service Context project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import ServiceContextModule 16 | import XCTest 17 | 18 | final class ServiceContextTests: XCTestCase { 19 | func test_topLevelServiceContextIsEmpty() { 20 | let context = ServiceContext.topLevel 21 | 22 | XCTAssertTrue(context.isEmpty) 23 | XCTAssertEqual(context.count, 0) 24 | } 25 | 26 | func test_readAndWriteThroughSubscript() throws { 27 | var context = ServiceContext.topLevel 28 | XCTAssertNil(context[FirstTestKey.self]) 29 | XCTAssertNil(context[SecondTestKey.self]) 30 | 31 | context[FirstTestKey.self] = 42 32 | context[SecondTestKey.self] = 42.0 33 | 34 | XCTAssertFalse(context.isEmpty) 35 | XCTAssertEqual(context.count, 2) 36 | XCTAssertEqual(context[FirstTestKey.self], 42) 37 | XCTAssertEqual(context[SecondTestKey.self], 42.0) 38 | } 39 | 40 | func test_forEachIteratesOverAllServiceContextItems() { 41 | var context = ServiceContext.topLevel 42 | 43 | context[FirstTestKey.self] = 42 44 | context[SecondTestKey.self] = 42.0 45 | context[ThirdTestKey.self] = "test" 46 | 47 | var contextItems = [AnyServiceContextKey: Any]() 48 | // swift-format-ignore: ReplaceForEachWithForLoop 49 | context.forEach { key, value in 50 | contextItems[key] = value 51 | } 52 | XCTAssertEqual(contextItems.count, 3) 53 | XCTAssertTrue(contextItems.contains(where: { $0.key.name == "FirstTestKey" })) 54 | XCTAssertTrue(contextItems.contains(where: { $0.value as? Int == 42 })) 55 | XCTAssertTrue(contextItems.contains(where: { $0.key.name == "SecondTestKey" })) 56 | XCTAssertTrue(contextItems.contains(where: { $0.value as? Double == 42.0 })) 57 | XCTAssertTrue(contextItems.contains(where: { $0.key.name == "explicit" })) 58 | XCTAssertTrue(contextItems.contains(where: { $0.value as? String == "test" })) 59 | } 60 | 61 | func test_TODO_doesNotCrashWithoutExplicitCompilerFlag() { 62 | _ = ServiceContext.TODO(#function) 63 | } 64 | 65 | func test_automaticPropagationThroughTaskLocal() throws { 66 | guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { 67 | throw XCTSkip("Task locals are not supported on this platform.") 68 | } 69 | 70 | XCTAssertNil(ServiceContext.current) 71 | 72 | var context = ServiceContext.topLevel 73 | context[FirstTestKey.self] = 42 74 | 75 | var propagatedServiceContext: ServiceContext? 76 | func exampleFunction() { 77 | propagatedServiceContext = ServiceContext.current 78 | } 79 | 80 | let c = ServiceContext.$current 81 | c.withValue(context, operation: exampleFunction) 82 | 83 | XCTAssertEqual(propagatedServiceContext?.count, 1) 84 | XCTAssertEqual(propagatedServiceContext?[FirstTestKey.self], 42) 85 | } 86 | 87 | actor SomeActor { 88 | var value: Int = 0 89 | 90 | func check() async { 91 | ServiceContext.$current.withValue(.topLevel) { 92 | value = 12 // should produce no warnings 93 | } 94 | ServiceContext.withValue(.topLevel) { 95 | value = 12 // should produce no warnings 96 | } 97 | await ServiceContext.withValue(.topLevel) { () async in 98 | value = 12 // should produce no warnings 99 | } 100 | } 101 | } 102 | 103 | private enum FirstTestKey: ServiceContextKey { 104 | typealias Value = Int 105 | } 106 | 107 | private enum SecondTestKey: ServiceContextKey { 108 | typealias Value = Double 109 | } 110 | 111 | private enum ThirdTestKey: ServiceContextKey { 112 | typealias Value = String 113 | 114 | static let nameOverride: String? = "explicit" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /dev/git.commit.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Motivation:** 4 | 5 | 7 | 8 | **Modifications:** 9 | 10 | 11 | 12 | **Result:** 13 | 14 | 15 | --------------------------------------------------------------------------------