# Technical Documentation: apple/swift-log

> ℹ️ **Provenance:** In-Tree Documentation (92 Chapters) · [CodeWiki Reference](https://codewiki.google/github.com/apple/swift-log) · Recency: Active (< 180 days)

## File: README.md

# SwiftLog

[](https://swiftpackageindex.com/apple/swift-log/documentation)
[](https://github.com/apple/swift-log/releases)
[](https://swiftpackageindex.com/apple/swift-log)
[](https://swiftpackageindex.com/apple/swift-log)

This repository contains a logging API implementation for Swift.
SwiftLog provides a unified, performant, and ergonomic logging API that can be
adopted by libraries and applications across the Swift ecosystem.

- 📚 **Documentation** and **tutorials** are available on the [Swift Package Index](https://swiftpackageindex.com/apple/swift-log)
- 🚀 **Contributions** are welcome, please see [CONTRIBUTING.md](CONTRIBUTING.md)
- 🪪 **License** is Apache 2.0, repeated in [LICENSE.txt](LICENSE.txt)
- 🔒 **Security** issues should be reported via the process in [Security](https://github.com/apple/swift-log?tab=security-ov-file)
- 🔀 **Available Logging Backends**: SwiftLog is an API package - you'll want to
choose from the many
[community-maintained logging backends](#available-log-handler-backends) for production use

## Quick Start

The following snippet shows how to add SwiftLog to your Swift Package:

```swift
// swift-tools-version: 6.2
import PackageDescription

let package = Package(
    name: "YourApp",
    dependencies: [
        .package(url: "https://github.com/apple/swift-log", from: "1.6.0")
    ],
    targets: [
        .target(
            name: "YourApp",
            dependencies: [
                .product(name: "Logging", package: "swift-log")
            ]
        )
    ]
)
```

Then start logging:

```swift
import Logging

// Create a logger
let logger = Logger(label: "com.example.YourApp")

// Log at different levels
logger.info("Application started")
logger.warning("This is a warning")
logger.error("Something went wrong", metadata: ["error": "\(error)"])

// Add metadata for context
var requestLogger = logger
requestLogger[metadataKey: "request-id"] = "\(UUID())"
requestLogger.info("Processing request")
```

## Available log handler backends

The community has built numerous specialized logging backends.

A great way to discover available log backend implementations is searching the
[Swift Package Index](https://swiftpackageindex.com/search?query=swift-log)
for the `swift-log` keyword.

## Building with CMake

While Swift Package Manager (SPM) is the primary build system for development and testing, CMake build support is also available for integrated or mixed build environments if needed. Note that tests are not included in the CMake build, they are managed through Swift Package Manager with `swift test`.

---

## File: CMakeLists.txt

#===----------------------------------------------------------------------===#
#
# This source file is part of the Swift Logging API open source project
#
# Copyright (c) 2018-2026 Apple Inc. and the Swift Logging API project authors
# Licensed under Apache License v2.0
#
# See LICENSE.txt for license information
# See CONTRIBUTORS.txt for the list of Swift Logging API project authors
#
# SPDX-License-Identifier: Apache-2.0
#
#===----------------------------------------------------------------------===#

cmake_minimum_required(VERSION 3.19)

# Initialize C language first to inspect the compiler environment
project(swift-log LANGUAGES C)

# Set Swift compiler target from C compiler target if not manually set for Windows
if(WIN32 AND NOT CMAKE_Swift_COMPILER_TARGET)
    if(CMAKE_C_COMPILER_ARCHITECTURE_ID MATCHES "(ARM64|arm64|aarch64)")
        set(CMAKE_Swift_COMPILER_TARGET "aarch64-unknown-windows-msvc" CACHE STRING "Swift compiler target triple")
    elseif(CMAKE_C_COMPILER_ARCHITECTURE_ID MATCHES "(X86|x64|AMD64|x86_64)")
        set(CMAKE_Swift_COMPILER_TARGET "x86_64-unknown-windows-msvc" CACHE STRING "Swift compiler target triple")
    endif()
endif()

# Enable Swift after C defines the architecture
enable_language(Swift)

option(BUILD_SHARED_LIBS "Build shared libraries by default" YES)

set(CMAKE_Swift_LANGUAGE_VERSION 6)

if(CMAKE_Swift_COMPILER_VERSION VERSION_LESS "6.2")
  message(FATAL_ERROR "swift-log requires Swift compiler version 6.2 or newer. Detected version: ${CMAKE_Swift_COMPILER_VERSION}")
endif()

set(SWIFT_LOG_COMMON_COMPILE_OPTIONS
  "SHELL:-enable-upcoming-feature ExistentialAny"
  "SHELL:-enable-upcoming-feature MemberImportVisibility"
  "SHELL:-enable-upcoming-feature InternalImportsByDefault"
  "SHELL:-enable-upcoming-feature NonisolatedNonsendingByDefault"
  "SHELL:-Xfrontend -require-explicit-sendable"
  "SHELL:-package-name swift_log")

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)

if(MSVC)
  message(STATUS "MSVC condition triggered successfully!")
  set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
  set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()

include(GNUInstallDirs)
include(SwiftSupport)

if(NOT APPLE)
  find_package(dispatch QUIET)
  find_package(Foundation QUIET)
endif()

add_subdirectory(Sources)

add_subdirectory(cmake/modules)

---

## File: CONTRIBUTING.md

## Legal

By submitting a pull request, you represent that you have the right to license
your contribution to Apple and the community, and agree by submitting the patch
that your contributions are licensed under the Apache 2.0 license (see
`LICENSE.txt`).

## How to submit a bug report

Please ensure to specify the following:

* SwiftLog commit hash
* Contextual information (e.g. what you were trying to achieve with SwiftLog)
* Simplest possible steps to reproduce
  * More complex the steps are, lower the priority will be.
  * A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description.
* Anything that might be relevant in your opinion, such as:
  * Swift version or the output of `swift --version`
  * OS version and the output of `uname -a`
  * Network configuration

### Example

```
SwiftLog commit hash: 4fe877816ad82627602377f415b6a66850214824

Context:
While testing my application that uses with SwiftLog, I noticed that ...

Steps to reproduce:
1. ...
2. ...
3. ...
4. ...

$ swift --version
Swift version 4.0.2 (swift-4.0.2-RELEASE)
Target: x86_64-unknown-linux-gnu

Operating system: Ubuntu Linux 16.04 64-bit

$ uname -a
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

My system has IPv6 disabled.
```

## Make sure Tests work on Linux

SwiftLog uses Swift Testing to run tests on all supported platforms.

## Run CI checks locally

You can run the Github Actions workflows locally using
[act](https://github.com/nektos/act). To run all the jobs that run on a pull
request, use the following command:

```
% act pull_request
```

To run just a single job, use `workflow_call -j <job>`, and specify the inputs
the job expects. For example, to run just shellcheck:

```
% act workflow_call -j soundness --input shell_check_enabled=true
```

To bind-mount the working directory to the container, rather than a copy, use
`--bind`. For example, to run just the formatting, and have the results
reflected in your working directory:

```
% act --bind workflow_call -j soundness --input format_check_enabled=true
```

If you'd like `act` to always run with certain flags, these can be be placed in
an `.actrc` file either in the current working directory or your home
directory, for example:

```
--container-architecture=linux/amd64
--remote-name upstream
--action-offline-mode
```

## Benchmarks

Benchmarks for `swift-log` are in a separate Swift Package in the `Benchmarks` subfolder of this repository.
They use the [`benchmark`](https://github.com/ordo-one/benchmark) plugin.
On Swift versions prior to 6.3 Benchmarks depends on the [`jemalloc`](https://jemalloc.net) memory allocation library, which is used by `benchmark` to capture memory allocation statistics.
An installation guide can be found in the [Getting Started article](https://swiftpackageindex.com/ordo-one/benchmark/documentation/benchmark/gettingstarted#Installing-Prerequisites-and-Platform-Support) of `benchmark`.
Afterwards you can run the benchmarks from CLI by going to the `Benchmarks` subfolder (e.g. `cd Benchmarks`) and invoking:
```
swift package benchmark
```

For more information please refer to `swift package benchmark --help` or the [documentation of `benchmark`](https://swiftpackageindex.com/ordo-one/benchmark/documentation/benchmark).


## How to contribute your work

For non-trivial changes that affect the public API, it is good practice to have a discussion phase before writing code. Please follow the [proposal process](Sources/Logging/Docs.docc/Proposals/Proposals.md) to gather feedback and align on the approach with other contributors.

1. Prepare your change, keeping in mind that a good patch is:
  - Concise, and contains as few changes as needed to achieve the end result.
  - Tested, ensuring that any tests provided failed before the patch and pass after it.
  - Documented, adding API documentation as needed to cover new functions and properties.
  - Accompanied by a great commit message.
2. Open a pull request at https://github.com/apple/swift-log and wait for code review by the maintainers.


## Automated release process

This repository uses automated releases based on semantic versioning labels. See the [Auto Release Workflow documentation](https://github.com/apple/swift-temporal-sdk/blob/main/.github/workflows/README.md) for details.

---

## File: LICENSE.txt

Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

---

## File: NOTICE.txt

The SwiftLog Project
                            ========================

Please visit the SwiftLog web site for more information:

  * https://github.com/apple/swift-log

Copyright 2018, 2019 The SwiftLog Project

The SwiftLog Project licenses this file to you under the Apache License,
version 2.0 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at:

  https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.

Also, please refer to each LICENSE.<component>.txt file, which is located in
the 'license' directory of the distribution file, for the license terms of the
components that this product depends on.

-------------------------------------------------------------------------------

This product contains a derivation of the lock implementation and various
scripts from SwiftNIO.

  * LICENSE (Apache License 2.0):
    * https://www.apache.org/licenses/LICENSE-2.0
  * HOMEPAGE:
    * https://github.com/apple/swift-nio

---

## File: .spi.yml

version: 1
builder:
  configs:
    - documentation_targets: [Logging]

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.3/MaxLogLevelNone.critical_log_with_critical_log_level.p90.json

{
  "instructions" : 734,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.3/MaxLogLevelNone.critical_log_with_critical_log_level_generic.p90.json

{
  "instructions" : 734,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.3/MaxLogLevelNone.critical_log_with_critical_log_level_logLevel_set.p90.json

{
  "instructions" : 734,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.3/MaxLogLevelNone.critical_log_with_critical_log_level_metadata_set.p90.json

{
  "instructions" : 710,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.4/MaxLogLevelNone.critical_log_with_critical_log_level.p90.json

{
  "instructions" : 810,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.4/MaxLogLevelNone.critical_log_with_critical_log_level_generic.p90.json

{
  "instructions" : 803,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.4/MaxLogLevelNone.critical_log_with_critical_log_level_logLevel_set.p90.json

{
  "instructions" : 803,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelNone/Thresholds/Xcode swift 6.4/MaxLogLevelNone.critical_log_with_critical_log_level_metadata_set.p90.json

{
  "instructions" : 803,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.3/MaxLogLevelWarning.notice_log_with_notice_log_level.p90.json

{
  "instructions" : 734,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.3/MaxLogLevelWarning.notice_log_with_notice_log_level_attributed_generic.p90.json

{
  "instructions" : 734,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.3/MaxLogLevelWarning.notice_log_with_notice_log_level_generic.p90.json

{
  "instructions" : 689,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.3/MaxLogLevelWarning.warning_log_with_warning_log_level.p90.json

{
  "instructions" : 1993,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.3/MaxLogLevelWarning.warning_log_with_warning_log_level_generic.p90.json

{
  "instructions" : 1968,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.4/MaxLogLevelWarning.notice_log_with_notice_log_level.p90.json

{
  "instructions" : 815,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.4/MaxLogLevelWarning.notice_log_with_notice_log_level_attributed_generic.p90.json

{
  "instructions" : 750,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.4/MaxLogLevelWarning.notice_log_with_notice_log_level_generic.p90.json

{
  "instructions" : 775,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.4/MaxLogLevelWarning.warning_log_with_warning_log_level.p90.json

{
  "instructions" : 2008,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/MaxLogLevelWarning/Thresholds/Xcode swift 6.4/MaxLogLevelWarning.warning_log_with_warning_log_level_generic.p90.json

{
  "instructions" : 2002,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_error_log_level_1_attribute.p90.json

{
  "instructions" : 1050,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_error_log_level_2_attributes.p90.json

{
  "instructions" : 1050,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_error_log_level_generic.p90.json

{
  "instructions" : 1046,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_1.p90.json

{
  "instructions" : 5483,
  "objectAllocCount" : 1
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_1000.p90.json

{
  "instructions" : 4939775,
  "objectAllocCount" : 2016
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_20.p90.json

{
  "instructions" : 82239,
  "objectAllocCount" : 37
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_5.p90.json

{
  "instructions" : 18351,
  "objectAllocCount" : 6
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_1_attribute.p90.json

{
  "instructions" : 6200,
  "objectAllocCount" : 2
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_2_attributes.p90.json

{
  "instructions" : 7600,
  "objectAllocCount" : 3
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_current_read_fallback.p90.json

{
  "instructions" : 1005,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_current_read_inside_scope.p90.json

{
  "instructions" : 1017,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_generic.p90.json

{
  "instructions" : 1966,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_withLogger_handler.p90.json

{
  "instructions" : 4551,
  "objectAllocCount" : 1
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.3/NoTraits.error_log_with_error_log_level_withLogger_mergingMetadata.p90.json

{
  "instructions" : 9623,
  "objectAllocCount" : 4
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_error_log_level_1_attribute.p90.json

{
  "instructions" : 1119,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_error_log_level_2_attributes.p90.json

{
  "instructions" : 1119,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_error_log_level_generic.p90.json

{
  "instructions" : 1117,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_1.p90.json

{
  "instructions" : 5327,
  "objectAllocCount" : 1
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_1000.p90.json

{
  "instructions" : 4263935,
  "objectAllocCount" : 2016
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_20.p90.json

{
  "instructions" : 69119,
  "objectAllocCount" : 37
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.debug_log_with_info_log_level_metadata_nesting_depth_5.p90.json

{
  "instructions" : 15471,
  "objectAllocCount" : 6
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_1_attribute.p90.json

{
  "instructions" : 6851,
  "objectAllocCount" : 2
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_2_attributes.p90.json

{
  "instructions" : 8399,
  "objectAllocCount" : 3
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_current_read_fallback.p90.json

{
  "instructions" : 1095,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_current_read_inside_scope.p90.json

{
  "instructions" : 1038,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_generic.p90.json

{
  "instructions" : 1972,
  "objectAllocCount" : 0
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_withLogger_handler.p90.json

{
  "instructions" : 4171,
  "objectAllocCount" : 1
}

---

## File: Benchmarks/NoTraits/Thresholds/Xcode swift 6.4/NoTraits.error_log_with_error_log_level_withLogger_mergingMetadata.p90.json

{
  "instructions" : 10231,
  "objectAllocCount" : 4
}

---

## File: cmake/modules/CMakeLists.txt

#===----------------------------------------------------------------------===#
#
# This source file is part of the Swift Logging API open source project
#
# Copyright (c) 2018-2026 Apple Inc. and the Swift Logging API project authors
# Licensed under Apache License v2.0
#
# See LICENSE.txt for license information
# See CONTRIBUTORS.txt for the list of Swift Logging API project authors
#
# SPDX-License-Identifier: Apache-2.0
#
#===----------------------------------------------------------------------===#

set(SwiftLog_EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/SwiftLogExports.cmake)

configure_file(SwiftLogConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/SwiftLogConfig.cmake)

get_property(SwiftLog_EXPORTS GLOBAL PROPERTY SwiftLog_EXPORTS)
export(TARGETS ${SwiftLog_EXPORTS}
  FILE ${SwiftLog_EXPORTS_FILE})

---

## File: Sources/CMakeLists.txt

#===----------------------------------------------------------------------===#
#
# This source file is part of the Swift Logging API open source project
#
# Copyright (c) 2018-2026 Apple Inc. and the Swift Logging API project authors
# Licensed under Apache License v2.0
#
# See LICENSE.txt for license information
# See CONTRIBUTORS.txt for the list of Swift Logging API project authors
#
# SPDX-License-Identifier: Apache-2.0
#
#===----------------------------------------------------------------------===#

add_subdirectory(Logging)
add_subdirectory(InMemoryLogging)

---

## File: Sources/InMemoryLogging/CMakeLists.txt

#===----------------------------------------------------------------------===#
#
# This source file is part of the Swift Logging API open source project
#
# Copyright (c) 2018-2026 Apple Inc. and the Swift Logging API project authors
# Licensed under Apache License v2.0
#
# See LICENSE.txt for license information
# See CONTRIBUTORS.txt for the list of Swift Logging API project authors
#
# SPDX-License-Identifier: Apache-2.0
#
#===----------------------------------------------------------------------===#

add_library(InMemoryLogging
  "InMemoryLogHandler.swift"
)

set_target_properties(InMemoryLogging PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "$>;$>")

target_compile_options(InMemoryLogging PRIVATE
  ${SWIFT_LOG_COMMON_COMPILE_OPTIONS})

target_link_libraries(InMemoryLogging PRIVATE Logging)

_install_target(InMemoryLogging)
set_property(GLOBAL APPEND PROPERTY SwiftLog_EXPORTS InMemoryLogging)

---

## File: Sources/Logging/CMakeLists.txt

#===----------------------------------------------------------------------===#
#
# This source file is part of the Swift Logging API open source project
#
# Copyright (c) 2018-2026 Apple Inc. and the Swift Logging API project authors
# Licensed under Apache License v2.0
#
# See LICENSE.txt for license information
# See CONTRIBUTORS.txt for the list of Swift Logging API project authors
#
# SPDX-License-Identifier: Apache-2.0
#
#===----------------------------------------------------------------------===#

add_library(Logging
  "Handlers/MultiplexLogHandler.swift"
  "Handlers/StreamLogHandler.swift"
  "Handlers/SwiftLogNoOpLogHandler.swift"
  "Locks.swift"
  "LogEvent.swift"
  "LogHandler.swift"
  "Logger+Attributes.swift"
  "Logger+With.swift"
  "Logger.swift"
  "LoggingSystem.swift"
  "MetadataProvider.swift"
)

set_target_properties(Logging PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES "$>;$>")

target_compile_options(Logging PRIVATE
  ${SWIFT_LOG_COMMON_COMPILE_OPTIONS})

_install_target(Logging)
set_property(GLOBAL APPEND PROPERTY SwiftLog_EXPORTS Logging)

---

## File: Sources/Logging/Docs.docc/DisableLogLevelsDuringCompilation.md

# Disable log levels during compilation

SwiftLog provides compile-time traits to eliminate less severe log levels from
your binary reducing the runtime overhead.

## Overview

### Motivation

When deploying applications to production, you often know in advance which log
levels will never be needed. For example, a production service might only need
warning and above, while trace and debug levels are only useful during
development. By using traits, you can completely remove these unnecessary log
levels at compile time, achieving zero runtime overhead.

### Available traits

SwiftLog defines seven maximum log level traits, ordered from most permissive
to most restrictive:

- `MaxLogLevelDebug`: Debug and above available (compiles out trace)
- `MaxLogLevelInfo`: Info and above available (compiles out trace, debug)
- `MaxLogLevelNotice`: Notice and above available (compiles out trace, debug,
  info)
- `MaxLogLevelWarning`: Warning and above available (compiles out trace, debug,
  info, notice)
- `MaxLogLevelError`: Error and above available (compiles out trace, debug,
  info, notice, warning)
- `MaxLogLevelCritical`: Only critical available (compiles out all except
  critical)
- `MaxLogLevelNone`: All logging compiled out (no log levels available)

By default (when no traits are specified), all log levels are available.

When you specify a maximum log level trait, all less severe levels are
completely removed from your binary at compile time. This applies to both
level-specific methods (e.g., `logger.debug()`) and calls to the generic
`logger.log(level:)` method. Traits are additive. If multiple max level traits
are specified, the most restrictive one takes effect.

> Note: Traits should only be set by the applications and not libraries as
> any traits defined in a transitive dependency will affect the whole resolution tree.

### Example

To enable a trait, specify it when declaring your package dependency:

```swift
// In your Package.swift:
dependencies: [
    .package(
        url: "https://github.com/apple/swift-log.git",
        from: "1.0.0",
        traits: ["MaxLogLevelWarning"]
    )
]
```

With `MaxLogLevelWarning` enabled, all trace, debug, info, and notice log
statements are compiled out:

```swift
// These become no-ops (compiled out completely):
logger.trace("This will not be in the binary")
logger.debug("This will not be in the binary")
logger.info("This will not be in the binary")
logger.notice("This will not be in the binary")
logger.log(level: .debug, "This will not log anything")

// These work normally:
logger.warning("This still works")
logger.error("This still works")
logger.critical("This still works")
logger.log(level: .error, "This still works")
```

When `MaxLogLevelNone` is enabled no log statement can ever fire, so
mutations that only configure the underlying ``LogHandler`` for future log
statements are also compiled out. Specifically, the body of the
``Logger/subscript(metadataKey:)`` setter and the body of the
``Logger/logLevel`` setter become empty, so calls like
`logger[metadataKey: "user-id"] = "\(id)"` and `logger.logLevel = .info`
do not reach the handler. Reads continue to work and return whatever the
handler reports.

---

## File: Sources/Logging/Docs.docc/ImplementingALogHandler.md

# Implementing a log handler

Create a custom logging backend that provides logging services for your apps and libraries.

## Overview

To become a compatible logging backend that any SwiftLog consumer can use,
you need to fulfill a few requirements, primarily conforming to the
``LogHandler`` protocol.

### Implement with value type semantics

Your log handler **must be a `struct`** and exhibit value semantics. This
ensures that changes to one logger don't affect others.

To verify that your handler reflects value semantics ensure that it passes this
test:

```swift
@Test
func logHandlerValueSemantics() {
    LoggingSystem.bootstrap(MyLogHandler.init)
    var logger1 = Logger(label: "first logger")
    logger1.logLevel = .debug
    logger1[metadataKey: "only-on"] = "first"

    var logger2 = logger1
    logger2.logLevel = .error                  // Must not affect logger1
    logger2[metadataKey: "only-on"] = "second" // Must not affect logger1

    // These expectations must pass
    #expect(logger1.logLevel == .debug)
    #expect(logger2.logLevel == .error)
    #expect(logger1[metadataKey: "only-on"] == "first")
    #expect(logger2[metadataKey: "only-on"] == "second")
}
```

> Note: In special cases, it is acceptable for a log handler to provide
> global log level overrides that may affect all log handlers created.

### Example implementation

Here's a complete example of a simple print-based log handler:

```swift
import Foundation
import Logging

public struct PrintLogHandler: LogHandler {
    private let label: String
    public var logLevel: Logger.Level = .info
    public var metadata: Logger.Metadata = [:]

    public init(label: String) {
        self.label = label
    }

    public func log(event: LogEvent) {
        let timestamp = ISO8601DateFormatter().string(from: Date())
        let levelString = event.level.rawValue.uppercased()

        // Merge handler metadata with message metadata
        let combinedMetadata = Self.prepareMetadata(
            base: self.metadata,
            explicit: event.metadata
        )

        // Format metadata
        let metadataString = combinedMetadata.map { "\($0.key)=\($0.value)" }.joined(separator: ",")

        // Create log line and print to console
        let logLine = "\(label) \(timestamp) \(levelString) [\(metadataString)]: \(event.message)"
        print(logLine)
    }

    public subscript(metadataKey key: String) -> Logger.Metadata.Value? {
        get {
            return self.metadata[key]
        }
        set {
            self.metadata[key] = newValue
        }
    }

    static func prepareMetadata(
        base: Logger.Metadata,
        explicit: Logger.Metadata?
    ) -> Logger.Metadata? {
        var metadata = base

        guard let explicit else {
            // all per-log-statement values are empty
            return metadata
        }

        metadata.merge(explicit, uniquingKeysWith: { _, explicit in explicit })

        return metadata
    }
}

```

### Constructing handlers that need more than a label

Many log handler backends need more than a `label` in order to construct them: a file path, a remote address, credentials, or
a buffer. A handler is an ordinary value that you construct, so nothing constrains its
initializer — it can take any parameters it needs, and it can `throw`. There is no
protocol-required `init(label:)`. The single `label` argument on factory methods such as
`StreamLogHandler.standardOutput(label:)` is a convention, not a requirement.

Open expensive or fallible resources, such as a file, a socket, early and up front in your own initializer that can `throw`, so that failures surface at setup time.
``LogHandler/log(event:)`` cannot throw, so an error deferred to logging time has nowhere to go.
Keep the handler `struct` cheap and copyable by placing any shared resource behind a thread-safe reference type.
Value semantics apply to a handler's *configuration* — its level and metadata — not its *destination*, so copies writing to
the same file are correct and expected.

The `FileLogHandler` below opens its file once, in an initializer that throws and exception when you create it with an overlapping `Destination`.
The handler `struct` itself stays a cheap, copyable value:

```swift
import Foundation
import Logging
import Synchronization

/// A log handler that appends formatted log lines to a file on disk.
public struct FileLogHandler: LogHandler {
    /// The shared, thread-safe destination. Open it once, then share it across handlers: because 
    /// the destination is a reference type, all those value-semantic handlers write through a single
    /// locked file handle.
    public final class Destination: @unchecked Sendable {
        private let fileHandle: Mutex<FileHandle>

        /// Opens `url` for appending. All validation happens here, at setup time.
        public init(writingTo url: URL) throws {
            let fileManager = FileManager.default
            if !fileManager.fileExists(atPath: url.path) {
                guard fileManager.createFile(atPath: url.path, contents: nil) else {
                    throw Failure.cannotCreateFile(url)
                }
            }
            self.fileHandle = .init(try FileHandle(forWritingTo: url))
            _ = try self.fileHandle.withLock { try $0.seekToEnd() }
        }

        func write(_ line: String) {
            // `log(event:)` cannot throw, so a write failure is handled here, not propagated.
            do {
                try self.fileHandle.withLock { try $0.write(contentsOf: Data(line.utf8)) }
            } catch {
                // Surface the failure without crashing the application.
                try? FileHandle.standardError.write(contentsOf: Data("FileLogHandler: \(error)\n".utf8))
            }
        }

        /// Flushes and closes the file. Call this during shutdown.
        public func close() {
            self.fileHandle.withLock { try? $0.close() }
        }
    }

    /// Errors thrown while opening a ``Destination``.
    public enum Failure: Error {
        case cannotCreateFile(URL)
    }

    private let label: String
    private let destination: Destination

    // Per-logger configuration — this is what must carry value semantics.
    public var logLevel: Logger.Level = .info
    public var metadata: Logger.Metadata = [:]
    public var metadataProvider: Logger.MetadataProvider?

    /// Cheap and non-throwing, so it is safe to call from a bootstrap factory closure per label.
    public init(label: String, destination: Destination, metadataProvider: Logger.MetadataProvider? = nil) {
        self.label = label
        self.destination = destination
        self.metadataProvider = metadataProvider
    }

    public func log(event: LogEvent) {
        var merged = self.metadata
        if let provided = self.metadataProvider?.get() {
            merged.merge(provided, uniquingKeysWith: { _, new in new })
        }
        if let explicit = event.metadata {
            merged.merge(explicit, uniquingKeysWith: { _, new in new })
        }
        let renderedMetadata =
            merged.isEmpty
            ? ""
            : " " + merged.sorted { $0.key < $1.key }.map { "\($0.key)=\($0.value)" }.joined(separator: " ")
        self.destination.write("\(event.level) \(self.label):\(renderedMetadata) \(event.message)\n")
    }

    public subscript(metadataKey key: String) -> Logger.Metadata.Value? {
        get { self.metadata[key] }
        set { self.metadata[key] = newValue }
    }
}
```

#### Using your log handler

Construct the destination where you can use `try` to build a logger backed by your handler, then bind it
as ``Logger/current`` for the application's lifetime using `withLogger`:

```swift
// Open the file once, where setup can throw.
let destination = try FileLogHandler.Destination(writingTo: URL(fileURLWithPath: "/var/log/myapp.log"))
defer { destination.close() }

// Build a logger backed by the handler, then bind it for the scope.
let logger = Logger(label: "app") { label in
    FileLogHandler(label: label, destination: destination)
}
try await withLogger(logger) { logger in
    logger.info("Application started")
    // Code in this scope reads the handler through Logger.current.
}
```

The `try` on `Destination` is where setup fails if the path is unwritable: validation happens
before the logger is constructed, so you never need a throwing factory overload. 
Use the same pattern for handlers that connect to a remote service:
  - validate and connect up front
  - keep ``LogHandler/log(event:)`` non-blocking by buffering onto a background task
  - provide a `close()` or `shutdown()` (or use [Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle) service) to flush on shutdown

For runtime failures the handler can't avoid, such as a full disk or a broken pipe, degrade gracefully inside `log(event:)` by dropping the log message, retrying, or reporting to `stderr`.
Never call `fatalError` on the logging path.

### Advanced features

#### Metadata providers

Metadata providers allow you to dynamically add contextual information to all
log messages without explicitly passing it each time. Common use cases include
request IDs, user sessions, or trace contexts that should be included in logs
throughout a request's lifecycle.

```swift
import Foundation
import Logging

public struct PrintLogHandler: LogHandler {
    private let label: String
    public var logLevel: Logger.Level = .info
    public var metadata: Logger.Metadata = [:]
    public var metadataProvider: Logger.MetadataProvider?

    public init(label: String) {
        self.label = label
    }

    public func log(event: LogEvent) {
        let timestamp = ISO8601DateFormatter().string(from: Date())
        let levelString = event.level.rawValue.uppercased()

        // Merge handler metadata with message metadata
        let combinedMetadata = Self.prepareMetadata(
            base: self.metadata,
            provider: self.metadataProvider,
            explicit: event.metadata
        )

        // Format metadata
        let metadataString = combinedMetadata.map { "\($0.key)=\($0.value)" }.joined(separator: ",")

        // Create log line and print to console
        let logLine = "\(label) \(timestamp) \(levelString) [\(metadataString)]: \(event.message)"
        print(logLine)
    }

    public subscript(metadataKey key: String) -> Logger.Metadata.Value? {
        get {
            return self.metadata[key]
        }
        set {
            self.metadata[key] = newValue
        }
    }

    static func prepareMetadata(
        base: Logger.Metadata,
        provider: Logger.MetadataProvider?,
        explicit: Logger.Metadata?
    ) -> Logger.Metadata? {
        var metadata = base

        let provided = provider?.get() ?? [:]

        guard !provided.isEmpty || !((explicit ?? [:]).isEmpty) else {
            // all per-log-statement values are empty
            return metadata
        }

        if !provided.isEmpty {
            metadata.merge(provided, uniquingKeysWith: { _, provided in provided })
        }

        if let explicit = explicit, !explicit.isEmpty {
            metadata.merge(explicit, uniquingKeysWith: { _, explicit in explicit })
        }

        return metadata
    }
}
```

#### Reading metadata attributes in LogHandlers

Metadata values can carry attributes alongside their string representation. Attributes are embedded inside
`MetadataValue` via the `.stringConvertible` case and are accessible through the `value.attributes` property. This
enables features like sensitivity annotations without any changes to your handler's protocol conformance.

To read attributes in your log handler:

```swift
public func log(event: LogEvent) {
    // Merge handler metadata, provider metadata, and event metadata as usual
    var merged = self.metadata

    if let provider = self.metadataProvider {
        merged.merge(provider.get(), uniquingKeysWith: { _, rhs in rhs })
    }

    if let eventMetadata = event.metadata {
        merged.merge(eventMetadata, uniquingKeysWith: { _, rhs in rhs })
    }

    // Read attributes from individual values:
    for (key, value) in merged {
        let attributes = value.attributes    // Empty if the value carries no attributes

        // Process based on your handler's needs
        // For example, check for a custom attribute:
        // if attributes[MyAttribute.self] == .flagged { ... }
    }
}
```

**Key considerations:**

- **Opt-in inspection**: Attributes are invisible unless you call `value.attributes`. Handlers that do not care about
  attributes work without any changes.

- **No new protocol requirements**: Reading attributes does not require implementing any new `LogHandler` properties
  or subscripts. The `metadata` property and `metadataKey` subscript work exactly as before.

- **Attributes flow through metadata**: Attributed values flow naturally through metadata merging, `MetadataProvider`,
  and `MultiplexLogHandler` — no special handling needed.

### Performance considerations

1. **Avoid blocking**: Don't block the calling thread for I/O operations.
2. **Lazy evaluation**: Remember that messages and metadata are autoclosures.
3. **Memory efficiency**: Don't hold onto large amounts of messages.

## See Also

- ``LogHandler``
- ``StreamLogHandler``
- ``MultiplexLogHandler``

---

## File: Sources/Logging/Docs.docc/index.md

# ``Logging``

A unified, performant, and ergonomic logging API for Swift.

## Overview

SwiftLog provides a logging API package designed to establish a common API the
ecosystem can use. It allows packages to emit log messages without tying them to
any specific logging implementation, while applications can choose any
compatible logging backend.

SwiftLog is an _API package_ which cuts the logging problem in half:
1. A logging API (this package)
2. Logging backend implementations (community-provided)

This separation allows libraries to adopt the API while applications choose any
compatible logging backend implementation without requiring changes from
libraries.

### Getting Started

Use this package if you're writing a cross-platform application (for example, Linux and
macOS) or library, and want to target this logging API.

### Adding the Dependency

Add the dependency to your `Package.swift`:

```swift
.package(url: "https://github.com/apple/swift-log", from: "1.6.0")
```

And to your target:

```swift
.target(
    name: "YourTarget",
    dependencies: [
        .product(name: "Logging", package: "swift-log")
    ]
)
```

### Basic Usage

```swift
// Import the logging API
import Logging

// Create a logger with a label
let logger = Logger(label: "MyLogger")

// Use it to log messages
logger.info("Hello World!")
```

This outputs:
```
2025-10-24T17:26:47-0700 info MyLogger: [your_app] Hello World!
```

### Default Behavior

SwiftLog provides basic console logging via ``StreamLogHandler``. By default it
uses `stdout`, however, you can configure it to use `stderr` instead:

```swift
LoggingSystem.bootstrap(StreamLogHandler.standardError)
```

``StreamLogHandler`` is primarily for convenience. For production applications,
implement the ``LogHandler`` protocol directly or use a community-maintained
backend.


## Topics

### Logging API

- <doc:UnderstandingLoggers>
- ``Logger``
- ``LoggingSystem``

### Log Handlers

- ``LogHandler``
- ``LogEvent``
- ``MultiplexLogHandler``
- ``StreamLogHandler``
- ``SwiftLogNoOpLogHandler``

### Best Practices

- <doc:LoggingBestPractices>
- <doc:ImplementingALogHandler>
- <doc:DisableLogLevelsDuringCompilation>

### Contributing

- <doc:Proposals>

---

## File: Sources/Logging/Docs.docc/LoggingBestPractices.md

# Logging best practices

Best practices for effective logging with SwiftLog.

## Overview

This collection of best practices helps library authors and application
developers create effective, maintainable logging that works well across diverse
environments. Each practice is designed to ensure your logs are useful for
debugging while being respectful of system resources and operational
requirements.

### Who Should Use These Practices

- **Library Authors**: Creating reusable components that log appropriately.
- **Application Developers**: Implementing logging strategies in applications.

### Philosophy

Good logging strikes a balance between providing useful information and avoiding
system overhead. These practices are based on real-world experience with
production systems and emphasize:

- **Predictable behavior** across different environments.
- **Performance consciousness** to avoid impacting application speed.
- **Operational awareness** to support production debugging and monitoring.
- **Developer experience** to make debugging efficient and pleasant.

### Contributing to these practices

These best practices evolve based on community experience and are maintained by
the Swift Server Working Group ([SSWG](https://www.swift.org/sswg/)). Each
practice includes:

- **Clear motivation** explaining why the practice matters
- **Concrete examples** showing good and bad patterns
- **Alternatives considered** documenting trade-offs and rejected approaches

## Topics

- <doc:001-ChoosingLogLevels>
- <doc:002-StructuredLogging>
- <doc:003-AcceptingLoggers>
- <doc:004-TestingLogging>

---

## File: Sources/Logging/Docs.docc/UnderstandingLoggers.md

# Understanding Loggers and Log Handlers

Learn how to create and configure loggers, set log levels, and use metadata to add context to your log messages.

## Overview

Create or retrieve a logger to get an instance for logging messages.
Log messages have a level that you use to indicate the message's importance.

SwiftLog defines seven log levels, represented by ``Logger/Level``, ordered from least to
most severe:

- ``Logger/Level/trace``
- ``Logger/Level/debug``
- ``Logger/Level/info``
- ``Logger/Level/notice``
- ``Logger/Level/warning``
- ``Logger/Level/error``
- ``Logger/Level/critical``

Once a message is sent to a logger, a log handler processes it.
The app using the logger configures the handler, usually for the environment in which that app runs, processing messages appropriate to that environment.
If the app doesn't provide its own log handler, SwiftLog defaults to using a ``StreamLogHandler`` that outputs log messages to `STDOUT`.

### Loggers

Loggers are used to emit log messages at different severity levels:

```swift
// Informational message
logger.info("Processing request")

// Something went wrong
logger.error("Houston, we have a problem")
```

``Logger`` is a value type with value semantics, meaning that when you modify a
logger's configuration (like its log level or metadata), it only affects that
specific logger instance:

```swift
let baseLogger = Logger(label: "MyApp")

// Create a new logger with different configuration.
var requestLogger = baseLogger
requestLogger.logLevel = .debug
requestLogger[metadataKey: "request-id"] = "\(UUID())"

// baseLogger is unchanged. It still has default log level and no metadata
// requestLogger has debug level and request-id metadata.
```

This value type behavior makes loggers safe to pass between functions and modify
without unexpected side effects.

### Log Levels

Log levels can be changed per logger without affecting others:

```swift
var logger = Logger(label: "MyLogger")
logger.logLevel = .debug
```

For guidance on what level to use for a message, see <doc:001-ChoosingLogLevels>.

### Logging Metadata

Metadata provides contextual information crucial for debugging:

```swift
var logger = Logger(label: "com.example.server")
logger[metadataKey: "request.id"] = "\(UUID())"
logger.info("Processing request")
```

Output:
```
2019-03-13T18:30:02+0000 info: request-uuid=F8633013-3DD8-481C-9256-B296E43443ED Processing request
```

### Source vs Label

A ``Logger`` has an immutable `label` that identifies its creator, while each log
message carries a `source` parameter that identifies where the message originated.
Use `source` for filtering messages from specific subsystems.

---

## File: Sources/Logging/Docs.docc/BestPractices/001-ChoosingLogLevels.md

# 001: Choosing log levels

Select appropriate log levels in applications and libraries.

## Overview

SwiftLog defines seven log levels, and choosing the right level is crucial for
creating well-behaved libraries that don't overwhelm logging systems or misuse
severity levels. This practice provides clear guidance on when to use each
level.

### Motivation

Libraries must be well-behaved across various use cases and cannot assume
specific logging backend configurations. Using inappropriate log levels can
flood production logs, trigger false alerts, or make debugging more difficult.
Following consistent log level guidelines ensures your library integrates well
with diverse application environments.

### Log levels

SwiftLog defines seven log levels via ``Logger/Level``, ordered from least to
most severe:

- ``Logger/Level/trace``
- ``Logger/Level/debug``
- ``Logger/Level/info``
- ``Logger/Level/notice``
- ``Logger/Level/warning``
- ``Logger/Level/error``
- ``Logger/Level/critical``

### Level guidelines

How you use log levels depends in large part if you are developing a library, or
an application which bootstraps its logging system and is in full control over
its logging environment.

#### For libraries

Libraries should use **info level or less severe** (info, debug, trace).

Libraries **should not** log information on **warning or more severe levels**,
unless it is a one-time (for example during startup) warning, that cannot lead
to overwhelming log outputs.

Each level serves different purposes:

##### Trace Level
- **Usage**: Log everything needed to diagnose hard-to-reproduce bugs.
- **Performance**: May impact performance; assume it won't be used in production.
- **Content**: Internal state, detailed operation flows, diagnostic information.

##### Debug Level
- **Usage**: May be enabled in some production deployments.
- **Performance**: Should not significantly undermine production performance.
- **Content**: High-level operation overview, connection events, major decisions.

##### Info Level
- **Usage**: Reserved for things that went wrong but can't be communicated
through other means, like throwing from a method.
- **Examples**: Connection retry attempts, fallback mechanisms, recoverable
  failures.
- **Guideline**: Use sparingly - Don't use for normal successful operations.

#### For applications

Applications can use **any level** depending on the context and what they want
to achieve. Applications have full control over their logging strategy.

#### Configuring logger log levels

It depends on the use-case of your application which log level your logger
should use. For **console and other end-user-visible displays**: Consider using
**notice level** as the minimum visible level to avoid overwhelming users with
technical details.

### Example

#### Recommended: Libraries should use info level or lower

```swift
// ✅ Good: Trace level for detailed diagnostics
logger.trace("Connection pool state", metadata: [
    "active": "\(activeConnections)",
    "idle": "\(idleConnections)",
    "pending": "\(pendingRequests)"
])

// ✅ Good: Debug level for high-value operational info
logger.debug("Database connection established", metadata: [
    "host": "\(host)",
    "database": "\(database)",
    "connectionTime": "\(duration)"
])

// ✅ Good: Info level for issues that can't be communicated through other means
logger.info("Connection failed, retrying", metadata: [
    "attempt": "\(attemptNumber)",
    "maxRetries": "\(maxRetries)",
    "host": "\(host)"
])
```

#### Use sparingly: Warning and error levels

```swift
// ✅ Good: One-time startup warning or error
logger.warning("Deprecated TLS version detected. Consider upgrading to TLS 1.3")
```

#### Avoid: Logging potentially intentional failures at info level

Some failures may be completely intentional from the high-level perspective of a
developer or system using your library. For example: failure to resolve a
domain, failure to make a request, or failure to complete some task;

Instead, log at debug or trace levels and offer alternative ways to observe
these behaviors, for example using `swift-metrics` to emit counts.

```swift
// ❌ Bad: Normal operations at info level flood production logs
logger.info("Request failed")
```

#### Avoid: Normal operations at info level

```swift
// ❌ Bad: Normal operations at info level flood production logs
logger.info("HTTP request received")
logger.info("Database query executed")
logger.info("Response sent")

// ✅ Good: Use appropriate levels instead
logger.debug("Processing request", metadata: ["path": "\(path)"])
logger.trace("Query", metadata: ["sql": "\(query)"])
logger.debug("Request completed", metadata: ["status": "\(status)"])
```

---

## File: Sources/Logging/Docs.docc/BestPractices/002-StructuredLogging.md

# 002: Structured logging

Use metadata to create machine-readable, searchable log entries.

## Overview

Structured logging uses metadata to separate human-readable messages from
machine-readable data. This practice makes logs easier to search, filter, and
analyze programmatically while maintaining readability.

### Motivation

Traditional string-based logging embeds all information in the message text,
making it more difficult for automated tools to parse and extract.
Structured logging separates these concerns; messages provide human readable
context while metadata provides structured data for tooling.

### Example

#### Recommended: Structured logging

```swift
// ✅ Structured - message provides context, metadata provides data
logger.info(
    "Accepted connection",
    metadata: [
        "connection.id": "\(id)",
        "connection.peer": "\(peer)", 
        "connections.total": "\(count)"
    ]
)

logger.error(
    "Database query failed",
    metadata: [
        "query.retries": "\(retries)",
        "query.error": "\(error)",
        "query.duration": "\(duration)"
    ]
)
```

### Advanced: Nested metadata for complex data

```swift
// ✅ Complex structured data
logger.trace(
    "HTTP request started",
    metadata: [
        "request.id": "\(requestId)",
        "request.method": "GET",
        "request.path": "/api/users",
        "request.headers": [
            "user-agent": "\(userAgent)"
        ],
        "client.ip": "\(clientIP)",
        "client.country": "\(country)"
    ]
)
```

#### Avoid: Unstructured logging

```swift
// ❌ Not structured - hard to parse programmatically
logger.info("Accepted connection \(id) from \(peer), total: \(count)")
logger.error("Database query failed after \(retries) retries: \(error)")
```

### Metadata key conventions

Use hierarchical dot-notation for related fields:

```swift
// ✅ Good: Hierarchical keys
logger.debug(
    "Database operation completed",
    metadata: [
        "db.operation": "SELECT",
        "db.table": "users",
        "db.duration": "\(duration)",
        "db.rows": "\(rowCount)"
    ]
)

// ✅ Good: Consistent prefixing
logger.info(
    "HTTP response",
    metadata: [
        "http.method": "POST",
        "http.status": "201",
        "http.path": "/api/users",
        "http.duration": "\(duration)"
    ]
)
```

---

## File: Sources/Logging/Docs.docc/BestPractices/003-AcceptingLoggers.md

# 003: Logger propagation in libraries

Propagate caller context by accepting a `Logger` parameter or reading the task-local
``Logger/current`` — never by constructing your own logger.

## Overview

Libraries should obtain a `Logger` in one of two ways: accept one through a method
parameter or read ``Logger/current`` from the task-local. Both approaches
preserve the caller's metadata, log level, and handler choice. Constructing a logger
inside a library — via ``Logger/init(label:)`` — takes those choices away from the
application and breaks metadata propagation.

### Motivation

The application controls logging: which backend, which log level, which metadata
travels with each request. Libraries that participate in this picture obtain a logger
the application has set up, rather than constructing their own from scratch. This
ensures correlation IDs and other contextual metadata flow through the entire call
stack, and gives the application a single place to redirect or filter all log output.

Two propagation mechanisms are available, and they coexist. Choose the explicit
parameter when the library's API already accepts a `Logger` (or it's natural to add
one) — the call site stays declarative about what gets logged where. Choose the
task-local when adding a `logger:` parameter would pollute an API that otherwise has
no logging concern in its signature. Application code binds the initial task-local;
library code reads it and updates the metadata.

### Example

#### Recommended: Read ``Logger/current`` from the task-local

When there is no `logger:` parameter in the API, read the
task-local. The application's accumulated metadata (`request.id`, etc.) flows in
automatically without an explicit hand-off.

```swift
// ✅ Good: Library reads Logger.current; caller scopes context via withLogger.
public struct AnalyticsClient {
    public func track(_ event: String) {
        Logger.current.info("event", metadata: ["event.name": "\(event)"])
    }
}

// Application binds at @main and scopes per-request metadata.
@main
struct MyServer {
    static func main() async throws {
        let logger = Logger(label: "my-server")
        try await withLogger(logger) { _ in
            try await runServices()
        }
    }
}

func handleRequest(_ req: HTTPRequest) async throws {
    try await withLogger(mergingMetadata: ["request.id": "\(req.id)"]) { _ in
        AnalyticsClient().track("request.received")    // sees request.id automatically
    }
}
```

For per-statement metadata, pass it via the `metadata:` parameter on the log call:

```swift
Logger.current.info("step", metadata: ["step.name": "validate"])
```

For a few back-to-back lines, take a local copy and stamp metadata there — also no
propagation:

```swift
var local = Logger.current
local[metadataKey: "step.name"] = "validate"
local.info("entering")
local.info("done")
```

#### Alternative: Accept logger through method parameters

```swift
// ✅ Good: Pass the logger through method parameters,
//          default to Logger.current in the public API.
struct RequestProcessor {
    func processRequest(_ request: HTTPRequest, logger: Logger = Logger.current) async throws -> HTTPResponse {
        // Add structured metadata that every log statement should contain.
        var logger = logger
        logger[metadataKey: "request.method"] = "\(request.method)"
        logger[metadataKey: "request.path"] = "\(request.path)"
        logger[metadataKey: "request.id"] = "\(request.id)"

        logger.debug("Processing request")

        // Pass the logger down to maintain metadata context.
        let validatedData = try validateRequest(request, logger: logger)
        let result = try await executeBusinessLogic(validatedData, logger: logger)

        logger.debug("Request processed successfully")
        return result
    }

    private func validateRequest(_ request: HTTPRequest, logger: Logger) throws -> ValidatedRequest {
        logger.debug("Validating request parameters")
        return ValidatedRequest(request)
    }

    private func executeBusinessLogic(_ data: ValidatedRequest, logger: Logger) async throws -> HTTPResponse {
        logger.debug("Executing business logic")
        let dbResult = try await databaseService.query(data.query, logger: logger)
        logger.debug("Business logic completed")
        return HTTPResponse(data: dbResult)
    }
}
```

#### Alternative: Accept logger through initializer for long-lived components

```swift
// ⚠️ Acceptable: Logger through initializer for long-lived components
final class BackgroundJobProcessor {
    private let logger: Logger

    init(logger: Logger) {
        self.logger = logger
    }

    func run() async {
        // Execute some long running work
        logger.debug("Update about long running work")
        // Execute some more long running work
    }
}
```

#### Avoid: Libraries creating their own loggers

Constructing ``Logger/init(label:)`` inside a library takes control over the handler, log
level, and base metadata away from the application. The application cannot redirect,
filter, or silence the library's output.

```swift
// ❌ Bad: Library creates its own logger — loses caller's context.
final class MyLibrary {
    private let logger = Logger(label: "MyLibrary")
}
```

``Logger/init(label:)`` is for the application — typically at `@main` paired with `.withLogger()`.
Library code, including internal application modules, should not construct loggers.
If they do, setting up the global factory with ``LoggingSystem/bootstrap(_:)`` allows
enforcing a specific factory on all the loggers constructed in the process. This
does not necessary need to be the same factory as was used to create the task-local
logger.

#### Avoid: Relying on ``Logger/current`` across non-Task boundaries

``Logger/current`` is backed by a `TaskLocal` and propagates through Swift's structured
concurrency model only. Callbacks invoked on non-Task threads — GCD blocks,
`URLSession` completion handlers, delegate methods dispatched onto specific queues,
`NotificationCenter` observers, C-API callbacks — see the *default* fallback logger,
not the bound one. Metadata bound by the calling `Task` is invisible inside those
callbacks.

```swift
// ❌ Bad: completion handler runs without the Task context; Logger.current is the fallback.
try await withLogger(mergingMetadata: ["request.id": "r1"]) { _ in
    URLSession.shared.dataTask(with: req) { data, _, _ in
        Logger.current.info("response")  // empty-label fallback, no request.id
    }.resume()
}
```

For libraries with async completion-handler APIs, accept an explicit `Logger` parameter.
If capturing and rebinding across the boundary is the only option:

```swift
// ✅ Good: capture before the boundary, rebind on the other side.
try await withLogger(mergingMetadata: ["request.id": "r1"]) { captured in
    URLSession.shared.dataTask(with: req) { data, _, _ in
        withLogger(captured) { _ in          // Bind Logger.current inside the callback
            Logger.current.info("response")  // Logger.current now has request.id
        }
    }.resume()
}
```

---

## File: Sources/Logging/Docs.docc/BestPractices/004-TestingLogging.md

# 004: Testing code that logs

Validate what you code logs by capturing entries in memory rather than bootstrapping a process-wide backend.

## Overview

Import and use `InMemoryLogging`, which provides an `InMemoryLogHandler` that collects every emitted record into an array you can inspect.
Keep a handler you create local to each test.
Each handler that you create in a test has no shared state, so the test stays isolated is safe to run on parallel with other tests.
Don't use ``LoggingSystem/bootstrap(_:)`` in a test, it sets one process-wide backend and traps if called twice.

Add the product to your test target:

```swift
.product(name: "InMemoryLogging", package: "swift-log")
```

### Capture logs from a logger you pass in

When the code under test accepts a `Logger` (see <doc:003-AcceptingLoggers>), back it with
an `InMemoryLogHandler` and validate the results on `entries`.

The following code illustrates how to set up and validate a handler using `InMemoryLogging` to check the expected results:

```swift
import InMemoryLogging
import Logging
import Testing

struct RequestProcessor {
    func handle(id: String, logger: Logger) {
        logger.info("request received", metadata: ["request.id": "\(id)"])
    }
}

@Test
func stampsRequestID() {
    let handler = InMemoryLogHandler()
    let logger = Logger(label: "test", factory: { _ in handler })

    RequestProcessor().handle(id: "42", logger: logger)

    #expect(handler.entries.count == 1)
    #expect(handler.entries[0].message == "request received")
    #expect(handler.entries[0].metadata["request.id"] == "42")
}
```

Each `Entry` is equatable and exposes `level`, `message`, `metadata`, and `error`.
The handler log level defaults to ``Logger/Level/info``; set `handler.logLevel = .trace` before you use it to
capture lower levels.

### Capture logs from code that reads `Logger.current`

When the code you want to test reads the task-local ``Logger/current`` instead of taking a parameter, bind an `InMemoryLogHandler` for the scope of that by using ``withLogger(logLevel:handler:metadata:_:)-2pd9p``.
The handler shares its storage by reference, so the copy you hold sees what was logged.

Bind the handler for the scope of the call and validate the captured entry:

```swift
import InMemoryLogging
import Logging
import Testing

struct AnalyticsClient {
    func track(_ event: String) {
        Logger.current.info("event", metadata: ["event.name": "\(event)"])
    }
}

@Test
func tracksEvent() {
    let handler = InMemoryLogHandler()
    withLogger(handler: handler) { _ in
        AnalyticsClient().track("signup")  // calls Logger.current.info(...)
    }

    #expect(handler.entries.count == 1)
    #expect(handler.entries[0].metadata["event.name"] == "signup")
}
```

---

## File: Sources/Logging/Docs.docc/Proposals/Proposals.md

# Proposals

Collaborate on API changes to SwiftLog by writing a proposal.

## Overview

For non-trivial changes that affect the public API, the SwiftLog project adopts a lightweight version of the [Swift Evolution](https://github.com/apple/swift-evolution/blob/main/process.md) process.

Writing a proposal first helps discuss multiple possible solutions early, apply useful feedback from other contributors, and avoid reimplementing the same feature multiple times.

While it's encouraged to get feedback by opening a pull request with a proposal early in the process, it's also important to consider the complexity of the implementation when evaluating different solutions. For example, this might mean including a link to a branch containing a prototype implementation of the feature in the pull request description.

> Note: The goal of this process is to help solicit feedback from the whole community around the project, and we will continue to refine the proposal process itself. Use your best judgement, and don't hesitate to propose changes to the proposal structure itself!

### Steps

1. Make sure there's a GitHub issue for the feature or change you would like to propose.
2. Duplicate the `SLG-NNNN.md` document and replace `NNNN` with the next available proposal number.
3. Link the GitHub issue from your proposal, and fill in the proposal.
4. Open a pull request with your proposal and solicit feedback from other contributors.
5. Once a maintainer confirms that the proposal is ready for review, the state is updated accordingly. The review period is 7 days, and ends when one of the maintainers marks the proposal as Ready for Implementation, or Deferred.
6. Before the pull request is merged, there should be an implementation ready, either in the same pull request, or a separate one, linked from the proposal.
7. The proposal is considered Approved once the implementation, proposal PRs have been merged, and, if originally disabled by a feature flag, feature flag enabled unconditionally.

If you have any questions, ask in an issue on GitHub.

### Possible review states

- Awaiting Review
- In Review
- Ready for Implementation
- In Preview
- Approved
- Deferred

## Topics

### Implemented Proposals

- <doc:SLG-0001-metadata-providers>
- <doc:SLG-0002>
- <doc:SLG-0003>
- <doc:SLG-0004-metadata-value-attributes>
- <doc:SLG-0005-log-event-LogHandler-API>
- <doc:SLG-0006-task-local-logger>

### Proposal Template

- <doc:SLG-NNNN>

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0001-metadata-providers.md

# SLG-0001: Metadata Providers

While global metadata attributes may be manually set on a `LogHandler` level, there's currently no way of reliably providing contextual, automatically propagated, metadata when logging with swift-log.

## Authors

[Moritz Lang](https://github.com/slashmo), [Konrad 'ktoso' Malawski](https://github.com/ktoso)

## Motivation

To benefit from tools such as [Distributed Tracing](https://github.com/apple/swift-distributed-tracing) it is necessary for libraries to make use of the trace information.

Most notably, loggers should participate in tracing by including some trace metadata (such as e.g. a `trace-id`) when logging, as it transparently enables developers to benefit from log correlation using those IDs.

Today, the only supported way of providing such metadata is to pass them along to each log call explicitly:

```swift
logger.info("first this ...", metadata: ["trace-id": MyTracingLibrary.currentTraceID])
logger.info("... now this", metadata: ["trace-id": MyTracingLibrary.currentTraceID])
```

This comes with a couple of downsides:

### Error-prone and repetitive

It's easy to forget passing this metadata to _all_ log statements, resulting in an inconsistent debugging experience as these log statements cannot be found using correlation IDs.

The repetitiveness and verboseness of logging multiple metadata in-line quickly becomes annoying and vastly decreases the signal-to-noise ratio of Swift code trying to be a good citizen and making use of log correlation techniques such as distributed tracing.

### Impossible to implement for libraries

A large portion of logs are not generated by the end-user but by libraries such as AsyncHTTPClient, Vapor etc.
These libraries, by design, don't know about the specific metadata keys that should be included in the logs.
Those keys are after all runtime dependent, and may change depending on what tracing system is configured by the end user.

For example, a specific `Tracer` implementation would use a type representing a trace ID, and has a way of extracting
that trace ID from a [Swift Distributed Tracing Baggage](https://github.com/apple/swift-distributed-tracing-baggage). But other libraries can't know about this _specific_ trace ID and therefore would not be
able to pass such values along to their log statements.

## Proposed solution

To support this kind of runtime-generated metadata in `swift-log`, we need to extend the logging APIs in an open-ended way, to allow any kinds of metadata to be provided from the asynchronous context (i.e. task local values).

We also have a desire to keep `swift-log` a "zero dependencies" library, as it intends only to focus on describing a logging API, and not incur any additional dependencies, so it can be used in the most minimal of projects.

To solve this, we propose the extension of swift-log APIs with a new concept: _metadata providers_.

`MetadataProvider` is a struct nested in the `Logger` type, sitting alongside `MetadataValue` and the `Metadata`. 
Its purpose is to _provide_ `Logger.Metadata` from the asynchronous context, by e.g. looking up various task-local values,
and converting them into `Logger.Metadata`. This is performed by calling the `get()` function, like so:

```swift
extension Logger {
    public struct MetadataProvider: Sendable {
        // ... 
        public init(_ provideMetadata: @escaping @Sendable () -> Metadata)
    
        public get() -> Metadata
    }
}
```

### Defining a `MetadataProvider`

While `MetadataProvider`s can be created in an ad-hoc fashion, the struct may be used as a namespace
to define providers in. 

For example, a `MyTracer` implementation of swift-distributed-tracing would be expected to provide a metadata provider
that is aware of its own `Baggage` and specific keys it uses to carry the trace and span identifiers, like this:

```swift
import Tracing // swift-distributed-tracing

extension Logger.MetadataProvider {
    static let myTracer = Logger.MetadataProvider {
        guard let baggage = Baggage.current else {
            return [:]
        }
        guard let spanContext = baggage.spanContext else { 
            return [:]
        }
        
        return [
          "traceID": "\(spanContext.traceID)",
          "spanID": "\(spanContext.spanID)",
        ]
    }
}
```

### Using a `MetadataProvider`

A `MetadataProvider` can be set up either globally, on a boot-strapped logging system:

```swift
LoggingSystem.bootstrap(
    metadataProvider: .myTracer,
    StreamLogHandler.standardOutput
)
```

or, using the metadata provider specific bootstrap method:

```swift
LoggingSystem.bootstrapMetadataProvider(.myTracer)
```

It is also possible to configure a metadata provider on a per-logger basis, like this:

```swift
let logger = Logger(label: "example", metadataProvider: Logger.MetadataProvider { 
    guard let baggage = Baggage.current else {
        return [:]
    }
    guard let operationID = baggage.operationID else { 
        return nil
    }
    return ["extra/opID": "\(opID)"]
})
```

which _overrides_ the default bootstrapped metadata provider.

> NOTE: Setting the metadata provider on the logger directly means the `LoggingSystem` metadata provider
> is skipped (if defined), following how an explicitly passed handler `factory`
> overrides the `LoggingSystem`s `factory`.

Once a metadata provider was set up, when a log statement is about to be emitted, the log handler implementation shall
invoke it whenever it is about to emit a log statement. This must be done from the same asynchronous context as the log statement
was made; I.e. if a log handler were to asynchronously–in a _detached_ task–create the actual log message, the invocation of
the metadata provider still _must_ be performed before passing the data over to the other detached task.

Usually, metadata providers will reach for the task's Swift Distributed Tracing [`Baggage`](https://github.com/apple/swift-distributed-tracing-baggage) which is the mechanism that swift-distributed-tracing 
and instrumentation libraries use to propagate metadata across asynchronous, as well as process, boundaries. Handlers 
may also inspect other task-local values, however they should not expect other libraries to be able to propagage those
as well as they will propagate `Baggage` values.

Those metadata will then be included in the log statement, e.g. like this:

```swift
var baggage = Baggage.topLevel
baggage.spanContext = SpanContext()
Baggage.withValue(baggage) {
    test()
}

func test() {
    log.info("Test", metadata: ["oneOff": "42"])
    // info [traceID: abc, spanID: 123, onOff: 42] Test
}
```

### Multiple `MetadataProvider`s using `MetadataProvider.multiplex(_:)`

Borrowing the concept from log handlers, metadata providers also have a multiplexing implementation.
It is defined as an extension on `MetadataProvider` and is useful in cases where users want to utilize
more than one metadata provider at the same time:

```swift
extension Logger.MetadataProvider {
    public static func multiplex(_ providers: [Logger.MetadataProvider]) -> Logger.MetadataProvider {
        precondition(!providers.isEmpty, "providers MUST NOT be empty")
        return Logger.MetadataProvider { baggage in
            providers.reduce(into: nil) { metadata, provider in
                if let providedMetadata = provider.metadata(baggage) {
                    if metadata != nil {
                        metadata!.merge(providedMetadata, uniquingKeysWith: { _, rhs in rhs })
                    } else {
                        metadata = providedMetadata
                    }
                }
            }
        }
    }
}
```

Metadata keys are unique, so in case multiple metadata providers return the same key,
the last provider in the array _wins_ and provides the value.

Note that it is possible to query the `LoggingSystem` for the configured, system-wide, metadata provider,
and combine it using a `multiplex` provider if the system-wide provider should not be replaced, but augmented
by additional providers.

### Understanding Swift Distributed Tracing `Baggage`

The `Baggage` type is more than just a fancy type-safe container for your values which are meant to be carried across
using a single, well-known, [task-local value](https://developer.apple.com/documentation/swift/tasklocal).

Values stored in `Baggage` are intended to be carried _across process boundaries_, and e.g. libraries such as HTTP clients,
or other RPC mechanisms, or even messaging systems implement distributed tracing [instruments](https://github.com/apple/swift-distributed-tracing/blob/main/Sources/Instrumentation/Instrument.swift) 
which `inject` and `extract` baggage values into their respective carrier objects, e.g. an `HTTPRequest` in the case of an HTTP client.

In other words, whenever intending to propagate information _across processes_ utilize the `Baggage` type to carry it, 
and ensure to provide an `Instrument` that is able to inject/extract the values of interest into the carrier types you are interested in.
To learn more about baggage and instrumentation, refer to the [Swift Distributed Tracing](https://github.com/apple/swift-distributed-tracing/) library documentation.

#### When to use `Baggage` vs. `Logger[metadataKey:]`

While `Baggage` is context-dependent and changes depending on where the log methods are being called from, the metadata set on a `Logger` is static and not context-dependent. E.g, if you wanted to add things like an instance ID or a subsystem name to a `Logger`, that could be seen as static information and set via `Logger[metadataKey:]`.

```swift
var logger = Logger(label: "org.swift.my-service")
logger[metadataKey: "instanceId"] = "123"

logger.info("Service started.")
// [instanceId: 123] Service started.
```

On the other hand, things like a trace ID are dynamic and context-dependent, therefore would be obtained via `Baggage`:

```swift
logger.info("Product fetched.")
// [traceId: 42] Product fetched.
```

Inline-metadata is suitable for one-offs such as a `productId` or a `paymentMethod` in an online store service, but are not enough to corralate the following log statements, i.e. tying them both to the same request:

```swift
logger.info("Product fetched.", metadata: ["productId": "42"])
logger.info("Product purchased.", metadata: ["paymentMethod": "apple-pay"])

// [productId: 42] Product fetched.
// [paymentMethod: apple-pay] Product fetched.
```

If there was a `Baggage` value with a trace ID surrounding these log statements, they would be automatically correlatable:

```swift
var baggage = Baggage.topLevel
baggage.traceID = 42
Baggage.$current.withValue(baggage) {
    logger.info("Product fetched.", metadata: ["productId": "42"])
    logger.info("Product purchased.", metadata: ["paymentMethod": "apple-pay"])
}

// [trace-id: 42, productId: 42] Product fetched.
// [trace-id: 42, paymentMethod: apple-pay] Product fetched.
```

## Alternatives considered

### MetadataProviders as a function of `Baggage -> Metadata`

This was considered and fully developed, however it would cause swift-log to have a dependency on the instrumentation type `Baggage`, 
which was seen as in-conflict-with the interest of swift-log remaining a zero-dependency library.

`Baggage` _is_ the well known type that is intended to be used for any kind of distributed systems instrumentation and tracing,
however it adds additional complexity and confusion for users who are not interested in this domain. For example, developers
may be confused about why `Baggage` and `Logger.Metadata` look somewhat similar, but behave very differently. This complexity
is inherent to the two types actually being _very_ different, however we do not want to overwhelm newcomers or developers
who are only intending to use swift-log within process. Such developers do not need to care about the added complexities 
of distributed systems.

The tradeoff we take here is that every metadata provider will have to perform its own task-local (and therefore also thread-local),
access in order to obtain the `Baggage` value, rather than the lookup being able to be performed _once_ and shared between 
multiple providers when a multiplex provider was configured in the system. We view this tradeoff as worth taking, as the cost
of actually formatting the metadata usually strongly dominates the cost of the task-local lookup. 

### Removing `LogHandler.Metadata` in favor of `Baggage`

Removing logger metadata is not a good option because it serves a slightly different style of metadata than the baggage.

Baggage is intended for contextual, task-local, metadata which is _carried across multiple libraries_. The values stored in baggage are well-typed, and must declare keys for accessing values in a baggage, this works well for multiple pieces of an application needing to reach for specific baggage items: everyone aware of the `traceID` key, is able to query the baggage for this key (`baggage.traceID`) and obtain a well-typed value. This comes with a higher cost on declaring keys though, as well as a global namespace for those - which is desirable for such kinds of metadata.

This is not the same usage pattern as emitting a plain structured log where we'd like to include the name of an item we just queried:

```swift
log.info("Obtained item! Hooray!", metadata: ["item": "\(item)"])
```

In this example, the key/value pair for `"item"` is pretty ad-hoc, and we never need to refer to it elsewhere in the program. It never is queried by other pieces of the application, nor would it be useful to set it in baggage metadata, as the only purpose of the `item` key here is to log, and forget about it.

### Explicitly passing `Baggage` always

Baggage is designed for use cases like distributed tracing, or similar instrumentation patterns where "all" participating code may need to reach for it.

Specifically in logging, this means that _every_ call site for _every_ log statement would have to pass it explicitly resulting in annoying noisy code:

```swift
class StoresRepository {
    func store(byID id: String, eventLoop: EventLoop, logger: Logger, baggage: Baggage) async throws -> Store {
        InstrumentationSystem.tracer.withSpan("Fetch Store") { span in
            logger.info("Fetched store.", baggage: span.baggage)
        }
    }
}

try await storesRepository.store(
    byID: storeID,
    eventLoop: eventLoop,
    logger: logger,
    baggage: baggage
)
```

### Explicitly passing `Logger` always

Imagine we don't have metadata providers, we'd have to manually set trace IDs on loggers which doesn't really work as all libraries involved would need to know about the same specific trace ID. Even if we inverted the dependency to have `Tracing` depend on `Logging` so that we'd be able to define something like "Tracing, please populate this logger with metadata", we'd have to make sure this thing is called in all places to avoid dropping contextual metadata.

```swift
import Tracing
import Logging

let contextualLogger = InstrumentationSystem.tracer.populateTraceMetadata(logger)
contextualLogger.info("Request received.")
```

### Tracing providing extensions on Logger

Instead of having `swift-log` depend on `swift-distributed-tracing-baggage` we could also create extensions for `Logger` inside `swift-distributed-tracing` and have users call these new overloaded methods instead:

```swift
extension Logger {
    func tinfo(_ message: ..., baggage: Baggage?) {
        // ...
    }
}
```

Such extensions could work like the currently-proposed APIs, but the risk of users calling the wrong methods is incredibly high and we cannot overload the existing methods' signatures because of ambiguity of call-sides without explicit Baggage being passed:

```swift
logger.info("Hello")
// we want this to pick up Baggage, but the signature would be ambiguous
```

Also, this extension-based contextual metadata would require basically everyone in Server-side Swift to adapt their usage of `Logging` to use these extensions instead. With the proposed APIs, we'd only need to modify `Logging` and any NIO-based libraries such as `AsyncHTTPClient`, `Vapor`, etc. and not every single log statement in every application.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0002.md

# SLG-0002: Compile-time log level elimination using traits

Enable compile-time elimination of log levels to achieve zero runtime overhead for logs that will never be needed in production.

## Overview

- Proposal: SLG-0002
- Author(s): [Vladimir Kukushkin](https://github.com/kukushechkin)
- Status: **Approved**
- Issue: [apple/swift-log#378](https://github.com/apple/swift-log/issues/378)
- Implementation:
    - [apple/swift-log#389](https://github.com/apple/swift-log/pull/389)
- Related links:
    - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)

### Introduction

Add Swift package traits to allow applications to compile out less severe log levels, completely eliminating their runtime overhead from the final binary.

### Motivation

In production deployments, applications often know in advance which log levels will never be needed. For example, a production service typically only needs warning and above, while trace and debug levels are exclusively useful during development. Currently, even with the log level set to `.warning` at runtime, the code for trace and debug statements still exists in the binary and incurs overhead.

For performance-critical applications or resource-constrained environments, this overhead is unacceptable. Applications need a way to completely remove unnecessary log levels at compile time, achieving zero runtime cost.

Before this proposal, the only workarounds were:
- Manually wrapping log statements in `#if DEBUG` checks (error-prone and verbose)
- Accepting the runtime overhead (unacceptable for many use cases)

### Proposed solution

This proposal introduces seven package traits that correspond to maximum log level thresholds. Applications specify a trait when declaring their dependency on SwiftLog, and all less severe log levels are completely compiled out.

In your `Package.swift`:

```swift
dependencies: [
    .package(
        url: "https://github.com/apple/swift-log.git",
        from: "1.0.0",
        traits: ["MaxLogLevelWarning"]
    )
]
```

With `MaxLogLevelWarning` enabled:

```swift
// These become no-ops (compiled out completely):
logger.trace("This will not be in the binary")
logger.debug("This will not be in the binary")
logger.info("This will not be in the binary")
logger.notice("This will not be in the binary")
logger.log(level: .debug, "This will not log anything")

// These work normally:
logger.warning("This still works")
logger.error("This still works")
logger.critical("This still works")
logger.log(level: .error, "This still works")
```

### Detailed design

The seven available traits, ordered from most permissive to most restrictive, are:

```swift
traits: [
    .trait(name: "MaxLogLevelDebug", description: "Debug and above available (compiles out trace)"),
    .trait(name: "MaxLogLevelInfo", description: "Info and above available (compiles out trace, debug)"),
    .trait(name: "MaxLogLevelNotice", description: "Notice and above available (compiles out trace, debug, info)"),
    .trait(
        name: "MaxLogLevelWarning",
        description: "Warning and above available (compiles out trace, debug, info, notice)"
    ),
    .trait(
        name: "MaxLogLevelError",
        description: "Error and above available (compiles out trace, debug, info, notice, warning)"
    ),
    .trait(name: "MaxLogLevelCritical", description: "Only critical available (compiles out all except critical)"),
    .trait(name: "MaxLogLevelNone", description: "All logging compiled out (no log levels available)"),

    .default(enabledTraits: []),
]
```

By default (when no traits are specified), all log levels remain available.

Traits are additive: if multiple max level traits are specified in the dependency graph, the most restrictive one takes effect. This ensures that if any dependency requests stricter elimination, the entire build respects it.

> Important: Traits should only be set by applications, not libraries, as any trait defined in a transitive dependency will affect the entire dependency resolution tree.

For the generic `log(level:)` method, the implementation switches behavior based on whether any trait is enabled:

```swift
public func log(
    level: Logger.Level,
    _ message: @autoclosure () -> Logger.Message,
    metadata: @autoclosure () -> Logger.Metadata? = nil,
    source: @autoclosure () -> String? = nil,
    file: String = #fileID,
    function: String = #function,
    line: UInt = #line
) {
    #if MaxLogLevelDebug || MaxLogLevelInfo || MaxLogLevelNotice || MaxLogLevelWarning || MaxLogLevelError || MaxLogLevelCritical || MaxLogLevelNone
    // When traits are enabled, dispatch to level-specific methods
    // to leverage their compile-time elimination
    switch level {
    case .trace:
        self.trace(message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
    case .debug:
        self.debug(message(), metadata: metadata(), source: source(), file: file, function: function, line: line)
    // ... (all levels)
    }
    #else
    // When no traits are enabled, avoid the switch overhead
    self._log(
        level: level,
        message(),
        metadata: metadata(),
        source: source(),
        file: file,
        function: function,
        line: line
    )
    #endif
}
```

This approach adds a minimal switch statement overhead when traits are enabled, but allows the level-specific methods' compile-time elimination to work even when called through `log(level:)`. When no traits are enabled, the original zero-overhead path is preserved. Level-specific methods has no additional overhead.

### API stability

**For existing `Logger` users:**
- When no traits are enabled (the default), behavior is identical to before
- Users can opt in to traits at the application level without modifying any source code

**For existing `LogHandler` implementations:**
- No changes are required to any `LogHandler` implementation

### Future directions

No future directions identified.

### Alternatives considered

#### Per-log-level traits

An alternative approach would be to define one trait per log level that could be individually disabled:

```swift
traits: [
    .trait(name: "DisableTraceLogs"),
    .trait(name: "DisableDebugLogs"),
    .trait(name: "DisableInfoLogs"),
    .trait(name: "DisableNoticeLogs"),
    .trait(name: "DisableWarningLogs"),
    .trait(name: "DisableErrorLogs"),
    .trait(name: "DisableCriticalLogs"),
]
```

Users would specify multiple traits to eliminate multiple levels:

```swift
.package(
    url: "https://github.com/apple/swift-log.git",
    from: "1.0.0",
    traits: ["DisableTraceLogs", "DisableDebugLogs", "DisableInfoLogs", "DisableNoticeLogs"]
)
```

This approach was rejected in favor of a `MaxLogLevel` semantics to make it less verbose to use the functionality and to be aligned with the similar functionality in logging packages in other languages users might already be familiar with.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0003.md

# SLG-0003: Support for Attaching Errors to Log Entries

Add standardized way of attaching data from `Error` instances to log entries.

## Overview

- Proposal: SLG-0003
- Author(s): [Samuel Murray](https://github.com/samuelmurray)
- Status: **Approved**
- Issue: [apple/swift-log#291](https://github.com/apple/swift-log/issues/291)
- Implementation:
    - [apple/swift-log#425](https://github.com/apple/swift-log/pull/425)
- Related links:
    - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)
    - [First attempt at solution](https://github.com/apple/swift-log/pull/384)

### Introduction

Add new methods on `Logger` that accept an `Error` instance, which is passed as-is to the underlying `LogHandler`. To ensure backwards compatibility and a smooth adoption, a default implementation where the error is serialized into `MetadataValue`s is provided.

### Motivation

SwiftLog advocates for the use of _structured logging_, which in its implementation is achieved by using the `metadata` attribute in the log methods. This is important since it allows for automated tools to parse the logs and extract metadata values for filtering and/or visualization. When logging in a catch-block, it often makes sense to attach some information from the thrown `Error` in the log post, so as to capture the root cause.

```swift
do {
    try someThrowingFunc()
} catch {
    logger.warning("Something went wrong") // Want to somehow attach information about the error
}
```

Currently, there is no standardized way of doing this. The simplest way to do it would be to simply attach the stringified error to the message.

```swift
logger.warning("Something went wrong: \(error)")
```

This of course is not _structured_, so a better way would be:

```swift
logger.warning("Something went wrong", metadata: ["error": "\(error)"])
```

However, since there is no clear preference for what metadata key to use, it is likely that different code bases use variations of the above. This makes it hard for automated tools to extract error information, since there is no single key to look for.

Additionally, there is more information that we could extract from the error, such as the type name.

```swift
logger.warning("Something went wrong", metadata: ["error.message": "\(error)", "error.type": "\(String(reflecting: type(of: error)))"])
```

The above is a reasonable way to log a generic error, but it is unlikely that everyone will use the same metadata keys. It is also quite a lot of code, for something that feels like it should be easy to do - i.e. attach an error to a log.

Finally, even if you log errors in a consistent way in your code, you do not control logs generated in other modules/packages that you use. In practice, this makes it so that different parts of your application could log error information under different keys, which, again, makes it harder for tooling.

### Proposed solution

The solution we propose is to add convenience methods in `Logger` that allows the caller to do the following:

```swift
do {
    try someThrowingFunc()
} catch {
    logger.warning("Something went wrong", error: error)
}
```

The error would get passed as-is to the underlying `LogHandler`, which is free to serialize it in an appropriate way. We propose that such convenience methods are added for all log methods (even if it arguably makes less sense for e.g. `trace` and `debug` than `warning`).

There are two clear gains from this:

* It is obvious for the caller _how_ to attach an `Error` to a log (and the caller code also looks cleaner).
* `LogHandler`s are free to serialize the `Error` in an way appropriate to that handler.

A `LogHandler` instance can access the `Error` using a new protocol method. A typical `LogHandler` implementation could be to extract the string representation and type name, and serialize it as metadata.

```swift
struct ErrorLogHandler: LogHandler {
    func log(
        level: Logger.Level,
        message: Logger.Message,
        error: (any Error)?,
        metadata: Logger.Metadata?,
        source: String,
        file: String,
        function: String,
        line: UInt
    ) {
        var metadata = metadata ?? [:]
        if let error {
            metadata.merge([
                "error.message": "\(error)",
                "error.type": "\(String(reflecting: type(of: error)))",
            ]) { $1 }
        }
        // ...
    }

    /* ... */
}
```

As we extend the API of both `Logger` and `LogHandler`, we need to ensure compatibility with existing solutions on both sides. Specifically, users should be able to use the new `Logger` methods with existing `LogHandler`s, and get a reasonable serialization of the error. Otherwise, early adopters would have to manually serialize it _in addition to_ passing it using the new argument, until all `LogHandler`s have been updated. To achieve this, we add a default implementation on the `LogHandler` method, that serializes the `Error` as metadata, similar to the example above.

### Detailed design

The proposed solution is to add a new overload for each log method (`trace`, ..., `critical`) that accepts an `Error` as its second argument, as well as a new overload for `Logger.log`, that accepts an `Error` as its third argument.

```swift
extension Logger {
    /// Log a message at the 'trace' log level with the source that you provide.
    ///
    /// If ``Level/trace`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func trace(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'debug' log level with the source that you provide.
    ///
    /// If ``Level/debug`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func debug(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'info' log level with the source that you provide.
    ///
    /// If ``Level/info`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func info(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'notice' log level with the source that you provide.
    ///
    /// If ``Level/notice`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func notice(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'warning' log level with the source that you provide.
    ///
    /// If ``Level/warning`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func warning(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'error' log level with the source that you provide.
    ///
    /// If ``Level/error`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func error(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message at the 'critical' log level with the source that you provide.
    ///
    /// If ``Level/critical`` is at least as severe as this logger's ``logLevel`` the system logs the message;
    /// otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func critical(
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )

    /// Log a message using the log level and source that you provide.
    ///
    /// If the `logLevel` passed to this method is more severe than the `Logger`'s ``logLevel``, the library
    /// logs the message, otherwise nothing will happen.
    ///
    /// - parameters:
    ///    - level: The severity level of the `message`.
    ///    - message: The message to be logged. The `message` parameter supports any string interpolation literal.
    ///    - error: An `Error` to attach to this log message.
    ///    - metadata: One-off metadata to attach to this log message.
    ///    - source: The source this log message originates from. The value defaults
    ///              to the module that emits the log message.
    ///    - file: The file this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#fileID`.
    ///    - function: The function this log message originates from. There's usually no need to pass it explicitly, as
    ///                it defaults to `#function`.
    ///    - line: The line this log message originates from. There's usually no need to pass it explicitly, as it
    ///            defaults to `#line`.
    @inlinable
    public func log(
        level: Logger.Level,
        _ message: @autoclosure () -> Logger.Message,
        error: (any Error)?,
        metadata: @autoclosure () -> Logger.Metadata? = nil,
        source: @autoclosure () -> String? = nil,
        file: String = #fileID,
        function: String = #function,
        line: UInt = #line
    )
}
```

A new method is also added to `LogHandler`, which receives the `Error` passed to `Logger`.

```swift
protocol LogHandler {
    /// The library calls this method when a log handler must emit a log message.
    ///
    /// There is no need for the `LogHandler` to check if the `level` is above or
    /// below the configured `logLevel` as `Logger` already performed this check and
    /// determined that a message should be logged.
    ///
    /// - Parameters:
    ///   - level: The log level of the message.
    ///   - message: The message to log. To obtain a `String` representation call `message.description`.
    ///   - error: The `Error` associated to this log message.
    ///   - metadata: The metadata associated to this log message.
    ///   - source: The source where the log message originated, for example the logging module.
    ///   - file: The file this log message originates from.
    ///   - function: The function this log message originates from.
    ///   - line: The line this log message originates from.
    func log(
        level: Logger.Level,
        message: Logger.Message,
        error: (any Error)?,
        metadata: Logger.Metadata?,
        source: String,
        file: String,
        function: String,
        line: UInt
    )
}
```

A default implementation is included via an extension method, that calls the existing `log` function. This is to ensure backwards compatibility, and is similar to how `source` was added. The default implementation will serialize the `Error`, extracting its string representation and fully qualified type name, and add those to the metadata under the keys `"error.message"` and `"error.type"` respectively. 

A draft implementation can be found in [apple/swift-log#425](https://github.com/apple/swift-log/pull/425).

We choose the keys `"error.message"` and `"error.type"` since they correspond to what [ECS](https://www.elastic.co/docs/reference/ecs/ecs-error) and [OpenTelemetry](https://opentelemetry.io/docs/specs/semconv/registry/attributes/error/#error-type) use. However, we propose that the metadata keys used should **not** be considered stable or part of the `swift-log` API. Instead, if a `LogHandler` wants to use separate keys, it should implement the new protocol method, rather than re-map the metadata keys.

### API stability

The proposed additions are fully backwards compatible, meaning that all existing code will continue to function as is. Additionally, a user of `Logger` can start using the new convenience methods, without any modifications of the `LogHandler`, thanks to the default implementation and since a `LogHandler` is already expected to handle `Metadata`. 

However, when using the default implementation, a potential metadata collision exists when an `Error` is passed via the new methods while the same keys (`"error.message"`, `"error.type"`) are present in metadata:

```swift
logger.warning("Something went wrong", error: error, metadata: ["error.message": "some other info"])
```

We recommend that the `error` parameter takes precedence in such cases, with conflicting metadata values discarded. This aligns with the principle that explicit `error:` parameters represent primary data, while the same keys in metadata are likely incidental. In practice, we don't expect callers to deliberately pass conflicting error information.

Conversely, a `LogHandler` can implement the new protocol method, and still function with existing users of `Logger` - the `error` parameter will then always be `nil`.

### Future directions

#### recordError

If `swift-log` develops better support for scoped loggers, it might be possible to add a `recordError` method on `Logger`, that would attach the `Error` to all logs in that scope.

### Alternatives considered

#### Standardized Error metadata via Logger convenience

The first proposed solution was to treat the new `Logger` methods as "convenience methods", that simply serialized the `Error` as metadata and did not pass the raw `Error` to `LogHandler`. The end result was similar to the currently proposed default implementation in `LogHandler`. In this solution, the metadata keys (`"error.message"` and `"error.type"`) were considered stable and part of the API, since that allowed for `LogHandler` implementations to re-map the keys (e.g. to `"err.*"`). 

Ultimately, this was declined in favor of the current proposal. While the metadata based solution was simple, it lacked the flexibility that passing the raw `Error` allows for.

#### recordError convenience method

In the first iteration, we considered adding a new convenience method `recordError` on `Logger`, that would take an `Error`, serialize it, and add to the metadata on the `Logger`. This would primarily have been useful for when a `Logger` instance were passed down a call hierarchy and the `Error` metadata were relevant further down. However, it doesn't work well with the current proposal, since the serialization happens at a later stage.

#### Using .stringConvertible to pass Error to LogHandler

An alternative way to let the `LogHandler` get access to the raw `Error` would be to use the existing `.stringConvertible` enum case of `MetadataValue`. By putting the `Error` under a well-known metadata key (such as `"error"`), a `LogHandler` could get access to it, without any changes to the `LogHandler` protocol. However, this would only work for `Error`s that conform to `CustomStringConvertible`.

#### some Error vs any Error

The error argument in `Logger` was considered to be `some Error` rather than `any Error`. Ultimately, `any Error` was preferred. This is because in most typical use cases (e.g. general catch blocks), the error is an existential, so by using `some Error` we would get a slight performance penalty.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0004-metadata-value-attributes.md

# SLG-0004: Metadata value attributes

Add an extensible per-value attribute mechanism for metadata.

## Overview

- Proposal: SLG-0004
- Author(s): [Vladimir Kukushkin](https://github.com/kukushechkin)
- Status: **Approved**
- Issue: [apple/swift-log#204](https://github.com/apple/swift-log/issues/204)
- Implementation:
    - [apple/swift-log#453](https://github.com/apple/swift-log/pull/453)
- Related links:
    - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)
    - [First iteration of the proposal](https://forums.swift.org/t/proposal-slg-0004-metadata-values-privacy-attribute/85249)

### Introduction

Introduce an extensible mechanism for attaching per-value attributes to metadata. The core `Logging` module provides
the protocol and storage. Attribute packages define concrete attributes using the
`Logger.MetadataValueAttributes.Attribute` protocol.

### Motivation

Metadata values in `swift-log` are opaque strings by the time the `LogHandler` receives them. The call site often
knows things about a value that the handler cannot infer — for example, whether the value should be redacted in
different environments.

Today, there is no way to express this. A single log statement can contain values that need different treatment, and
log levels cannot help because they are per-statement, not per-value:

```swift
logger.info("Login", metadata: [
    "action": "\(action)",        // safe to log
    "user_email": "\(email)",     // should be redacted in production, but nothing tells the handler
])
```

The workaround is handler-side key-based rules (`redact: ["email", "card-*", ...]`), but this is fragile:

- Rules break when libraries rename keys.
- Rules require coordination across all dependencies.
- The rule mapping is invisible at the call site.

### Proposed solution

Attributes are embedded directly in `MetadataValue` via the existing `.stringConvertible` case. A custom
`MetadataValue.StringInterpolation` type produces attributed values when any interpolation segment specifies
attributes. Libraries define attribute types conforming to `Logger.MetadataValueAttributes.Attribute` and provide
ergonomic string interpolation overloads. Handlers read the attributes via `value.attributes` and act on them:

```swift
import Logging

public enum Sensitivity: Int64, Logger.MetadataValueAttributes.Attribute, Sendable {
    case sensitive = 1
    case `public` = 2
}

extension Logger.MetadataValue.StringInterpolation {
    public mutating func appendInterpolation<T: CustomStringConvertible & Sendable>(
        _ value: T, sensitivity: Sensitivity
    ) {
        self.appendInterpolation(value, attributes: { $0[Sensitivity.self] = sensitivity })
    }
}

logger.info("Request", metadata: [
    "method": "\(req.method)",
    "user_id": "\(req.userId, sensitivity: .sensitive)",
])
```

Handlers that support specific attributes read them from `value.attributes` and act accordingly. Handlers that do not
understand attributes see plain `MetadataValue` instances — attributes are invisible unless explicitly inspected. The
existing `metadata:` parameter is the only parameter needed; no new log method overloads are added.

Attributed values can also be created programmatically with the `.attributed` factory, which behaves like an additional
enum case without breaking existing exhaustive switches:

```swift
let value: Logger.MetadataValue = .attributed(userId, attributes: [Sensitivity.sensitive])
```

Intermediate handlers can read **and modify** attributes as metadata flows through a handler chain. The `attributes`
property supports both getting and setting, enabling composable pipelines where each handler in the chain can enrich,
transform, or strip attributes before forwarding:

```swift
// An enriching handler that tags all metadata with a processing timestamp attribute
func log(event: LogEvent) {
    var mutatedEvent = event
    if var eventMetadata = event.metadata {
        for (key, _) in eventMetadata {
            var attrs = eventMetadata[key]!.attributes
            attrs[ProcessingStage.self] = .enriched
            eventMetadata[key]?.attributes = attrs
        }
        mutatedEvent.metadata = eventMetadata
    }
    self.inner.log(event: mutatedEvent)
}
```

Attributes are **classifications, not enforcement**. An attribute does not guarantee any particular handler behavior —
the handler may not support it. Applications needing guaranteed behavior must enforce it at a different abstraction.

The attribute mechanism is extensible: packages define their own attribute types using
`Logger.MetadataValueAttributes.Attribute`. A
chained `LogHandler` can read the attributes it cares about, act on them, and forward the rest to the next handler in
the chain — enabling composable handler pipelines (e.g., redaction handler -> metrics-extraction handler -> output
handler). Common attributes should be defined in separate packages so that the core `Logging` module remains free of
domain-specific attributes while providing shared vocabulary across libraries. To improve discoverability, `swift-log`
can reference attribute packages that are common across the whole ecosystem.

### Detailed design

#### `Attribute` protocol

```swift
extension Logger.MetadataValueAttributes {
    /// A protocol for defining custom metadata attributes.
    ///
    /// Conform to this protocol to define a custom attribute that can be stored in
    /// ``MetadataValueAttributes``. Each conforming type acts as both the key (identified
    /// by its metatype) and the value.
    ///
    /// This protocol is designed for **small, fixed-vocabulary attributes**.
    /// Each attribute value is stored as an `Int64` raw value, so attributes occupy minimal
    /// space (one inline slot without heap allocation for the common single-attribute case).
    ///
    /// ## Examples
    ///
    /// ```swift
    /// public enum Priority: Int64, Sendable, Logger.MetadataValueAttributes.Attribute {
    ///     case low = 1
    ///     case high = 2
    /// }
    /// ```
    ///
    /// ```swift
    /// public struct Priority: Sendable, Hashable, Logger.MetadataValueAttributes.Attribute {
    ///     public let rawValue: Int64
    ///     public init(rawValue: Int64) {
    ///         self.rawValue = rawValue
    ///     }
    ///     public static let low = Priority(rawValue: 1)
    ///     public static let high = Priority(rawValue: 2)
    /// }
    /// ```
    public protocol Attribute: Sendable,
        RawRepresentable where RawValue == Int64 {}
}
```

Each conforming type serves as both the key and the value for an attribute. The metatype (`Key.Type`) identifies
the attribute at runtime — no coordination between packages is needed.

#### `MetadataValueAttributes`

```swift
extension Logger {
    /// Attributes that can be associated with metadata values.
    public struct MetadataValueAttributes: Sendable, Equatable, ExpressibleByArrayLiteral {
        /// Nested ``Attribute`` protocol (see above).
        public protocol Attribute: Sendable, RawRepresentable where RawValue == Int64 {}

        /// Create empty metadata value attributes.
        public init()

        /// Create metadata value attributes from an array literal of attribute values.
        ///
        /// ```swift
        /// let attrs: MetadataValueAttributes = [Sensitivity.sensitive, ValueType.int64]
        /// ```
        public init(arrayLiteral elements: any Attribute...)

        /// Get or set a custom attribute by its type.
        ///
        /// - Parameter attribute: The metatype of the attribute to access.
        /// - Returns: The attribute value, or `nil` if not set.
        public subscript<Attr: Attribute>(attribute: Attr.Type) -> Attr? { get set }
    }
}
```

The common case (0–1 attributes) avoids heap allocation. `Equatable` compares entries as an unordered set (O(n²),
n typically 0–2).

#### `MetadataValue.attributes` computed property

```swift
extension Logger.MetadataValue {
    /// The attributes associated with this metadata value, if any.
    ///
    /// **Getting:** When the value is a ``stringConvertible(_:)`` wrapping an attributed carrier,
    /// returns that carrier's attributes. For all other cases returns empty attributes.
    ///
    /// **Setting:** Replaces the value with `.stringConvertible(AttributedStringCarrier(...))`
    /// carrying the given attributes. For `.string` and `.stringConvertible` cases the string
    /// representation is preserved. For `.dictionary` and `.array` cases the setter is a no-op.
    public var attributes: Logger.MetadataValueAttributes { get set }
}
```

This is the handler-side read/write API. Internally, the `Logging` module stores attributes in an internal
`AttributedStringCarrier` class wrapped by `.stringConvertible`. The getter check is a concrete type comparison — no
protocol conformance lookup. Users attach attributes via string interpolation and read them via this property. The
setter allows handlers to modify or add attributes as they process metadata values through the handler chain.

#### `MetadataValue.attributed` factory method

```swift
extension Logger.MetadataValue {
    /// Creates an attributed metadata value from a string-convertible value and attributes.
    public static func attributed(
        _ value: some CustomStringConvertible & Sendable,
        attributes: Logger.MetadataValueAttributes
    ) -> Self
}
```

This convenience factory behaves like an additional enum case, producing a
`.stringConvertible(AttributedStringCarrier(...))` value without requiring string interpolation:

```swift
let value: Logger.MetadataValue = .attributed(userId, attributes: [Sensitivity.sensitive])
```

#### String interpolation

`MetadataValue` replaces its empty `ExpressibleByStringInterpolation` conformance with a custom `StringInterpolation`
type. When no attributes are specified, the result is `.string(...)` — identical to the previous behavior. When any
interpolation segment specifies attributes, the result is `.stringConvertible(...)` wrapping the internal carrier:
```swift
extension Logger.MetadataValue: ExpressibleByStringInterpolation {
    /// Custom string interpolation that optionally captures metadata attributes.
    ///
    /// When no attributes are specified, produces `.string(...)` — identical to the
    /// default `DefaultStringInterpolation` behavior. When any interpolation segment
    /// specifies attributes (via the `attributes:` parameter), the result is
    /// `.stringConvertible(AttributedStringCarrier(...))` carrying both the string
    /// and the accumulated attributes.
    ///
    /// Attribute packages (like `LoggingAttributes`) can add overloads with
    /// domain-specific parameters (e.g. `sensitivity:`) that call through to
    /// `appendInterpolation(_:attributes:)`.
    public struct StringInterpolation: StringInterpolationProtocol, Sendable {
        public init(literalCapacity: Int, interpolationCount: Int)
        public mutating func appendLiteral(_ literal: String)

        /// Interpolation with a custom attributes closure.
        ///
        /// When called, the result will be `.stringConvertible(AttributedStringCarrier(...))`
        /// instead of `.string(...)`.
        ///
        /// ```swift
        /// "\(userId, attributes: { $0[MyAttribute.self] = .flagged })"
        /// ```
        public mutating func appendInterpolation<T>(
            _ value: T,
            attributes: @Sendable (inout Logger.MetadataValueAttributes) -> Void
        ) where T: CustomStringConvertible & Sendable

        /// Plain interpolation without attributes.
        public mutating func appendInterpolation<T>(
            _ value: T
        ) where T: CustomStringConvertible & Sendable

        /// Fallback interpolation for types that are not `CustomStringConvertible`.
        public mutating func appendInterpolation<T>(
            _ value: T
        ) where T: Sendable

        /// Unconstrained fallback for non-`Sendable` types.
        ///
        /// The value is immediately converted to `String` via `String(describing:)` and never
        /// stored, so no `Sendable` safety issue arises. This overload exists so that interpolating
        /// a non-`Sendable` type into a `MetadataValue` continues to compile, matching the behavior
        /// of `DefaultStringInterpolation`.
        public mutating func appendInterpolation<T>(
            _ value: T
        )
    }

    /// Creates a metadata value from string interpolation.
    ///
    /// If any interpolation segment specified attributes, the result is
    /// `.stringConvertible(AttributedStringCarrier(...))`. Otherwise, `.string(...)`.
    public init(stringInterpolation: StringInterpolation)
}
```

Attribute packages add ergonomic overloads that wrap the closure-based method — for example:

```swift
extension Logger.MetadataValue.StringInterpolation {
    public mutating func appendInterpolation<T: CustomStringConvertible & Sendable>(
        _ value: T, sensitivity: Sensitivity
    ) {
        self.appendInterpolation(value, attributes: { $0[Sensitivity.self] = sensitivity })
    }
}
```

This enables the clean call-site syntax: `"\(userId, sensitivity: .sensitive)"`.

When a single `MetadataValue` contains multiple interpolated segments with different attributes, the merging behavior
is defined by each attribute's `appendInterpolation` implementation.

#### No changes to `Logger`, `LogHandler`, `LogEvent`, or `MetadataProvider`

Unlike earlier iterations of this proposal, the piggyback approach requires **no new API** on `Logger`, `LogHandler`,
`LogEvent`, or `MetadataProvider`:

- **`Logger`**: No new `attributedMetadata:` log method overloads. Attributes flow through the existing `metadata:`
  parameter.
- **`LogHandler`**: No new protocol requirements. No `attributedMetadata` property, no `attributedMetadataKey`
  subscript. Existing handlers work unchanged.
- **`LogEvent`**: No dual storage. `metadata` remains a simple `Logger.Metadata?` stored property. Values inside the
  dictionary may carry attributes, but `LogEvent` is unaware of this.
- **`MetadataProvider`**: No `.attributed()` factory or `getAttributedMetadata()` method. Providers return
  `Logger.Metadata` where values may carry attributes via string interpolation.

### API stability

- **Existing `Logger` users.** No changes to existing API. The same `metadata:` parameter carries attributed values.
- **Existing `LogHandler` implementations.** No protocol changes. Handlers that only call `.description` on metadata
  values see the string as before — attributes are invisible. Handlers that want to inspect attributes read
  `value.attributes`.

### Future directions

- **Attribute-aware equality.** The current `MetadataValue` equality for `.stringConvertible` values compares only
  `.description`, ignoring attributes. Two values with the same text but different attributes compare as equal. A
  future proposal could refine equality to include attributes when both sides carry them, but this
  requires careful consideration of backward compatibility since existing code may rely on description-only equality.
- **Metadata values sensitivity attribute.** A separate ecosystem package could add the sensitivity attribute,
  allowing compatible `LogHandler` to redact values in certain situations.
- **Typed metadata values.** A separate ecosystem package could add typed variants to `MetadataValue` (`.int64`,
  `.double`, `.bool`), reducing the need for attributes that compensate for stringly-typed metadata.

### Alternatives considered

#### Wrapper type with parallel API ("attributed metadata value" approach)

Introduce an `AttributedMetadataValue` struct wrapping `MetadataValue` + `MetadataValueAttributes`, a separate
`AttributedMetadata` typealias, new `attributedMetadata:` overloads on all Logger methods, dual storage in
`LogEvent`, and new `LogHandler` protocol requirements. This was the design in the first iteration of this proposal.

It provides strong type separation but creates a parallel API surface: 7 new convenience methods, new protocol
requirements with default implementations, dual `MetadataStorage` enum in `LogEvent`, and `mapValues` bridging
between plain and attributed representations. The piggyback approach achieves the same functionality by embedding
attributes inside `MetadataValue` via the existing `.stringConvertible` case, avoiding the parallel universe
entirely.

#### Sidecar dictionary on `LogEvent`

Store attributes in a separate `[String: MetadataValueAttributes]` dictionary on `LogEvent`, keyed by the same
metadata keys. This keeps `MetadataValue` unchanged but introduces sync issues — attributes can drift out of sync
with metadata keys when handlers merge or transform metadata. The piggyback approach avoids this by co-locating
attributes with the value they describe.

#### New enum case on `MetadataValue`

Add a dedicated `.attributed(String, MetadataValueAttributes)` case to the `MetadataValue` enum. This would make
attributes statically visible in the type — no runtime downcast needed, and `switch` statements would handle
attributed values explicitly. However, `MetadataValue` is a public enum that is exhaustively matched by every
`LogHandler` in the ecosystem. Adding a case is source-breaking: every existing handler's `switch` over
`MetadataValue` would fail to compile until updated. The piggyback approach avoids this by reusing the existing
`.stringConvertible` case, which handlers already handle.

#### Concrete stored property instead of extensible mechanism

Add concrete stored properties (e.g., `var sensitivity: Sensitivity?`) on `MetadataValueAttributes`. Simpler, but
closed — chained handlers cannot define their own attributes. The extensible mechanism keeps the core `Logging` module
free of domain-specific attributes.

#### Bitmask storage

Pack attribute values into an inline `UInt64` using declared bit offsets. O(1) access, but requires authors to
coordinate bit layout and risks collisions between independent packages.

#### Pure dynamic array storage

Use a dynamic array for all attributes with no inline slot. Simpler, but requires heap allocation even for the first
attribute.

#### No per-value attributes

Rely on handler-side configuration (key-name-based rules). Simpler, but fragile — rules break when keys are renamed,
require coordination across all dependencies, and are invisible at the call site.

### Example attributes

The following examples illustrate how ecosystem packages could define attributes using the
`Logger.MetadataValueAttributes.Attribute` protocol. These are not part of this proposal.

#### Sensitivity

A sensitivity attribute for marking metadata values that contain private or personally identifiable information:

```swift
public enum Sensitivity: Int64, Logger.MetadataValueAttributes.Attribute, Sendable {
    case sensitive = 1
    case `public` = 2
}

extension Logger.MetadataValue.StringInterpolation {
    public mutating func appendInterpolation<T: CustomStringConvertible & Sendable>(
        _ value: T, sensitivity: Sensitivity
    ) {
        self.appendInterpolation(value, attributes: { $0[Sensitivity.self] = sensitivity })
    }
}

logger.info("Request", metadata: [
    "method": "\(req.method)",
    "user_id": "\(req.userId, sensitivity: .sensitive)",
])
```

A handler reads the attribute:

```swift
for (key, value) in mergedMetadata {
    if value.attributes[Sensitivity.self] == .sensitive {
        // redact this value
    }
}
```

#### Value type hint

A type hint for structured logging backends that benefit from native types:

```swift
public enum ValueType: Int64, Logger.MetadataValueAttributes.Attribute, Sendable {
    case string = 1
    case int64 = 2
    case double = 3
    case bool = 4
}

extension Logger.MetadataValue.StringInterpolation {
    public mutating func appendInterpolation<T: CustomStringConvertible & Sendable>(
        _ value: T, valueType: ValueType
    ) {
        self.appendInterpolation(value, attributes: { $0[ValueType.self] = valueType })
    }
}

logger.info("Metrics", metadata: [
    "latency_ms": "\(latency, valueType: .double)",
    "retry_count": "\(retries, valueType: .int64)",
])
```

#### Metric extraction

A metric attribute for a chained `LogHandler` that dual-writes to swift-metrics:

```swift
public enum MetricKind: Int64, Logger.MetadataValueAttributes.Attribute, Sendable {
    case counter = 1
    case gauge = 2
    case histogram = 3
}

extension Logger.MetadataValue.StringInterpolation {
    public mutating func appendInterpolation<T: CustomStringConvertible & Sendable>(
        _ value: T, metricKind: MetricKind
    ) {
        self.appendInterpolation(value, attributes: { $0[MetricKind.self] = metricKind })
    }
}

logger.info("Request completed", metadata: [
    "duration_ms": "\(duration, metricKind: .histogram)",
    "error_count": "\(errors, metricKind: .counter)",
])
```

#### Attribute-enriching intermediate handler

An intermediate `LogHandler` that reads and modifies attributes before forwarding to the next handler in the chain.
This pattern enables composable pipelines — for example, a handler that adds a default sensitivity to all metadata
values that do not already carry one:

```swift
struct DefaultSensitivityHandler: LogHandler {
    private var inner: any LogHandler

    // ... logLevel, metadata, metadataProvider forwarded to inner ...

    func log(event: LogEvent) {
        var mutatedEvent = event
        if var eventMetadata = event.metadata {
            for (key, _) in eventMetadata {
                if eventMetadata[key]?.attributes[Sensitivity.self] == nil {
                    eventMetadata[key]?.attributes = [Sensitivity.sensitive]
                }
            }
            mutatedEvent.metadata = eventMetadata
        }
        self.inner.log(event: mutatedEvent)
    }
}
```

With this handler in the chain, any metadata value without an explicit sensitivity annotation is treated as sensitive
by default — a "deny by default" policy that individual call sites can override with `.public`.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0005-log-event-LogHandler-API.md

# SLG-0005: `LogEvent`-based `LogHandler` API

Replace the flat-parameter `log(level:message:metadata:source:file:function:line:)` method on `LogHandler` with
`log(event: LogEvent)`, enabling forward-compatible evolution of the `LogHandler` interface without breaking existing
handler implementations.

## Overview

- Proposal: SLG-0005
- Author(s): [Vladimir Kukushkin](https://github.com/kukushechkin)
- Status: **Approved**
- Issue: [apple/swift-log#421](https://github.com/apple/swift-log/issues/421)
- Implementation:
    - [apple/swift-log#423](https://github.com/apple/swift-log/pull/423)
- Related links:
    - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)
    - [SLG-0003: Standardized Error Metadata via Logger Convenience](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/SLG-0003.md)

### Introduction

Introduce `LogEvent` that bundles all log-statement data, and replace the flat-parameter
`log(level:message:metadata:source:file:function:line:)` method on `LogHandler` with `log(event: LogEvent)`.

### Motivation

The `LogHandler` protocol currently requires implementing a single method with seven parameters:

```swift
func log(
    level: Logger.Level,
    message: Logger.Message,
    metadata: Logger.Metadata?,
    source: String,
    file: String,
    function: String,
    line: UInt
)
```

Every time a new piece of data needs to be forwarded from a log call site to the handler — an error, or a richer metadata type — the only options are:

- **Add a new parameter to the existing method.** This is a source-breaking change: every existing `LogHandler`
  implementation across the entire Swift ecosystem must be updated simultaneously.
- **Add a new `Logger` overload.** This requires 2 parallel APIs to coexist until the old one can be safely deprecated.

### Proposed solution

Introduce `LogEvent`, a `struct` that carries all the data associated with a single log statement. `LogEvent` is
a handler-side type: it is constructed by `Logger._log` and passed to `LogHandler`. The public `Logger` call-site
API — `logger.info(...)`, `logger.log(level:...)` and so on — is unchanged.

The `LogHandler` protocol gains a new required method `log(event: LogEvent)`, while the old flat-parameter method
is deprecated.

For existing handlers, a default implementation of `log(event:)` forwards to the old method, so no code changes
are required to keep existing implementations working. Handlers that want to take advantage of new fields added to
`LogEvent` in future proposals implement `log(event:)`.

### Detailed design

#### New `LogEvent` type

A new top-level `struct` in the `Logging` module:

```swift
public struct LogEvent: Sendable {
    public var level: Logger.Level
    public var message: Logger.Message
    private var _metadata: Logger.Metadata?
    public var metadata: Logger.Metadata? {
        get { self._metadata }
        set { self._metadata = newValue }
    }
    public var source: String
    public var file: String
    public var function: String
    public var line: UInt

    public init(
        level: Logger.Level,
        message: Logger.Message,
        metadata: Logger.Metadata?,
        source: String,
        file: String,
        function: String,
        line: UInt
    )
}
```

#### Updated `LogHandler` protocol

```swift
public protocol LogHandler: _SwiftLogSendableLogHandler {

    /// The library calls this method when a log handler must emit a log message.
    ///
    /// There is no need for the `LogHandler` to check if the level is above or below the configured `logLevel`
    /// as `Logger` already performed this check and determined that a message should be logged.
    ///
    /// - Parameter event: The log event containing the level, message, metadata, and source location.
    func log(event: LogEvent)

    /// Please do _not_ implement this method when you create a `LogHandler` implementation.
    /// Implement ``log(event:)`` instead.
    ///
    /// - Parameters:
    ///   - level: The log level of the message.
    ///   - message: The message to log. To obtain a `String` representation call `message.description`.
    ///   - metadata: The metadata associated to this log message.
    ///   - source: The source where the log message originated, for example the logging module.
    ///   - file: The file this log message originates from.
    ///   - function: The function this log message originates from.
    ///   - line: The line this log message originates from.
    @available(*, deprecated, renamed: "log(event:)")
    func log(
        level: Logger.Level,
        message: Logger.Message,
        metadata: Logger.Metadata?,
        source: String,
        file: String,
        function: String,
        line: UInt
    )
}
```

#### Default implementation

The default for `log(event:)` forwards all event fields to the old flat-parameter method,
preserving the behavior of existing handlers with no source changes required.

The default implementation is marked as deprecated to encourage handler authors to migrate to `log(event:)`.
Handlers that still rely on the forwarding bridge will see a deprecation warning at compile time, signaling that
they should adopt the new method directly.

```swift
extension LogHandler {
    @available(*, deprecated, message: "You should implement this method instead of using the default implementation")
    public func log(event: LogEvent) {
        self.log(
            level: event.level,
            message: event.message,
            metadata: event.metadata,
            source: event.source,
            file: event.file,
            function: event.function,
            line: event.line
        )
    }
}
```

### API stability

**For existing `Logger` users:** No changes. All existing call sites continue to compile and behave identically.

**For existing `LogHandler` implementations:** Implementations of
`log(level:message:metadata:source:file:function:line:)` continue to work without any source changes. The
default implementation of `log(event:)` forwards to the old method.

### Future directions

Future proposals can add new fields to `LogEvent` with sensible defaults — a richer metadata
type, or an attached `Error` (as a follow-up for [SLG-0003 proposal](https://forums.swift.org/t/proposal-slg-0003-standardized-error-metadata-via-logger-convenience/84518/39)) —
without touching the `LogHandler` protocol and without requiring any changes to
existing handler implementations. This is the primary motivation for this proposal.

The [SLG-0004 proposal](https://github.com/apple/swift-log/pull/416) introduces an `AttributedMetadata` type that wraps
`Logger.Metadata` with additional context. Adding it to `LogEvent` as a new optional field would be
source-compatible, and the existing `metadata` field could become a computed property that projects from
`attributedMetadata` when present, keeping old handlers fully functional without modification.

### Alternatives considered

#### Adding parameters with default values to the existing method

Adding new parameters with default values avoids introducing a new method, but still constitutes a
source-breaking change: every existing `LogHandler` implementation that implements the method explicitly
must add the new parameter. The whole point of this proposal is to avoid that pattern indefinitely.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-0006-task-local-logger.md

# SLG-0006: Task-local logger with automatic metadata propagation

Accumulate structured logging metadata across async call stacks using task-local storage.

## Overview

- Proposal: SLG-0006
- Author(s): [Vladimir Kukushkin](https://github.com/kukushechkin)
- Status: **Approved**
- Issue: [apple/swift-log#261](https://github.com/apple/swift-log/issues/261)
- Implementation: [apple/swift-log#459](https://github.com/apple/swift-log/pull/459)
- Feature flag: none
- Related links:
  - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)

### Introduction

This proposal adds task-local logger storage so that metadata can accumulate across async
call stacks without threading a `Logger` through every function signature.

### Motivation

Metadata propagation requires threading a logger through every layer. Every function on
the path from request ingress to the bottom of the call stack has to accept, mutate, and
forward a `Logger`:

```swift
func handleHTTPRequest(_ request: HTTPRequest, logger: Logger) async throws {
    var logger = logger
    logger[metadataKey: "request.id"] = "\(request.id)"
    try await processBusinessLogic(request, logger: logger)
}
```

Libraries must choose between polluting their public API with a `logger:` parameter and
losing all the caller's context. There is no third option today.

### Proposed solution

Use Swift's `@TaskLocal` to propagate a logger with accumulated metadata. The caller sets up
a scope, callees read `Logger.current`:

```swift
func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse {
    try await withLogger(mergingMetadata: ["request.id": "\(request.id)"]) { logger in
        logger.info("Handling request")  // Has request.id automatically
        return try await processRequest(request)  // no logger parameter needed
    }
}

func processRequest(_ request: HTTPRequest) async throws -> User {
    Logger.current.debug("Processing...")  // Has request.id automatically
    DatabaseClient.query(request.sqlQuery)
}
```

Libraries keep their APIs clean while still emitting logs with the caller's full context.
A library reads `Logger.current` directly — the caller's accumulated metadata flows in
automatically without a `logger:` parameter:

```swift
public struct DatabaseClient {
    public static func query(_ sql: String) async throws -> [Row] {
        Logger.current.debug("Executing", metadata: ["sql": "\(sql)"])
        return try await performQuery(sql)
    }
}
```

**Wiring it up with swift-service-lifecycle.** At the application entry point, bind the
bootstrapped logger once; service lifecycle propagates the task-local through the
`ServiceGroup` and into every task spawned from its `run()`:

```swift
@main
struct MyServer {
    static func main() async throws {
        let logger = Logger(label: "my-server")
        try await withLogger(logger) { _ in
            let serviceGroup = ServiceGroup(
                configuration: .init(
                    services: [HTTPServer(), BackgroundWorker()],
                    gracefulShutdownSignals: [.sigint],
                    cancellationSignals: [.sigterm],
                    logger: logger
                )
            )
            try await serviceGroup.run()
        }
    }
}
```

Metadata accumulates through nesting and task-local values propagate through structured
concurrency (`async let`, `withTaskGroup`, child `Task {}`). `Task.detached` does not
inherit context — capture the logger explicitly if needed.

### Detailed design

#### `Logger.current`

Returns the logger bound by the nearest enclosing `withLogger` scope. If none is active,
returns the process-wide unbound default: a `Logger(label: "")` constructed via
`LoggingSystem.factory` the first time the task-local is touched.

```swift
extension Logger {
    /// The current task-local logger.
    ///
    /// Returns the logger bound by the nearest enclosing ``withLogger(_:_:)`` scope.
    /// If none is active, returns the process-wide unbound default: a
    /// `Logger(label: "")` cached from the first time the task-local is touched.
    ///
    /// Task-local values propagate through structured concurrency (`async let`,
    /// `withTaskGroup`, child `Task {}`) but are **not** inherited by `Task.detached`.
    /// Capture the logger explicitly across detached boundaries.
    ///
    /// For many log calls in a tight scope, prefer extracting the logger once —
    /// either `let logger = Logger.current` or the closure's `logger` parameter
    /// from `withLogger { logger in ... }` — instead of re-reading the task-local
    /// on every call.
    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    public static var current: Logger { get }
}
```

The per-symbol `@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)` mirrors
`@TaskLocal`'s own runtime availability.

#### `withLogger` free functions

Three overload groups, each with sync and async variants. The closure receives the logger
as a parameter to avoid repeated task-local lookups inside the closure body.

**Bind a specific logger:**

```swift
/// Runs `operation` with `logger` bound to the task-local context.
///
/// Code called within `operation` can read the logger via ``Logger/current`` without an
/// explicit parameter. Binding a different logger with this overload **replaces** the
/// current task-local logger; any metadata accumulated by an outer
/// ``withLogger(mergingMetadata:_:)`` or
/// ``withLogger(logLevel:handler:metadata:_:)`` scope is not carried over.
///
/// This overload is the application-bootstrap binding mechanism: pass a `Logger`
/// constructed via ``Logger/init(label:)`` at your application entry point. Because
/// ``Logger/init(label:)`` consults ``LoggingSystem/factory``, the constructed `Logger`
/// only carries a useful handler once ``LoggingSystem/bootstrap(_:)`` has been called.
/// For mid-call-tree backend swaps that should work without bootstrap (tests, scoped
/// routing), use ``withLogger(logLevel:handler:metadata:_:)`` instead — it modifies the
/// current logger's handler in place without constructing a new one.
///
/// let logger = Logger(label: "app")
/// await withLogger(logger) { logger in
///     logger.info("Application started")
///     await handleRequests()  // reads Logger.current
/// }
///
/// - Parameters:
///   - logger: The logger to bind for the duration of `operation`.
///   - operation: The closure to run with `logger` bound. Receives `logger` as a
///     parameter so the body does not need to re-read ``Logger/current``.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public func withLogger<Result, Failure: Error>(
    _ logger: Logger,
    _ operation: (Logger) throws(Failure) -> Result
) throws(Failure) -> Result

/// Runs `operation` with `logger` bound to the task-local context. Async variant of
/// ``withLogger(_:_:)``; see that function for full semantics.
///
/// - Parameters:
///   - logger: The logger to bind for the duration of `operation`.
///   - operation: The async closure to run with `logger` bound. Receives `logger` as
///     a parameter so the body does not need to re-read ``Logger/current``.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public nonisolated(nonsending) func withLogger<Result, Failure: Error>(
    _ logger: Logger,
    _ operation: nonisolated(nonsending) (Logger) async throws(Failure) -> Result
) async throws(Failure) -> Result
```

**Append metadata for a scope:**

```swift
/// Runs `operation` with a copy of ``Logger/current`` that has `metadata` **layered on
/// top of** the inherited base metadata. Keys present in `metadata` override existing
/// keys with the same name; other inherited metadata is preserved. Handler and log
/// level are unchanged.
///
/// Use this overload at request boundaries and any point where context should
/// accumulate. Nested ``withLogger(mergingMetadata:_:)`` scopes layer on top of each
/// other.
///
/// withLogger(mergingMetadata: ["request.id": "\(request.id)"]) { logger in
///     logger.info("Handling request")
///     withLogger(mergingMetadata: ["user.id": "\(user.id)"]) { logger in
///         logger.info("Authenticated")  // sees both request.id and user.id
///     }
/// }
///
/// - Parameters:
///   - metadata: Metadata keys merged onto the inherited base metadata for the scope.
///     Keys override existing values with the same name.
///   - operation: The closure to run with the merged logger bound. Receives the
///     merged logger as a parameter.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public func withLogger<Result, Failure: Error>(
    mergingMetadata metadata: @autoclosure () -> Logger.Metadata,
    _ operation: (Logger) throws(Failure) -> Result
) throws(Failure) -> Result

/// Runs `operation` with a copy of ``Logger/current`` that has `metadata` layered on
/// top of the inherited base metadata. Async variant of ``withLogger(mergingMetadata:_:)``;
/// see that function for full semantics.
///
/// - Parameters:
///   - metadata: Metadata keys merged onto the inherited base metadata for the scope.
///     Keys override existing values with the same name.
///   - operation: The async closure to run with the merged logger bound. Receives the
///     merged logger as a parameter.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public nonisolated(nonsending) func withLogger<Result, Failure: Error>(
    mergingMetadata metadata: @autoclosure () -> Logger.Metadata,
    _ operation: nonisolated(nonsending) (Logger) async throws(Failure) -> Result
) async throws(Failure) -> Result
```

**Replace aspects of the current logger:**

```swift
/// Runs `operation` with a copy of ``Logger/current`` whose aspects are **replaced** by
/// the provided arguments. `nil` parameters leave the corresponding aspect unchanged.
///
/// Unlike ``withLogger(mergingMetadata:_:)``, which layers metadata on top of the
/// inherited base, this overload **replaces** the base metadata when `metadata` is
/// non-nil. Pass `metadata: [:]` to wipe the inherited metadata entirely.
///
/// With no arguments, this overload re-binds ``Logger/current`` unchanged — a convenient
/// way to extract it into a local variable for repeated use inside the closure.
///
/// withLogger(mergingMetadata: ["request.id": "\(request.id)"]) { _ in
///     // Background job: start a scope with metadata unrelated to the request.
///     withLogger(metadata: ["job.id": "\(job.id)"]) { logger in
///         logger.info("running")  // metadata: job.id only — request.id wiped
///     }
/// }
///
/// - Parameters:
///   - logLevel: When non-nil, replaces the current log level for the scope. When
///     `nil`, the inherited log level is preserved.
///   - handler: When non-nil, replaces the current logger's handler for the scope.
///     Useful in tests to route logs through an `InMemoryLogHandler` or similar while
///     keeping the caller's label. When `nil`, the inherited handler is preserved.
///   - metadata: When non-nil, replaces the handler's base metadata dictionary for
///     the scope. Pass `[:]` to erase inherited metadata; pass a fresh dictionary to
///     start a scope from a known state. When `nil`, the inherited base metadata is
///     preserved.
///   - operation: The closure to run with the modified logger bound. Receives the
///     modified logger as a parameter.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public func withLogger<Result, Failure: Error>(
    logLevel: Logger.Level? = nil,
    handler: (any LogHandler)? = nil,
    metadata: @autoclosure () -> Logger.Metadata? = nil,
    _ operation: (Logger) throws(Failure) -> Result
) throws(Failure) -> Result

/// Runs `operation` with a copy of ``Logger/current`` whose aspects are replaced by
/// the provided arguments. Async variant of
/// ``withLogger(logLevel:handler:metadata:_:)``; see that function for full semantics.
///
/// - Parameters:
///   - logLevel: When non-nil, replaces the current log level for the scope. When
///     `nil`, the inherited log level is preserved.
///   - handler: When non-nil, replaces the current logger's handler for the scope.
///     Useful in tests to route logs through an `InMemoryLogHandler` or similar while
///     keeping the caller's label. When `nil`, the inherited handler is preserved.
///   - metadata: When non-nil, replaces the handler's base metadata dictionary for
///     the scope. Pass `[:]` to erase inherited metadata; pass a fresh dictionary to
///     start a scope from a known state. When `nil`, the inherited base metadata is
///     preserved.
///   - operation: The async closure to run with the modified logger bound. Receives
///     the modified logger as a parameter.
/// - Returns: The value returned by `operation`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@inlinable
public nonisolated(nonsending) func withLogger<Result, Failure: Error>(
    logLevel: Logger.Level? = nil,
    handler: (any LogHandler)? = nil,
    metadata: @autoclosure () -> Logger.Metadata? = nil,
    _ operation: nonisolated(nonsending) (Logger) async throws(Failure) -> Result
) async throws(Failure) -> Result
```

Calling `withLogger { logger in ... }` with no arguments is the single-lookup idiom for
extracting `Logger.current` into a local variable for repeated use inside the closure —
analogous to `withSpan { span in ... }` in `swift-distributed-tracing`.


#### Relationship to `MetadataProvider`

Metadata merged via `withLogger(mergingMetadata:)` is written into the handler's base
metadata on the per-task logger copy. If a handler has a `MetadataProvider` attached — for
example a bootstrapped OpenTelemetry provider that reads `trace.id` from `ServiceContext` —
the provider still runs at log-emission time and follows the handler's conventional merge
order (base < provider < per-statement).

This proposal does **not** add a `metadataProvider:` parameter to the modifying overload.
Application code that wants to swap the provider for a scope should construct a new `Logger`
and use the binding overload. Keeping the two concerns separate avoids shipping the
replace-vs-compose question as part of the core API.

### API stability

**For existing `Logger` users:** no changes. The new API is purely additive.

**For existing `LogHandler` implementations:** no changes required. Task-local loggers use
the same `LogHandler` interface; no new protocol requirements are added.

**Package platform minimums.** `Package.swift` intentionally does not gain a `platforms:`
clause. Each new symbol carries `@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)`
to match `@TaskLocal`'s runtime availability. Callers targeting older deployment versions
keep working; they just can't call the task-local API.

**ABI and resilience.** The `withLogger` overloads are `@inlinable`: their bodies are thin
glue — read `Logger.current`, copy-and-modify the logger, then bind it for the scope — so
inlining them removes a cross-module call at each scope entry. The internal
`withTaskLocalLogger` bridge over `TaskLocal.withValue` is `@usableFromInline` (not
`@inlinable`), letting the inlinable bodies reach it without exposing the `@TaskLocal`
storage. That storage, the bridge, and `Logger.current` all stay non-`@inlinable`, so the
genuinely evolvable state — how the task-local is stored and how the unbound default is
resolved — remains behind a resilient boundary and can change without breaking
already-compiled clients. Only the small, settled control flow of the wrappers is committed
to ABI.

**Source compatibility caveat.** The name `withLogger` is added at the top-level namespace.
Codebases that previously defined their own free `withLogger` function may need to
fully-qualify calls (`Logging.withLogger(...)`) or rename their own.

### Future directions

- **`Logger.withMetadata(merging:) -> Logger` and `Logger.withMetadata(replacing:) -> Logger`
  value-level instance methods.** Construct a derived `Logger` value (not a scope) with
  metadata layered onto or wholly replacing the receiver's metadata. The scope-level
  equivalents (`withLogger(mergingMetadata:)` and `withLogger(metadata:)`) ship in this
  proposal; the value-level forms are useful for per-statement metadata that should not
  propagate via the task-local — for example, a library that wants to stamp a key on its
  own log lines without pushing it onto `Logger.current` (avoiding leakage into downstream
  callees).

### Alternatives considered

#### Task-local metadata dictionary instead of task-local logger

Make only the metadata dictionary task-local, so ad-hoc `Logger(label:)` calls automatically
merge it. Rejected because it changes default behavior for all existing logger creation
(breaking semantic change) and overlaps with `swift-distributed-tracing`'s
`ServiceContext`.

#### Static methods on `Logger` instead of free functions

`Logger.withCurrent(...)` instead of `withLogger(...)`. Rejected — inconsistent with
`withSpan(...)` from `swift-distributed-tracing` and `withMetricsFactory(...)` from
`swift-metrics`. Free functions follow the established ecosystem convention.

#### Use `ServiceContext` from swift-distributed-tracing instead of a new task-local

Store metadata in the existing `ServiceContext` — propagated by `swift-service-context`,
the leaf package already designed as a shared propagation primitive — rather than
introducing a second `@TaskLocal`. Rejected because the two propagation channels serve
different use cases: `ServiceContext` carries *distributed* correlation (trace/span IDs,
baggage) that flows across process boundaries, while `withLogger(mergingMetadata:)`
accumulates *local* per-scope logger configuration (log level, handler, and application
metadata like `request.id`) that is meaningful only within a single process. The
task-local logger also carries more than metadata — it holds the `LogHandler`, log
level, and label, none of which belong in a context intended for cross-process propagation.
The existing `MetadataProvider` already bridges the two systems at log-emission time:
handlers read trace/span IDs from `ServiceContext` without swift-log depending on it.

#### No closure parameter — require `Logger.current` inside the closure

`withLogger(logger) { ... }` without a parameter, reading `Logger.current` inside. Rejected
because passing the logger avoids repeated task-local lookups, follows the `withSpan`
convention, and makes it obvious at the call site which logger is in use.

#### Not propagating metadata across module boundaries by default

Expose a public `TaskLocal<Logger>` extension surface so that each module can declare
its own scoped logger and stay structurally isolated from other modules — pushes on one
module's task-local would be invisible to another's reads. Rejected for two reasons.

First, the public surface cost: `@TaskLocal`-declared task-locals require the `$logger`
projection syntax for the scope methods (`MyApp.$logger.withMetadata(merging: …) { … }`),
which is unfamiliar and easy to get wrong; alternatively, exposing `TaskLocal<Logger>`
typed properties would leak a stdlib type into the swift-log surface that we'd rather
keep internal. Hiding the task-local behind `Logger.current` and the `withLogger` free
functions trades isolation for a cleaner API.

Second, the leak case is disciplinable. The only way library code propagates its own
metadata downstream through `Logger.current` is by calling `withLogger(mergingMetadata:)`
or `withLogger(metadata:)`. The documented contract is that those overloads are
application-side APIs: libraries read `Logger.current` and use per-statement `metadata:`
or a local copy for their own context; they do not push. Per-module isolation would make
the leak impossible *by construction* even when discipline fails, but it would also
fragment cross-cutting metadata propagation (`request.id` accumulated at the application
layer would not appear on library log lines without an explicit bridge from one
task-local to another) — which is the propagation we most want by default.

If structural isolation turns out to matter for specific use cases, the public
`TaskLocal<Logger>` extension surface can be added as a non-breaking follow-up.

#### Use another third-party dependency management package

Swift packages ecosystem has packages for managing dependencies, one of those can
be used to handle a task-local `Logger` instance. Rejected because if a library
wants to benefit from a common logger without adding it to its API surface,
it would have to depend on that extra package, effectively imposing that choice on
the application. But the dependency mechanism is an application-level decision,
an app may prefer a different approach, which leads to fragmentation or extra solutions
to bridge them.

#### Let applications create their own task-local logger objects

Applications can declare their own task-local `Logger` instance and API explicitly,
or via another dependency mechanism package. Rejected because it leaves library
dependencies out of the picture and hurts discoverability and composability
of the solution.

---

## File: Sources/Logging/Docs.docc/Proposals/SLG-NNNN.md

# SLG-NNNN: Feature name

Feature abstract – a one sentence summary.

## Overview

- Proposal: SLG-NNNN
- Author(s): [Author 1](https://github.com/swiftdev), [Author 2](https://github.com/swiftdev)
- Status: **Awaiting Review**
- Issue: [apple/swift-log#1](https://github.com/apple/swift-log/issues/1)
- Implementation:
    - [apple/swift-log#1](https://github.com/apple/swift-log/pull/1)
- Feature flag: `proposalNNNN`
- Related links:
    - [Lightweight proposals process description](https://github.com/apple/swift-log/blob/main/Sources/Logging/Docs.docc/Proposals/Proposals.md)

### Introduction

A short, one-sentence overview of the feature or change.

### Motivation

Describe the problems that this proposal aims to address, and what workarounds adopters have to employ currently, if any.

### Proposed solution

Describe your solution to the problem. Provide examples and describe how they work. Show how your solution is better than current workarounds.

This section should focus on what will change for the _adopters_ of SwiftLog.

### Detailed design

Describe the implementation of the feature, a link to a prototype implementation is encouraged here.

This section should focus on what will change for the _contributors_ to SwiftLog.

### API stability

Discuss the API implications, making sure to considering all of:
- existing ``Logger`` users,
- existing ``LogHandler`` implementations.

### Future directions

Discuss any potential future improvements to the feature.

### Alternatives considered

Discuss the alternative solutions considered, even during the review process itself.

---

## File: Sources/Logging/Docs.docc/Reference/LogEvent.md

# ``Logging/LogEvent``

## Topics

### Creating a log event

- ``init(level:message:metadata:source:file:function:line:)``
- ``init(level:message:error:metadata:source:file:function:line:)``

### Inspecting a log event

- ``level``
- ``message``
- ``error``
- ``metadata``
- ``source``
- ``file``
- ``function``
- ``line``

---

## File: Sources/Logging/Docs.docc/Reference/Logger-Level.md

# ``Logging/Logger/Level``

## Topics

### Log Levels

- ``trace``
- ``debug``
- ``info``
- ``notice``
- ``warning``
- ``error``
- ``critical``

---

## File: Sources/Logging/Docs.docc/Reference/Logger-Message.md

# ``Logging/Logger/Message``

## Topics

### Creating a log message

- ``init(stringLiteral:)``

### Inspecting a log message

- ``description``

---

## File: Sources/Logging/Docs.docc/Reference/Logger-MetadataProvider.md

# ``Logging/Logger/MetadataProvider``

## Topics

### Creating a metadata provider

- ``init(_:)``

### Invoking the provider

- ``get()``

### Merging metadata

- ``multiplex(_:)``

---

## File: Sources/Logging/Docs.docc/Reference/Logger-MetadataValue.md

# ``Logging/Logger/MetadataValue``

## Topics

### Types of metadata values

- ``string(_:)``
- ``stringConvertible(_:)``
- ``array(_:)``
- ``dictionary(_:)``

### Working with attributes

- ``attributed(_:attributes:)``
- ``attributes``

---

## File: Sources/Logging/Docs.docc/Reference/Logger.md

# ``Logging/Logger``

## Topics

### Creating Loggers

- ``init(label:)``
- ``init(label:metadataProvider:)``
- ``init(label:factory:)-9uiy2``
- ``init(label:factory:)-6iktu``
- ``MetadataProvider``

### Task-local logger

- ``current``
- ``withLogger(_:_:)-3dt06``
- ``withLogger(_:_:)-70nli``
- ``withLogger(mergingMetadata:_:)-2c7dy``
- ``withLogger(mergingMetadata:_:)-3eduo``
- ``withLogger(logLevel:handler:metadata:_:)-2pd9p``
- ``withLogger(logLevel:handler:metadata:_:)-5nmw6``

### Sending trace log messages

- ``trace(_:metadata:file:function:line:)``
- ``trace(_:metadata:source:file:function:line:)``
- ``trace(_:error:metadata:source:file:function:line:)``

### Sending debug log messages

- ``debug(_:metadata:file:function:line:)``
- ``debug(_:metadata:source:file:function:line:)``
- ``debug(_:error:metadata:source:file:function:line:)``

### Sending info log messages

- ``info(_:metadata:file:function:line:)``
- ``info(_:metadata:source:file:function:line:)``
- ``info(_:error:metadata:source:file:function:line:)``

### Sending notice log messages

- ``notice(_:metadata:file:function:line:)``
- ``notice(_:metadata:source:file:function:line:)``
- ``notice(_:error:metadata:source:file:function:line:)``

### Sending warning log messages

- ``warning(_:metadata:file:function:line:)``
- ``warning(_:metadata:source:file:function:line:)``
- ``warning(_:error:metadata:source:file:function:line:)``

### Sending error log messages

- ``error(_:metadata:file:function:line:)``
- ``error(_:metadata:source:file:function:line:)``
- ``error(_:error:metadata:source:file:function:line:)``

### Sending critical log messages

- ``critical(_:metadata:file:function:line:)``
- ``critical(_:metadata:source:file:function:line:)``
- ``critical(_:error:metadata:source:file:function:line:)``

### Sending general log messages

- ``log(level:_:metadata:file:function:line:)``
- ``log(level:_:metadata:source:file:function:line:)``
- ``log(level:_:error:metadata:source:file:function:line:)``
- ``Level``
- ``Message``
- ``Metadata``

### Adjusting logger metadata

- ``subscript(metadataKey:)``
- ``MetadataValue``

### Metadata attribute types

- ``MetadataValueAttributes``

### Inspecting a logger

- ``label``
- ``logLevel``
- ``handler``
- ``metadataProvider``

---

## File: Sources/Logging/Docs.docc/Reference/LoggingSystem.md

# ``Logging/LoggingSystem``

## Topics

### Initializing the logging system

- ``bootstrap(_:)``
- ``bootstrap(_:metadataProvider:)``

### Inspecting the logging system

- ``metadataProvider``

---

## File: Sources/Logging/Docs.docc/Reference/LogHandler.md

# ``Logging/LogHandler``

## Topics

### Sending log messages

- ``log(event:)-7x67``
- ``log(event:)-17f2r``
- ``log(level:message:metadata:source:file:function:line:)-8kgt7``
- ``log(level:message:metadata:source:file:function:line:)-69pez``
- ``log(level:message:metadata:file:function:line:)-89rya``
- ``log(level:message:metadata:file:function:line:)-1xdau``

### Updating metadata

- ``subscript(metadataKey:)``

### Inspecting a log handler

- ``logLevel``
- ``metadata``
- ``metadataProvider``

---

## File: Sources/Logging/Docs.docc/Reference/MultiplexLogHandler.md

# ``Logging/MultiplexLogHandler``

## Topics

### Creating a multiplex log handler

- ``init(_:)``
- ``init(_:metadataProvider:)``

### Sending log messages

- ``log(event:)``
- ``log(level:message:metadata:source:file:function:line:)``

### Updating metadata

- ``subscript(metadataKey:)``

### Inspecting a log handler

- ``logLevel``
- ``metadata``
- ``metadataProvider``

---

## File: Sources/Logging/Docs.docc/Reference/StreamLogHandler.md

# ``Logging/StreamLogHandler``

## Topics

### Creating a stream log handler

- ``standardOutput(label:)``
- ``standardOutput(label:metadataProvider:)``
- ``standardError(label:)``
- ``standardError(label:metadataProvider:)``
- ``init(label:stream:)``
- ``init(label:stream:metadataProvider:)``

### Sending log messages

- ``log(event:)``
- ``log(level:message:metadata:source:file:function:line:)``

### Updating metadata

- ``subscript(metadataKey:)``

### Inspecting a log handler

- ``logLevel``
- ``metadata``
- ``metadataProvider``

---

## File: Sources/Logging/Docs.docc/Reference/SwiftLogNoOpLogHandler.md

# ``Logging/SwiftLogNoOpLogHandler``

A log handler that silently discards all log messages.

## Topics

### Creating a Swift Log no-op log handler

- ``init()``
- ``init(_:)``

### Sending log messages

- ``log(event:)``
- ``log(level:message:metadata:source:file:function:line:)``
- ``log(level:message:metadata:file:function:line:)``

### Updating metadata

- ``subscript(metadataKey:)``

### Inspecting a log handler

- ``logLevel``
- ``metadata``
- ``metadataProvider``

---

## File: .github/dependabot.yml

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
    labels:
      - "semver/none"

---

## File: .github/ISSUE_TEMPLATE.md

### Expected behavior
_[what you expected to happen]_

### Actual behavior
_[what actually happened]_

### Steps to reproduce

1. ...
2. ...

### If possible, minimal yet complete reproducer code (or URL to code)

_[anything to help us reproducing the issue]_

### SwiftLog version/commit hash

_[the SwiftLog tag/commit hash]_

### Swift & OS version (output of `swift --version && uname -a`)

---

## File: .github/PULL_REQUEST_TEMPLATE.md

_[One line description of your change]_

### Motivation:

_[Explain here the context, and why you're making that change. What is the problem you're trying to solve.]_

### Modifications:

_[Describe the modifications you've done.]_

### Result:

_[After your change, what will change.]_

---

## File: .github/release.yml

changelog:
  categories:
    - title: SemVer Major
      labels:
        - ⚠️ semver/major
    - title: SemVer Minor
      labels:
        - 🆕 semver/minor
    - title: SemVer Patch
      labels:
        - 🔨 semver/patch
    - title: Other Changes
      labels:
        - semver/none

---

## File: .github/workflows/auto-release.yml

name: Auto Release

on:
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: read

jobs:
  auto-release:
    uses: apple/swift-temporal-sdk/.github/workflows/auto-release.yml@main

---

## File: .github/workflows/main.yml

name: Main

permissions:
  contents: read

on:
  push:
    branches: [main]
  schedule:
    - cron: "0 8,20 * * *"

jobs:
  unit-tests:
    name: Unit tests
    uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
    with:
      linux_6_1_enabled: false
      linux_6_4_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_1_enabled: false
      windows_6_2_enabled: true
      windows_6_3_enabled: true
      windows_6_4_enabled: true
      windows_nightly_next_enabled: true
      windows_nightly_main_enabled: true
      windows_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_4_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"

  macos-benchmarks:
    name: macOS benchmarks
    uses: apple/swift-nio/.github/workflows/macos_benchmarks.yml@main
    with:
      benchmark_package_paths: '["Benchmarks/NoTraits", "Benchmarks/MaxLogLevelWarning", "Benchmarks/MaxLogLevelNone"]'
      macos_swift_6_3_enabled: true
      macos_swift_6_4_enabled: true

  cxx-interop:
    name: Cxx interop
    uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main

  static-linux:
    name: Static Linux Swift SDK
    # Workaround https://github.com/nektos/act/issues/1875
    uses: apple/swift-nio/.github/workflows/static_sdk.yml@main

  macos-tests:
    name: macOS tests
    uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
    with:
      runner_pool: nightly
      build_scheme: swift-log-Package
      swift_6_1_enabled: false
      swift_6_2_enabled: true
      swift_6_3_enabled: true
      swift_6_4_enabled: true
      macos_xcode_test_enabled: true
      ios_xcode_test_enabled: false
      watchos_xcode_test_enabled: false
      tvos_xcode_test_enabled: false
      visionos_xcode_test_enabled: false

  wasm:
    name: Wasm Swift SDK
    uses: apple/swift-nio/.github/workflows/wasm_swift_sdk.yml@main
    with:
      nightly_next_enabled: false

  android:
    name: Android Swift SDK
    # Workaround https://github.com/nektos/act/issues/1875
    uses: apple/swift-nio/.github/workflows/android_swift_sdk.yml@main

  release-builds:
    name: Release builds
    uses: apple/swift-nio/.github/workflows/release_builds.yml@main
    with:
      windows_6_1_enabled: false
      windows_6_2_enabled: true
      windows_6_3_enabled: true
      windows_6_4_enabled: true
      windows_nightly_next_enabled: true
      windows_nightly_main_enabled: true

---

## File: .github/workflows/pull_request.yml

name: PR

permissions:
  contents: read

on:
  pull_request:
    types: [opened, reopened, synchronize]

jobs:
  soundness:
    name: Soundness
    uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.16
    with:
      license_header_check_project_name: "Swift Logging API"

  unit-tests:
    name: Unit tests
    uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
    with:
      linux_6_1_enabled: false
      linux_6_4_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_1_enabled: false
      windows_6_2_enabled: true
      windows_6_3_enabled: true
      windows_6_4_enabled: true
      windows_nightly_next_enabled: true
      windows_nightly_main_enabled: true
      windows_6_2_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_3_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_6_4_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
      windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"

  macos-benchmarks:
    name: macOS benchmarks
    uses: apple/swift-nio/.github/workflows/macos_benchmarks.yml@main
    with:
      benchmark_package_paths: '["Benchmarks/NoTraits", "Benchmarks/MaxLogLevelWarning", "Benchmarks/MaxLogLevelNone"]'
      macos_runner_pool: general
      macos_swift_6_3_enabled: true
      macos_swift_6_4_enabled: true

  cxx-interop:
    name: Cxx interop
    uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main

  static-linux:
    name: Static Linux Swift SDK
    # Workaround https://github.com/nektos/act/issues/1875
    uses: apple/swift-nio/.github/workflows/static_sdk.yml@main

  macos-tests:
    name: macOS tests
    uses: apple/swift-nio/.github/workflows/macos_tests.yml@main
    with:
      runner_pool: general
      build_scheme: swift-log-Package
      swift_test_enabled: true
      swift_6_1_enabled: false
      swift_6_2_enabled: true
      swift_6_3_enabled: true
      swift_6_4_enabled: true
      macos_xcode_test_enabled: true
      ios_xcode_test_enabled: false
      watchos_xcode_test_enabled: false
      tvos_xcode_test_enabled: false
      visionos_xcode_test_enabled: false

  android:
    name: Android Swift SDK
    # Workaround https://github.com/nektos/act/issues/1875
    uses: apple/swift-nio/.github/workflows/android_swift_sdk.yml@main

  release-builds:
    name: Release builds
    uses: apple/swift-nio/.github/workflows/release_builds.yml@main
    with:
      windows_6_1_enabled: false
      windows_6_2_enabled: true
      windows_6_3_enabled: true
      windows_6_4_enabled: true
      windows_nightly_next_enabled: true
      windows_nightly_main_enabled: true

  wasm:
    name: Wasm Swift SDK
    uses: apple/swift-nio/.github/workflows/wasm_swift_sdk.yml@main
    with:
      nightly_next_enabled: false

  cmake-lists:
    name: Check CMake Lists
    uses: apple/swift-nio/.github/workflows/cmake_tests.yml@main
    with:
      image: "swift:6.2-jammy"
      update_cmake_lists_config: >-
        {
          "targets": [
            { "name": "Logging", "type": "source", "exceptions": [] },
            { "name": "InMemoryLogging", "type": "source", "exceptions": [] }
          ]
        }

---

## File: .github/workflows/pull_request_label.yml

name: PR label

permissions:
  contents: read

on:
  pull_request:
    types: [labeled, unlabeled, opened, reopened, synchronize]

jobs:
  semver-label-check:
    name: Semantic version label check
    runs-on: ubuntu-latest
    timeout-minutes: 1
    steps:
      - name: Checkout repository
        uses: actions/checkout@v7
        with:
          persist-credentials: false
      - name: Check for Semantic Version label
        uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main

--- METRICS ---
- Files Extracted: 92
- Estimated Token Budget: ~43858 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/apple/swift-log
