diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 79d6c5b5..14894530 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,13 +31,20 @@ jobs: windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" benchmarks: - name: Benchmarks + name: Benchmarks (NoTraits) uses: apple/swift-nio/.github/workflows/benchmarks.yml@main with: - benchmark_package_path: "Benchmarks" + benchmark_package_path: "Benchmarks/NoTraits" macos_xcode_16_4_enabled: true macos_xcode_26_1_enabled: true + benchmarks-MaxLogLevelWarningBenchmarks-trait: + name: Benchmarks (MaxLogLevelWarning) + uses: apple/swift-nio/.github/workflows/benchmarks.yml@main + with: + benchmark_package_path: "Benchmarks/MaxLogLevelWarning" + macos_xcode_26_1_enabled: true + cxx-interop: name: Cxx interop uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 2da77582..34aae4dc 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -35,14 +35,22 @@ jobs: windows_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" benchmarks: - name: Benchmarks + name: Benchmarks (NoTraits) uses: apple/swift-nio/.github/workflows/benchmarks.yml@main with: - benchmark_package_path: "Benchmarks" + benchmark_package_path: "Benchmarks/NoTraits" macos_runner_pool: general macos_xcode_16_4_enabled: true macos_xcode_26_1_enabled: true + benchmarks-MaxLogLevelWarningBenchmarks-trait: + name: Benchmarks (MaxLogLevelWarning) + uses: apple/swift-nio/.github/workflows/benchmarks.yml@main + with: + benchmark_package_path: "Benchmarks/MaxLogLevelWarning" + macos_runner_pool: general + macos_xcode_26_1_enabled: true + cxx-interop: name: Cxx interop uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main diff --git a/.licenseignore b/.licenseignore index e77f8dec..c2f7e546 100644 --- a/.licenseignore +++ b/.licenseignore @@ -19,6 +19,8 @@ Package.swift **/Package.swift Package@-*.swift **/Package@-*.swift +Package@swift-*.swift +**/Package@swift-*.swift Package.resolved **/Package.resolved Makefile diff --git a/Benchmarks/Benchmarks/SwiftLogBenchmarks/SwiftLogBenchmarks.swift b/Benchmarks/Benchmarks/SwiftLogBenchmarks/SwiftLogBenchmarks.swift deleted file mode 100644 index 9af4eac7..00000000 --- a/Benchmarks/Benchmarks/SwiftLogBenchmarks/SwiftLogBenchmarks.swift +++ /dev/null @@ -1,57 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Logging API open source project -// -// Copyright (c) 2025 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 -// -//===----------------------------------------------------------------------===// - -import Benchmark -import Foundation -import Logging - -let benchmarks: @Sendable () -> Void = { - let iterations = 1_000_000 - let metrics: [BenchmarkMetric] = [.instructions, .objectAllocCount] - - // Take a pair of levels to cover all condition ranges between the active level and logged level. - let logLevelParameterization: [Logger.Level] = [.debug, .error] - for logLevel in logLevelParameterization { - for logLevelUsed in logLevelParameterization { - var logger = Logger(label: "BenchmarkRunner_\(logLevel)_\(logLevelUsed)") - - // Use a NoOpLogHandler to avoid polluting the logs - logger.handler = NoOpLogHandler(label: "NoOpLogHandler") - logger.logLevel = logLevel - - Benchmark( - "\(logLevelUsed)_log_with_\(logLevel)_log_level", - configuration: .init( - metrics: metrics, - maxIterations: iterations, - thresholds: [ - .instructions: BenchmarkThresholds( - relative: [ - .p90: 1.0 // we only record p90 - ] - ), - .objectAllocCount: BenchmarkThresholds( - absolute: [ - .p90: 0 // we only record p90 - ] - ), - ] - ) - ) { benchmark in - // This is what we actually benchmark - logger.log(level: logLevelUsed, "hello, benchmarking world") - } - } - } -} diff --git a/Benchmarks/MaxLogLevelWarning/.gitignore b/Benchmarks/MaxLogLevelWarning/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Benchmarks/MaxLogLevelWarning/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Benchmarks/MaxLogLevelWarning/Benchmarks/MaxLogLevelWarningBenchmarks/MaxLogLevelWarning.swift b/Benchmarks/MaxLogLevelWarning/Benchmarks/MaxLogLevelWarningBenchmarks/MaxLogLevelWarning.swift new file mode 100644 index 00000000..2719bfce --- /dev/null +++ b/Benchmarks/MaxLogLevelWarning/Benchmarks/MaxLogLevelWarningBenchmarks/MaxLogLevelWarning.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Logging API open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Benchmark +import BenchmarksFactory +import Foundation +import Logging + +public let benchmarks: @Sendable () -> Void = { + makeBenchmark(loggerLevel: .notice, logLevel: .notice) { logger in + logger.notice("hello, benchmarking world") + } + makeBenchmark(loggerLevel: .notice, logLevel: .notice, "_generic") { logger in + logger.log(level: .notice, "hello, benchmarking world") + } + makeBenchmark(loggerLevel: .warning, logLevel: .warning) { logger in + logger.warning("hello, benchmarking world") + } + makeBenchmark(loggerLevel: .warning, logLevel: .warning, "_generic") { logger in + logger.log(level: .warning, "hello, benchmarking world") + } +} diff --git a/Benchmarks/MaxLogLevelWarning/Package.swift b/Benchmarks/MaxLogLevelWarning/Package.swift new file mode 100644 index 00000000..d78b8754 --- /dev/null +++ b/Benchmarks/MaxLogLevelWarning/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MaxLogLevelWarning", + platforms: [ + .macOS(.v13) + ], + products: [ + .executable(name: "MaxLogLevelWarning", targets: ["MaxLogLevelWarning"]) + ], + dependencies: [ + // swift-log + .package( + path: "../../", + traits: ["MaxLogLevelWarning"] + ), + // Parent Benchmarks + .package(name: "Benchmarks", path: "../"), + .package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.29.6"), + ], + targets: [ + .executableTarget( + name: "MaxLogLevelWarning", + dependencies: [ + .product(name: "BenchmarksFactory", package: "Benchmarks"), + .product(name: "Benchmark", package: "package-benchmark"), + .product(name: "Logging", package: "swift-log"), + ], + path: "Benchmarks/MaxLogLevelWarningBenchmarks", + plugins: [ + .plugin(name: "BenchmarkPlugin", package: "package-benchmark") + ] + ) + ] +) diff --git a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level.p90.json similarity index 52% rename from Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json rename to Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level.p90.json index cf2eff81..0e1fbb2a 100644 --- a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json +++ b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level.p90.json @@ -1,4 +1,4 @@ { - "instructions" : 1045, + "instructions" : 589, "objectAllocCount" : 0 } \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level_generic.p90.json similarity index 52% rename from Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json rename to Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level_generic.p90.json index f625c783..0e1fbb2a 100644 --- a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_error_log_level.p90.json +++ b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.notice_log_with_notice_log_level_generic.p90.json @@ -1,4 +1,4 @@ { - "instructions" : 1126, + "instructions" : 589, "objectAllocCount" : 0 } \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level.p90.json similarity index 52% rename from Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json rename to Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level.p90.json index 2371eb9d..b356a714 100644 --- a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json +++ b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level.p90.json @@ -1,4 +1,4 @@ { - "instructions" : 3279, + "instructions" : 3009, "objectAllocCount" : 1 } \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level_generic.p90.json similarity index 52% rename from Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json rename to Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level_generic.p90.json index 2371eb9d..b356a714 100644 --- a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json +++ b/Benchmarks/MaxLogLevelWarning/Thresholds/Xcode 26.1/MaxLogLevelWarning.warning_log_with_warning_log_level_generic.p90.json @@ -1,4 +1,4 @@ { - "instructions" : 3279, + "instructions" : 3009, "objectAllocCount" : 1 } \ No newline at end of file diff --git a/Benchmarks/NoTraits/.gitignore b/Benchmarks/NoTraits/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Benchmarks/NoTraits/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Benchmarks/NoTraits/Benchmarks/NoTraitsBenchmarks/NoTraits.swift b/Benchmarks/NoTraits/Benchmarks/NoTraitsBenchmarks/NoTraits.swift new file mode 100644 index 00000000..b7d94ce3 --- /dev/null +++ b/Benchmarks/NoTraits/Benchmarks/NoTraitsBenchmarks/NoTraits.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Logging API open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Benchmark +import BenchmarksFactory +import Foundation +import Logging + +public let benchmarks: @Sendable () -> Void = { + makeBenchmark(loggerLevel: .error, logLevel: .error, "_generic") { logger in + logger.log(level: .error, "hello, benchmarking world") + } + makeBenchmark(loggerLevel: .error, logLevel: .debug, "_generic") { logger in + logger.log(level: .debug, "hello, benchmarking world") + } +} diff --git a/Benchmarks/NoTraits/Package.swift b/Benchmarks/NoTraits/Package.swift new file mode 100644 index 00000000..0e22b2a0 --- /dev/null +++ b/Benchmarks/NoTraits/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "NoTraits", + platforms: [ + .macOS(.v13) + ], + products: [ + .executable(name: "NoTraits", targets: ["NoTraits"]) + ], + dependencies: [ + // swift-log + .package( + path: "../../" + ), + // Parent Benchmarks + .package(name: "Benchmarks", path: "../"), + .package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.29.6"), + ], + targets: [ + .executableTarget( + name: "NoTraits", + dependencies: [ + .product(name: "BenchmarksFactory", package: "Benchmarks"), + .product(name: "Benchmark", package: "package-benchmark"), + .product(name: "Logging", package: "swift-log"), + ], + path: "Benchmarks/NoTraitsBenchmarks", + plugins: [ + .plugin(name: "BenchmarkPlugin", package: "package-benchmark") + ] + ) + ] +) diff --git a/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.debug_log_with_error_log_level_generic.p90.json b/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.debug_log_with_error_log_level_generic.p90.json new file mode 100644 index 00000000..564cc7ce --- /dev/null +++ b/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.debug_log_with_error_log_level_generic.p90.json @@ -0,0 +1,4 @@ +{ + "instructions" : 937, + "objectAllocCount" : 0 +} \ No newline at end of file diff --git a/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.error_log_with_error_log_level_generic.p90.json b/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.error_log_with_error_log_level_generic.p90.json new file mode 100644 index 00000000..244764f5 --- /dev/null +++ b/Benchmarks/NoTraits/Thresholds/Xcode 16.4/NoTraits.error_log_with_error_log_level_generic.p90.json @@ -0,0 +1,4 @@ +{ + "instructions" : 1718, + "objectAllocCount" : 0 +} \ No newline at end of file diff --git a/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.debug_log_with_error_log_level_generic.p90.json b/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.debug_log_with_error_log_level_generic.p90.json new file mode 100644 index 00000000..65b4d934 --- /dev/null +++ b/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.debug_log_with_error_log_level_generic.p90.json @@ -0,0 +1,4 @@ +{ + "instructions" : 855, + "objectAllocCount" : 0 +} \ No newline at end of file diff --git a/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.error_log_with_error_log_level_generic.p90.json b/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.error_log_with_error_log_level_generic.p90.json new file mode 100644 index 00000000..81b71486 --- /dev/null +++ b/Benchmarks/NoTraits/Thresholds/Xcode 26.1/NoTraits.error_log_with_error_log_level_generic.p90.json @@ -0,0 +1,4 @@ +{ + "instructions" : 1636, + "objectAllocCount" : 0 +} \ No newline at end of file diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 7f3082f5..fb5b51bf 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -1,30 +1,27 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( - name: "Benchmarks", + name: "BenchmarksFactory", platforms: [ .macOS(.v13) ], + products: [ + .library(name: "BenchmarksFactory", targets: ["BenchmarksFactory"]) + ], dependencies: [ - .package(path: "../"), .package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.29.6"), + .package(path: "../"), // swift-log + ], + targets: [ + .target( + name: "BenchmarksFactory", + dependencies: [ + .product(name: "Benchmark", package: "package-benchmark"), + .product(name: "Logging", package: "swift-log"), + ] + ) ] ) - -// Benchmark of SwiftLogBenchmarks -package.targets += [ - .executableTarget( - name: "SwiftLogBenchmarks", - dependencies: [ - .product(name: "Benchmark", package: "package-benchmark"), - .product(name: "Logging", package: "swift-log"), - ], - path: "Benchmarks/SwiftLogBenchmarks", - plugins: [ - .plugin(name: "BenchmarkPlugin", package: "package-benchmark") - ] - ) -] diff --git a/Benchmarks/Sources/BenchmarksFactory/MakeBenchmark.swift b/Benchmarks/Sources/BenchmarksFactory/MakeBenchmark.swift new file mode 100644 index 00000000..42342dc8 --- /dev/null +++ b/Benchmarks/Sources/BenchmarksFactory/MakeBenchmark.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Logging API open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Benchmark +import Foundation +import Logging + +public func makeBenchmark( + loggerLevel: Logger.Level, + logLevel: Logger.Level, + _ suffix: String = "", + _ body: @escaping (Logger) -> Void +) { + let iterations = 1_000_000 + let metrics: [BenchmarkMetric] = [.instructions, .objectAllocCount] + + var logger = Logger(label: "BenchmarkRunner_\(loggerLevel)_\(logLevel)") + + // Use a NoOpLogHandler to avoid polluting the logs + logger.handler = NoOpLogHandler(label: "NoOpLogHandler") + logger.logLevel = loggerLevel + + Benchmark( + "\(logLevel)_log_with_\(loggerLevel)_log_level\(suffix)", + configuration: .init( + metrics: metrics, + maxIterations: iterations, + thresholds: [ + .instructions: BenchmarkThresholds( + relative: [ + .p90: 1.0 // we only record p90 + ] + ), + .objectAllocCount: BenchmarkThresholds( + absolute: [ + .p90: 0 // we only record p90 + ] + ), + ] + ) + ) { _ in + body(logger) + } +} diff --git a/Benchmarks/Benchmarks/SwiftLogBenchmarks/NoOpLogHandler.swift b/Benchmarks/Sources/BenchmarksFactory/NoOpLogHandler.swift similarity index 100% rename from Benchmarks/Benchmarks/SwiftLogBenchmarks/NoOpLogHandler.swift rename to Benchmarks/Sources/BenchmarksFactory/NoOpLogHandler.swift diff --git a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json b/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json deleted file mode 100644 index 2371eb9d..00000000 --- a/Benchmarks/Thresholds/Xcode 16.4/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "instructions" : 3279, - "objectAllocCount" : 1 -} \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json b/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json deleted file mode 100644 index 34571876..00000000 --- a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.debug_log_with_debug_log_level.p90.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "instructions" : 3197, - "objectAllocCount" : 1 -} \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json b/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json deleted file mode 100644 index 34571876..00000000 --- a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_debug_log_level.p90.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "instructions" : 3197, - "objectAllocCount" : 1 -} \ No newline at end of file diff --git a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json b/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json deleted file mode 100644 index 34571876..00000000 --- a/Benchmarks/Thresholds/Xcode 26.1/SwiftLogBenchmarks.error_log_with_error_log_level.p90.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "instructions" : 3197, - "objectAllocCount" : 1 -} \ No newline at end of file diff --git a/Package.swift b/Package.swift index 93ee785a..a97eb055 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:6.1 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Logging API open source project @@ -21,6 +21,24 @@ let package = Package( .library(name: "Logging", targets: ["Logging"]), .library(name: "InMemoryLogging", targets: ["InMemoryLogging"]), ], + 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)"), + + // By default, no traits are enabled (all log levels available) + .default(enabledTraits: []), + ], targets: [ .target( name: "Logging", diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift new file mode 100644 index 00000000..a2cdb678 --- /dev/null +++ b/Package@swift-6.0.swift @@ -0,0 +1,55 @@ +// swift-tools-version:6.0 +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Logging API open source project +// +// Copyright (c) 2018-2019 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 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "swift-log", + products: [ + .library(name: "Logging", targets: ["Logging"]) + ], + targets: [ + .target( + name: "Logging", + dependencies: [] + ), + .testTarget( + name: "LoggingTests", + dependencies: ["Logging"] + ), + ] +) + +for target in package.targets { + var settings = target.swiftSettings ?? [] + settings.append(.enableExperimentalFeature("StrictConcurrency=complete")) + target.swiftSettings = settings +} + +// --- STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // +for target in package.targets { + switch target.type { + case .regular, .test, .executable: + var settings = target.swiftSettings ?? [] + // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md + settings.append(.enableUpcomingFeature("MemberImportVisibility")) + target.swiftSettings = settings + case .macro, .plugin, .system, .binary: + () // not applicable + @unknown default: + () // we don't know what to do here, do nothing + } +} +// --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // diff --git a/Sources/Logging/Docs.docc/BestPractices/001-ChoosingLogLevels.md b/Sources/Logging/Docs.docc/BestPractices/001-ChoosingLogLevels.md index 607d08d2..fce4d75d 100644 --- a/Sources/Logging/Docs.docc/BestPractices/001-ChoosingLogLevels.md +++ b/Sources/Logging/Docs.docc/BestPractices/001-ChoosingLogLevels.md @@ -9,7 +9,7 @@ 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 +### Motivation Libraries must be well-behaved across various use cases and cannot assume specific logging backend configurations. Using inappropriate log levels can @@ -51,7 +51,7 @@ Each level serves different purposes: - **Performance**: May impact performance; assume it won't be used in production. - **Content**: Internal state, detailed operation flows, diagnostic information. -##### Debug Level +##### 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. @@ -128,7 +128,7 @@ logger.info("Request failed") ```swift // ❌ Bad: Normal operations at info level flood production logs logger.info("HTTP request received") -logger.info("Database query executed") +logger.info("Database query executed") logger.info("Response sent") // ✅ Good: Use appropriate levels instead diff --git a/Sources/Logging/Docs.docc/DisableLogLevelsDuringCompilation.md b/Sources/Logging/Docs.docc/DisableLogLevelsDuringCompilation.md new file mode 100644 index 00000000..e6b97ed4 --- /dev/null +++ b/Sources/Logging/Docs.docc/DisableLogLevelsDuringCompilation.md @@ -0,0 +1,73 @@ +# Disable log levels during compilation + +SwiftLog provides compile-time traits to eliminate less severe log levels from +your binary reducing the runtime overhead. + +## 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") +``` diff --git a/Sources/Logging/Docs.docc/index.md b/Sources/Logging/Docs.docc/index.md index 64906c8d..f1727856 100644 --- a/Sources/Logging/Docs.docc/index.md +++ b/Sources/Logging/Docs.docc/index.md @@ -92,6 +92,7 @@ backend. - - +- ### Contributing diff --git a/Sources/Logging/Logging.swift b/Sources/Logging/Logging.swift index cada2417..5fc2248b 100644 --- a/Sources/Logging/Logging.swift +++ b/Sources/Logging/Logging.swift @@ -102,6 +102,8 @@ extension Logger { /// If the `logLevel` passed to this method is more severe than the `Logger`'s ``logLevel``, the library /// logs the message, otherwise nothing will happen. /// + /// NOTE: This method adds a constant overhead over calling level-specific methods. + /// /// - parameters: /// - level: The severity level of the `message`. /// - message: The message to be logged. The `message` parameter supports any string interpolation literal. @@ -123,6 +125,66 @@ extension Logger { file: String = #fileID, function: String = #function, line: UInt = #line + ) { + #if MaxLogLevelDebug || MaxLogLevelInfo || MaxLogLevelNotice || MaxLogLevelWarning || MaxLogLevelError || MaxLogLevelCritical || MaxLogLevelNone + // A constant overhead is added for dynamic log level calls if one of the traits is enabled. + // This allows picking the necessary implementation with compiled out body in runtime. + 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) + case .info: + self.info(message(), metadata: metadata(), source: source(), file: file, function: function, line: line) + case .notice: + self.notice(message(), metadata: metadata(), source: source(), file: file, function: function, line: line) + case .warning: + self.warning(message(), metadata: metadata(), source: source(), file: file, function: function, line: line) + case .error: + self.error(message(), metadata: metadata(), source: source(), file: file, function: function, line: line) + case .critical: + self.critical(message(), metadata: metadata(), source: source(), file: file, function: function, line: line) + } + #else + // If no logs are excluded in the compile time, we can avoid checking the log level that extra time and go log it. + self._log( + level: level, + message(), + metadata: metadata(), + source: source(), + file: file, + function: function, + line: line + ) + #endif + } + + /// 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. + /// - 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 + package 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 self.logLevel <= level { self.handler.log( @@ -220,7 +282,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelDebug && !MaxLogLevelInfo && !MaxLogLevelNotice && !MaxLogLevelWarning && !MaxLogLevelError && !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .trace, message(), metadata: metadata(), @@ -229,6 +292,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'trace' log level. @@ -281,7 +345,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelInfo && !MaxLogLevelNotice && !MaxLogLevelWarning && !MaxLogLevelError && !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .debug, message(), metadata: metadata(), @@ -290,6 +355,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'debug' log level. @@ -342,7 +408,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelNotice && !MaxLogLevelWarning && !MaxLogLevelError && !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .info, message(), metadata: metadata(), @@ -351,6 +418,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'info' log level. @@ -403,7 +471,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelWarning && !MaxLogLevelError && !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .notice, message(), metadata: metadata(), @@ -412,6 +481,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'notice' log level. @@ -464,7 +534,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelError && !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .warning, message(), metadata: metadata(), @@ -473,6 +544,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'warning' log level. @@ -525,7 +597,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelCritical && !MaxLogLevelNone + self._log( level: .error, message(), metadata: metadata(), @@ -534,6 +607,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'error' log level. @@ -586,7 +660,8 @@ extension Logger { function: String = #function, line: UInt = #line ) { - self.log( + #if !MaxLogLevelNone + self._log( level: .critical, message(), metadata: metadata(), @@ -595,6 +670,7 @@ extension Logger { function: function, line: line ) + #endif } /// Log a message at the 'critical' log level.