swift
Expert guidance on Swift best practices, patterns, and implementation. Use when developers mention: (1) Swift configuration or environment variables, (2) swift-log or logging patterns, (3) OpenTelemetry or swift-otel, (4) Swift Testing framework or @Test macro, (5) Foundation avoidance or cross-platform Swift, (6) platform-specific code organization, (7) Span or memory safety patterns, (8) non-copyable types (~Copyable), (9) API design patterns or access modifiers.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install joannis-claude-skills-swift
Repository
Skill path: swift
Expert guidance on Swift best practices, patterns, and implementation. Use when developers mention: (1) Swift configuration or environment variables, (2) swift-log or logging patterns, (3) OpenTelemetry or swift-otel, (4) Swift Testing framework or @Test macro, (5) Foundation avoidance or cross-platform Swift, (6) platform-specific code organization, (7) Span or memory safety patterns, (8) non-copyable types (~Copyable), (9) API design patterns or access modifiers.
Open repositoryBest for
Primary workflow: Build Mobile.
Technical facets: Full Stack, Backend, Designer, Mobile, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: Joannis.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install swift into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/Joannis/claude-skills before adding swift to shared team environments
- Use swift for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: swift
description: 'Expert guidance on Swift best practices, patterns, and implementation. Use when developers mention: (1) Swift configuration or environment variables, (2) swift-log or logging patterns, (3) OpenTelemetry or swift-otel, (4) Swift Testing framework or @Test macro, (5) Foundation avoidance or cross-platform Swift, (6) platform-specific code organization, (7) Span or memory safety patterns, (8) non-copyable types (~Copyable), (9) API design patterns or access modifiers.'
---
# Swift
Swift is a modern general-purpose programming language.
## Reference Files
Load these files as needed for specific topics:
- **`references/swift-configuration.md`** - Swift Configuration: reading config from environment variables, files, CLI arguments; provider hierarchy, namespacing, hot reloading, secret handling
- **`references/swift-log.md`** - Swift Log logging API: log levels, structured logging, best practices for libraries, metadata, custom handlers
- **`references/swift-otel.md`** - Swift OTel: OpenTelemetry backend for server apps (preferred for Linux); OTLP export for logs, metrics, tracing; framework integration
- **`references/swift-testing.md`** - Swift Testing framework: @Test macro, #expect/#require assertions, traits, parameterized tests, test suites, parallel execution, XCTest migration
- **`references/debugging.md`** - Debugging tips: Terminal UI on Linux (alternate screen buffer), GitHub Actions log analysis
### Access Modifiers
Keep types and functions internal unless they need to be public for external use. This prevents accidental exposure of implementation details and makes access level errors easier to fix.
### Foundation Avoidance Policy
**Avoid Foundation in core library code when possible:**
- Foundation types (`Data`, `Date`, `UUID`, etc.) should be avoided in public APIs for libraries targeting:
- Embedded Swift
- Cross-platform consistency
- Binary size reduction (FoundationEssentials is 15-40MB)
- Use Swift standard library types instead:
- `[UInt8]` instead of `Data` for byte buffers
- `ContinuousClock.Instant` or custom types instead of `Date`
- Byte-based initializers instead of `UUID` strings
- Always use `internal import Foundation` or `internal import FoundationEssentials`, never `public import`
```swift
#if canImport(FoundationEssentials)
internal import FoundationEssentials
#else
internal import Foundation
#endif
```
### InternalImportsByDefault Feature
When using `InternalImportsByDefault` in Package.swift, all imports are internal by default unless explicitly marked with `public import`.
**When to use `public import`:**
- When types from the imported module are exposed in public API (return types, parameters, protocol conformances)
- Example: `public import ServiceLifecycle` when conforming to `ServiceLifecycle.Service` in a public type
- **Never use `public import Foundation`** - keep Foundation internal
### Platform-Specific File Organization
Use a `+platform` suffix convention for platform-specific implementations:
- `PlatformDeviceDiscovery+macos.swift` - macOS implementation
- `PlatformDeviceDiscovery+linux.swift` - Linux implementation
- `PlatformDeviceDiscovery+default.swift` - Fallback for other platforms
When adding methods to a protocol, **all platform files must be updated** to maintain conformance.
### Linux C Library Support
Support both Glibc and Musl for Linux compatibility:
```swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif
```
### Avoid Repetitive Code in Selection Logic
When selecting from multiple options with preference ordering, use sorting instead of multiple conditional blocks:
**Bad - Repetitive:**
```swift
if !preferBluetooth {
for interface in interfaces {
if case .lan(let device) = interface {
return .lan(device)
}
}
}
for interface in interfaces {
if case .bluetooth(let device) = interface {
return .bluetooth(device)
}
}
if preferBluetooth {
for interface in interfaces {
if case .lan(let device) = interface {
return .lan(device)
}
}
}
```
**Good - Sort once, iterate once:**
```swift
let sorted = interfaces.sorted { a, b in
if preferBluetooth {
return a.type == "Bluetooth" && b.type != "Bluetooth"
} else {
return a.type == "LAN" && b.type != "LAN"
}
}
for interface in sorted {
switch interface {
case .lan(let device): return .lan(device)
case .bluetooth(let device): return .bluetooth(device)
default: continue
}
}
```
### Memory Safety Patterns (Swift 6.2+)
Swift 6.2 introduces opt-in strict memory safety checking via `.strictMemorySafety()` in Package.swift.
**Span Lifetime Constraints:**
- `Span<T>` is lifetime-dependent - it borrows the memory of its backing storage
- Cannot cross async boundaries
- Cannot escape closure scope
- Cannot pass to async callbacks
**Solution: Asymmetric API Design**
Use **Span for parsing** (read-only, synchronous, borrowed) and **[UInt8] for writing** (owned, can cross boundaries):
```swift
public struct Characteristic<Value: Sendable>: Sendable {
// Parsing uses Span - borrowed, synchronous access
internal let parse: @Sendable (borrowing Span<UInt8>) throws -> Value
// Writing uses [UInt8] - owned, can cross closure boundaries
public typealias WithBytes = ([UInt8]) -> Void
internal let write: @Sendable (Value) -> (WithBytes) -> Void
}
```
**Safe Integer Loading from Bytes:**
```swift
// UNSAFE: unsafeLoad
return span.bytes.unsafeLoad(as: UInt64.self)
// SAFE: Manual byte-by-byte assembly
var value: UInt64 = 0
for i in 0..<8 {
value |= UInt64(span[i]) << (i * 8)
}
return value
```
### Span-Based Computed Properties with `_read`/`_modify`
With the `LifetimeDependence` experimental feature, computed properties can return non-escapable types like `RawSpan` and `MutableRawSpan` using `_read` and `_modify` accessors:
```swift
// Enable in Package.swift:
swiftSettings: [
.enableExperimentalFeature("LifetimeDependence"),
]
// Read-only span access
public var bytes: RawSpan {
_read {
var mapInfo = GstMapInfo()
guard mapBuffer(&mapInfo) else { fatalError("Failed to map") }
defer { unmapBuffer(&mapInfo) }
yield RawSpan(_unsafeStart: mapInfo.data, byteCount: Int(mapInfo.size))
}
}
// Mutable span access with Copy-on-Write
public var mutableBytes: MutableRawSpan {
_read {
fatalError("Cannot read mutableBytes")
}
_modify {
// Ensure unique ownership before write (CoW)
if !isKnownUniquelyReferenced(&storage) {
storage = storage.copy()!
}
var mapInfo = GstMapInfo()
guard mapBuffer(&mapInfo) else { fatalError("Failed to map") }
defer { unmapBuffer(&mapInfo) }
var span = MutableRawSpan(_unsafeStart: mapInfo.data, byteCount: Int(mapInfo.size))
yield &span
}
}
```
**Known Compiler Issue (Swift 6.2.3):** The LifetimeDependenceScopeFixup pass can crash when using `span.withUnsafeBytes` in certain contexts. Workaround: provide separate closure-based methods for C interop that don't go through the span accessor.
### Non-Copyable Types
Use `~Copyable` for move-only types that should not be duplicated:
```swift
public struct ResourceHandle: ~Copyable {
// Can only be moved, not copied
}
public struct ServiceRegistration: @unchecked Sendable, ~Copyable { ... }
```
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/swift-configuration.md
```markdown
# Swift Configuration
Swift Configuration is Apple's official package for reading application and library configuration. It provides an abstraction layer that separates how code reads configuration (through a consistent API) from where that configuration comes from (various providers).
## Core Concepts
### ConfigReader
The main interface applications use to access configuration values:
```swift
import Configuration
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(),
try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json")
])
// Read values with type-safe accessors
let timeout = config.int(forKey: "http.timeout", default: 60)
let host = config.string(forKey: "database.host", default: "localhost")
let debug = config.bool(forKey: "app.debug", default: false)
```
### ConfigProvider
The backend abstraction that retrieves values from specific sources. Providers are stacked hierarchically where higher-priority sources (listed first) override lower ones.
## Built-In Providers
| Provider | Description |
|----------|-------------|
| `EnvironmentVariablesProvider` | Reads from environment variables |
| `FileProvider<JSONSnapshot>` | Reads from JSON files |
| `FileProvider<YAMLSnapshot>` | Reads from YAML files (requires YAML trait) |
| `CommandLineArgumentsProvider` | Reads from CLI arguments (requires trait) |
| `DirectoryFilesProvider` | Reads from a directory of files |
| `InMemoryProvider` | Static in-memory configuration |
| `MutableInMemoryProvider` | Mutable in-memory configuration |
| `KeyMappingProvider` | Transforms keys from another provider |
## Basic Usage
### Setting Up Configuration
```swift
import Configuration
@main
struct MyApp {
static func main() async throws {
// Create configuration with provider hierarchy
// Earlier providers take precedence over later ones
let config = ConfigReader(providers: [
// 1. Environment variables (highest priority - for overrides)
EnvironmentVariablesProvider(),
// 2. Local config file (development overrides)
try? await FileProvider<JSONSnapshot>(filePath: "./config.local.json"),
// 3. Default config file (base configuration)
try await FileProvider<JSONSnapshot>(filePath: "./config.json")
].compactMap { $0 })
let server = MyServer(config: config)
try await server.run()
}
}
```
### Reading Values
```swift
// With defaults (recommended)
let port = config.int(forKey: "server.port", default: 8080)
let host = config.string(forKey: "server.host", default: "0.0.0.0")
let maxConnections = config.int(forKey: "server.maxConnections", default: 100)
// Optional values (returns nil if not found)
let apiKey: String? = config.string(forKey: "api.key")
// Nested keys use dot notation
let dbHost = config.string(forKey: "database.primary.host", default: "localhost")
let dbPort = config.int(forKey: "database.primary.port", default: 5432)
```
### Namespacing
Scope a reader to a configuration subtree:
```swift
let config = ConfigReader(providers: [...])
// Create a namespaced reader for database configuration
let dbConfig = config.namespaced("database")
let host = dbConfig.string(forKey: "host", default: "localhost") // reads "database.host"
let port = dbConfig.int(forKey: "port", default: 5432) // reads "database.port"
// Pass namespaced config to components
let database = Database(config: dbConfig)
```
## Provider Hierarchy
Providers are evaluated in order. The first provider to return a value wins:
```swift
let config = ConfigReader(providers: [
// Priority 1: Environment variables (for deployment overrides)
EnvironmentVariablesProvider(),
// Priority 2: Command-line arguments (for runtime overrides)
CommandLineArgumentsProvider(),
// Priority 3: Local file (for development)
try? await FileProvider<JSONSnapshot>(filePath: "./config.local.json"),
// Priority 4: Default file (base configuration)
try await FileProvider<JSONSnapshot>(filePath: "./config.json"),
// Priority 5: In-memory defaults (fallback)
InMemoryProvider(values: [
"server.port": "8080",
"server.host": "0.0.0.0"
])
].compactMap { $0 })
```
This allows:
- Environment variables to override everything (production deployments)
- CLI arguments for one-off changes
- Local config files for development without modifying tracked files
- Default files for base configuration
- In-memory fallbacks for sensible defaults
## Hot Reloading
Enable auto-reloading when configuration files change:
```swift
import Configuration
// Enable reloading trait in Package.swift first
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(),
try await ReloadingFileProvider<JSONSnapshot>(filePath: "/etc/myapp/config.json")
])
// Watch for configuration changes
for await _ in config.changes {
print("Configuration updated")
// Reconfigure services as needed
}
```
## Package Traits
Enable optional functionality via traits in `Package.swift`:
```swift
dependencies: [
.package(
url: "https://github.com/apple/swift-configuration",
from: "1.0.0",
traits: [.defaults, "YAML", "Reloading", "CommandLineArguments"]
)
]
```
| Trait | Description |
|-------|-------------|
| `JSON` | JSON file support (default) |
| `YAML` | YAML file support |
| `Logging` | Integration with swift-log |
| `Reloading` | Hot-reload configuration files |
| `CommandLineArguments` | CLI argument provider |
## Secret Handling
Redact sensitive values in logs and debug output:
```swift
// Mark keys as secrets for redaction
let config = ConfigReader(
providers: [...],
secretKeys: ["api.key", "database.password", "jwt.secret"]
)
// When logging, secrets are automatically redacted
logger.info("Config: \(config.debugDescription)")
// Output: Config: {api.key: <redacted>, server.port: 8080, ...}
```
## Best Practices
### 1. Centralize Configuration Setup
Configure providers once at application startup, not in libraries:
```swift
// Good: Application configures providers
@main
struct MyApp {
static func main() async throws {
let config = ConfigReader(providers: [...])
let service = MyService(config: config)
}
}
// Bad: Library creates its own providers
struct MyLibrary {
init() {
// Don't do this - let the application configure providers
let config = ConfigReader(providers: [
EnvironmentVariablesProvider()
])
}
}
```
### 2. Accept Configuration in Libraries
Libraries should accept a `ConfigReader` or namespaced reader:
```swift
public struct DatabaseClient {
private let host: String
private let port: Int
public init(config: ConfigReader) {
self.host = config.string(forKey: "host", default: "localhost")
self.port = config.int(forKey: "port", default: 5432)
}
}
// Application usage
let dbConfig = config.namespaced("database")
let client = DatabaseClient(config: dbConfig)
```
### 3. Provide Sensible Defaults
Always include fallback defaults for optional configuration:
```swift
// Good: Provides defaults
let timeout = config.int(forKey: "http.timeout", default: 30)
let retries = config.int(forKey: "http.retries", default: 3)
// Avoid: No defaults for optional config
let timeout = config.int(forKey: "http.timeout")! // Will crash if missing
```
### 4. Use Environment Variables for Deployment
Place environment variables first for easy deployment overrides:
```swift
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(), // Allows: HTTP_TIMEOUT=60 ./myapp
try await FileProvider<JSONSnapshot>(filePath: "./config.json")
])
```
### 5. Separate Configuration by Environment
```swift
let environment = ProcessInfo.processInfo.environment["APP_ENV"] ?? "development"
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(),
try await FileProvider<JSONSnapshot>(filePath: "./config.\(environment).json"),
try await FileProvider<JSONSnapshot>(filePath: "./config.json")
])
```
## Configuration File Examples
### JSON Configuration
```json
{
"server": {
"host": "0.0.0.0",
"port": 8080,
"maxConnections": 1000
},
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"pool": {
"minConnections": 5,
"maxConnections": 20
}
},
"logging": {
"level": "info"
}
}
```
### YAML Configuration
```yaml
server:
host: 0.0.0.0
port: 8080
maxConnections: 1000
database:
host: localhost
port: 5432
name: myapp
pool:
minConnections: 5
maxConnections: 20
logging:
level: info
```
### Environment Variables
Environment variables use uppercase with underscores:
```bash
# Overrides server.port
export SERVER_PORT=9090
# Overrides database.host
export DATABASE_HOST=db.example.com
# Overrides database.pool.maxConnections
export DATABASE_POOL_MAX_CONNECTIONS=50
```
## Platform Support
| Platform | Minimum Version |
|----------|-----------------|
| macOS | 15+ |
| iOS | 18+ |
| tvOS | 18+ |
| watchOS | 11+ |
| visionOS | 2+ |
| Linux | Supported |
```
### references/swift-log.md
```markdown
# Swift Log
Swift Log is the official logging API for Swift. It provides a common logging interface that libraries and applications can use, allowing log backends to be configured at the application level.
## Log Levels
SwiftLog defines 7 levels from least to most severe:
| Level | Use Case |
|-------|----------|
| `trace` | Finest-grained debugging for hard-to-reproduce issues. Assume trace logging will not be used in production unless specifically enabled. |
| `debug` | High-value operational insights without overwhelming systems. Should not undermine production performance. |
| `info` | General informational messages about application flow. |
| `notice` | Notable events worth attention but not problematic. |
| `warning` | Actionable alerts that may require attention. |
| `error` | Error conditions that affected an operation. |
| `critical` | System-critical failures requiring immediate attention. |
## Basic Usage
```swift
import Logging
// Create a logger
let logger = Logger(label: "com.example.myapp")
// Log at different levels
logger.trace("Detailed trace information")
logger.debug("Debug information")
logger.info("Application started")
logger.notice("Notable event occurred")
logger.warning("Resource usage is high")
logger.error("Failed to connect to database")
logger.critical("System is shutting down")
```
## Structured Logging
Prefer metadata dictionaries over string interpolation for machine-parseable logs:
```swift
// Preferred: Structured metadata
logger.info("Accepted connection", metadata: [
"connection.id": "\(connection.id)",
"connection.peer": "\(connection.peer)",
"connections.total": "\(connections.count)"
])
// Avoid: String interpolation
logger.info("Accepted connection \(connection.id) from \(connection.peer)")
```
Structured logging enables:
- Automated log processing and aggregation
- Correlation tracking via TraceIDs across distributed systems
- Better filtering and searching in log management systems
## Best Practices for Libraries
### Primary Levels
Libraries should primarily use `trace` and `debug` logging:
- **Trace**: Exhaustive diagnostic data for debugging hard-to-reproduce issues
- **Debug**: Valuable operational insights that won't overwhelm production systems
### When NOT to Log Errors/Warnings
Avoid logging errors or warnings when:
1. **End-users can handle the issue** - Use `throw` or return an `Error` instead
```swift
// Bad: Logging an error the caller should handle
logger.error("Invalid input provided")
throw ValidationError.invalidInput
// Good: Just throw, let the caller decide
throw ValidationError.invalidInput
```
2. **Reporting normal operations** - Don't log every successful request
```swift
// Bad: Logging expected behavior
logger.info("Request completed successfully")
// Good: Only log if it provides operational value
logger.debug("Processed batch of \(count) items")
```
3. **The information isn't actionable** - If a library user can't do anything about it, don't log it as a warning/error
### Configuration Responsibility
Log configuration belongs to the **executable target**, not libraries:
```swift
// In your application (main.swift or App entry point):
LoggingSystem.bootstrap { label in
StreamLogHandler.standardOutput(label: label)
}
// Libraries should NEVER do this:
// LoggingSystem.bootstrap { ... } // Don't configure logging in a library
```
## Creating Logger Instances
### In Applications
```swift
import Logging
@main
struct MyApp {
static func main() {
// Bootstrap once at startup
LoggingSystem.bootstrap { label in
StreamLogHandler.standardOutput(label: label)
}
let logger = Logger(label: "com.example.myapp")
logger.info("Application starting")
}
}
```
### In Libraries
```swift
import Logging
public struct MyLibrary {
private let logger: Logger
public init(logger: Logger = Logger(label: "com.example.mylibrary")) {
self.logger = logger
}
public func doWork() {
logger.trace("Starting work")
// ... implementation
logger.debug("Work completed")
}
}
```
## Log Metadata
### Adding Context
```swift
var logger = Logger(label: "com.example.myapp")
// Set metadata that persists across log calls
logger[metadataKey: "request-id"] = "\(requestId)"
logger[metadataKey: "user-id"] = "\(userId)"
// All subsequent logs include this metadata
logger.info("Processing request") // Includes request-id and user-id
```
### One-time Metadata
```swift
// Add metadata for a single log call
logger.info("Database query executed", metadata: [
"query": "\(sanitizedQuery)",
"duration_ms": "\(duration)"
])
```
## Custom Log Handlers
Implement `LogHandler` protocol to create custom backends:
```swift
import Logging
struct MyCustomLogHandler: LogHandler {
var metadata: Logger.Metadata = [:]
var logLevel: Logger.Level = .info
subscript(metadataKey key: String) -> Logger.Metadata.Value? {
get { metadata[key] }
set { metadata[key] = newValue }
}
func log(
level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt
) {
// Custom logging implementation
print("[\(level)] \(message)")
}
}
```
## Integration with Distributed Tracing
For distributed systems, include correlation IDs in metadata:
```swift
func handleRequest(_ request: Request) async {
var logger = Logger(label: "com.example.api")
logger[metadataKey: "trace-id"] = request.traceId
logger[metadataKey: "span-id"] = UUID().uuidString
logger.info("Handling request", metadata: [
"path": "\(request.path)",
"method": "\(request.method)"
])
// Pass logger to downstream services
await processRequest(request, logger: logger)
}
```
```
### references/swift-otel.md
```markdown
# Swift OTel
Swift OTel is the preferred OpenTelemetry backend for Swift server applications on Linux. It provides OTLP (OpenTelemetry Protocol) export for Swift's observability ecosystem, integrating with:
- **Swift Log** - Logging
- **Swift Metrics** - Metrics collection
- **Swift Distributed Tracing** - Distributed tracing
This package is a wire-protocol backend, not a full OTel SDK. It keeps dependencies minimal while providing seamless integration with Swift's lightweight observability APIs.
## Installation
Add to `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/swift-otel/swift-otel.git", from: "1.0.0")
]
```
Add the product to your target:
```swift
.target(
name: "MyApp",
dependencies: [
.product(name: "OTel", package: "swift-otel")
]
)
```
### Package Traits
Use traits to reduce build time by only including needed exporters:
```swift
.package(
url: "https://github.com/swift-otel/swift-otel.git",
from: "1.0.0",
traits: ["OTLPHTTP"] // or "OTLPGRPC"
)
```
## Quick Start
### One-Line Bootstrap
```swift
import OTel
@main
struct MyApp {
static func main() async throws {
let observability = try OTel.bootstrap()
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await observability.run()
}
// Your application logic here
group.addTask {
try await runServer()
}
try await group.next()
group.cancelAll()
}
}
}
```
### With ServiceLifecycle
```swift
import OTel
import ServiceLifecycle
@main
struct MyApp {
static func main() async throws {
let observability = try OTel.bootstrap()
let serviceGroup = ServiceGroup(
services: [observability, myServer],
logger: logger
)
try await serviceGroup.run()
}
}
```
## Configuration
### Environment Variables
Swift OTel follows OpenTelemetry environment variable specifications:
```bash
# Service identification
export OTEL_SERVICE_NAME=my-service
# Exporter endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.example.com:4318
# Protocol (http/protobuf or grpc)
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# Individual signal endpoints
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://collector:4318/v1/traces
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://collector:4318/v1/metrics
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://collector:4318/v1/logs
# Console exporter for development
export OTEL_LOGS_EXPORTER=console
export OTEL_TRACES_EXPORTER=console
export OTEL_METRICS_EXPORTER=console
```
### Code-Based Configuration
```swift
import OTel
var config = OTel.Configuration.default
// Service resource
config.resource.serviceName = "my-service"
config.resource.serviceVersion = "1.0.0"
// Traces configuration
config.traces.exporter = .otlp
config.traces.otlpExporter.endpoint = "https://otel-collector.example.com:4317"
config.traces.otlpExporter.protocol = .grpc
config.traces.otlpExporter.compression = .gzip
config.traces.otlpExporter.timeout = .seconds(3)
// Metrics configuration
config.metrics.exporter = .otlp
config.metrics.otlpExporter.endpoint = "https://otel-collector.example.com:4317"
// Logs configuration
config.logs.exporter = .otlp
config.logs.otlpExporter.endpoint = "https://otel-collector.example.com:4317"
let observability = try OTel.bootstrap(configuration: config)
```
### Hybrid Configuration
Combine code and environment variables. Environment variables take precedence:
```swift
// Set defaults in code
var config = OTel.Configuration.default
config.traces.otlpExporter.timeout = .seconds(5)
// Environment variables override:
// OTEL_EXPORTER_OTLP_ENDPOINT will override the endpoint
let observability = try OTel.bootstrap(configuration: config)
```
## Export Protocols
### HTTP/Protobuf (Default)
Default endpoint: `http://localhost:4318`
```swift
config.traces.otlpExporter.protocol = .httpProtobuf
config.traces.otlpExporter.endpoint = "https://collector:4318"
```
### gRPC
Default endpoint: `http://localhost:4317`
```swift
config.traces.otlpExporter.protocol = .grpc
config.traces.otlpExporter.endpoint = "https://collector:4317"
```
## Using the Observability APIs
Once bootstrapped, use Swift's standard observability APIs:
### Logging (Swift Log)
```swift
import Logging
let logger = Logger(label: "com.example.myapp")
logger.info("Request received", metadata: [
"request.id": "\(requestId)",
"user.id": "\(userId)"
])
```
### Metrics (Swift Metrics)
```swift
import Metrics
// Counter
let requestCounter = Counter(label: "http.requests.total")
requestCounter.increment()
// Gauge
let activeConnections = Gauge(label: "connections.active")
activeConnections.record(42)
// Histogram
let requestDuration = Timer(label: "http.request.duration")
requestDuration.recordMilliseconds(150)
```
### Tracing (Swift Distributed Tracing)
```swift
import Tracing
try await withSpan("handleRequest") { span in
span.attributes["http.method"] = "GET"
span.attributes["http.url"] = "/api/users"
let users = try await withSpan("fetchUsers") { childSpan in
try await database.query("SELECT * FROM users")
}
span.attributes["http.status_code"] = 200
return users
}
```
## Framework Integration
### Hummingbird
```swift
import Hummingbird
import OTel
let observability = try OTel.bootstrap()
let router = Router()
router.middlewares.add(TracingMiddleware())
router.middlewares.add(MetricsMiddleware())
router.middlewares.add(LogRequestsMiddleware())
router.get("/health") { _, _ in
"OK"
}
let app = Application(router: router)
let serviceGroup = ServiceGroup(
services: [observability, app],
logger: logger
)
try await serviceGroup.run()
```
### Vapor
```swift
import Vapor
import OTel
let observability = try OTel.bootstrap()
let app = try await Application.make()
app.middleware.use(TracingMiddleware())
// Configure routes...
let serviceGroup = ServiceGroup(
services: [observability, app],
logger: app.logger
)
try await serviceGroup.run()
```
## Manual Bootstrap
For advanced use cases, manually configure individual backends:
```swift
import OTel
// Create individual backends
let loggingBackend = try OTel.makeLoggingBackend(configuration: logsConfig)
let metricsBackend = try OTel.makeMetricsBackend(configuration: metricsConfig)
let tracingBackend = try OTel.makeTracingBackend(configuration: tracesConfig)
// Bootstrap Swift Log with custom handler
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
// Add OTel as secondary handler
return MultiplexLogHandler([handler, loggingBackend.makeLogHandler(label: label)])
}
```
## Console Exporter
For local development, export to console instead of a collector:
```swift
var config = OTel.Configuration.default
config.logs.exporter = .console
config.traces.exporter = .console
config.metrics.exporter = .console
let observability = try OTel.bootstrap(configuration: config)
```
Or via environment:
```bash
OTEL_LOGS_EXPORTER=console OTEL_TRACES_EXPORTER=console swift run
```
## Best Practices
### 1. Bootstrap Early
Initialize observability before other services:
```swift
@main
struct MyApp {
static func main() async throws {
// Bootstrap observability first
let observability = try OTel.bootstrap()
// Then initialize other services
let database = try await Database.connect()
let server = MyServer(database: database)
let serviceGroup = ServiceGroup(
services: [observability, server],
logger: logger
)
try await serviceGroup.run()
}
}
```
### 2. Use Structured Metadata
```swift
logger.info("Order processed", metadata: [
"order.id": "\(orderId)",
"order.total": "\(total)",
"customer.id": "\(customerId)"
])
```
### 3. Propagate Trace Context
Trace context propagates automatically through async/await:
```swift
try await withSpan("parentOperation") { _ in
// Child spans automatically linked
try await withSpan("childOperation") { _ in
// Trace context flows through
}
}
```
### 4. Set Resource Attributes
```swift
var config = OTel.Configuration.default
config.resource.serviceName = "order-service"
config.resource.serviceVersion = "2.1.0"
config.resource.deploymentEnvironment = "production"
config.resource.attributes["host.name"] = hostname
```
## Platform Support
| Platform | Status |
|----------|--------|
| Linux | Fully supported (preferred) |
| macOS | Supported |
| iOS/tvOS/watchOS | Not targeted (use Apple's observability tools) |
```
### references/swift-testing.md
```markdown
# Swift Testing
Swift Testing is the modern testing framework for Swift, included in Swift 6 toolchains and Xcode 16. It provides an expressive, macro-based API that reduces boilerplate and runs tests in parallel by default.
## Core Concepts
### @Test Macro
Mark functions as tests using the `@Test` attribute:
```swift
import Testing
@Test func basicTest() {
#expect(1 + 1 == 2)
}
// With custom display name
@Test("User can log in successfully")
func userLogin() async throws {
let result = try await auth.login(user: "test", password: "secret")
#expect(result.isSuccess)
}
// Async and throwing tests
@Test func asyncTest() async throws {
let data = try await fetchData()
#expect(!data.isEmpty)
}
```
## Assertions
### #expect Macro
The primary assertion macro. Accepts any boolean expression:
```swift
// Basic comparisons
#expect(value == expected)
#expect(count > 0)
#expect(array.contains(element))
#expect(string.hasPrefix("Hello"))
// With custom message
#expect(age >= 18, "User must be an adult")
// Negation
#expect(!list.isEmpty)
```
### #require Macro
Unwraps optionals or stops the test on failure. Requires `try`:
```swift
// Unwrap optional - test stops if nil
let user = try #require(users.first)
#expect(user.name == "Alice")
// Require condition - test stops if false
try #require(database.isConnected)
let results = try await database.query("SELECT * FROM users")
```
Use `#require` when subsequent code depends on the condition being true.
### Error Testing
Test that code throws expected errors:
```swift
// Expect any error
#expect(throws: (any Error).self) {
try dangerousOperation()
}
// Expect specific error type
#expect(throws: ValidationError.self) {
try validate(input: "")
}
// Expect specific error value
#expect(throws: NetworkError.timeout) {
try await fetchWithTimeout()
}
// Expect no error
#expect(throws: Never.self) {
try safeOperation()
}
// Custom error validation
#expect(performing: {
try processOrder(quantity: -1)
}, throws: { error in
guard let orderError = error as? OrderError else { return false }
return orderError.code == 422
})
```
### confirmation
Verify that events occur (useful for callbacks and delegates):
```swift
// Verify event happens exactly once
await confirmation { eventOccurred in
button.onTap = {
eventOccurred()
}
button.simulateTap()
}
// Verify event happens specific number of times
await confirmation(expectedCount: 3) { called in
for _ in 0..<3 {
processor.process { called() }
}
}
// Verify event never happens
await confirmation(expectedCount: 0) { shouldNotBeCalled in
cache.onEviction = { shouldNotBeCalled() }
cache.set("key", value: "value")
}
```
### withKnownIssue
Mark code with known problems without failing the test:
```swift
// Known bug - test passes even if code fails
withKnownIssue {
try buggyFunction()
}
// Intermittent issue
withKnownIssue(isIntermittent: true) {
try flakyNetworkCall()
}
// With bug reference
withKnownIssue("https://github.com/org/repo/issues/123") {
try brokenFeature()
}
```
## Test Organization
### Test Suites
Group related tests using structs or classes:
```swift
@Suite("User Authentication")
struct AuthenticationTests {
@Test func loginSucceeds() async throws {
// ...
}
@Test func loginFailsWithWrongPassword() async throws {
// ...
}
}
```
### Nested Suites
Create hierarchical test organization:
```swift
struct UserTests {
@Suite("Profile")
struct ProfileTests {
@Test func updateName() { }
@Test func updateEmail() { }
}
@Suite("Settings")
struct SettingsTests {
@Test func changePassword() { }
@Test func enableTwoFactor() { }
}
}
```
### Setup and Teardown
Use initializers and deinitializers for test lifecycle:
```swift
struct DatabaseTests {
let database: TestDatabase
init() async throws {
// Runs before each test
database = try await TestDatabase.create()
try await database.migrate()
}
deinit {
// Runs after each test (for classes/actors)
}
@Test func queryReturnsResults() async throws {
let results = try await database.query("SELECT * FROM users")
#expect(!results.isEmpty)
}
}
```
For cleanup with structs, use actors:
```swift
actor CleanupTests {
var tempFiles: [URL] = []
init() {
// Setup
}
deinit {
// Cleanup runs after each test
for file in tempFiles {
try? FileManager.default.removeItem(at: file)
}
}
@Test func writeFile() async throws {
let url = URL(fileURLWithPath: "/tmp/test.txt")
tempFiles.append(url)
try "test".write(to: url, atomically: true, encoding: .utf8)
}
}
```
## Traits
Traits customize test behavior. Apply to `@Test` or `@Suite`:
### Tags
Categorize tests for filtering:
```swift
extension Tag {
@Tag static var networking: Self
@Tag static var database: Self
@Tag static var slow: Self
}
@Test(.tags(.networking))
func fetchUsers() async throws { }
@Test(.tags(.database, .slow))
func migrateSchema() async throws { }
// Apply to entire suite
@Suite(.tags(.networking))
struct APITests {
@Test func getUser() { } // Inherits .networking tag
@Test func createUser() { }
}
```
Run tagged tests: `swift test --filter .tags:networking`
### Conditional Execution
```swift
// Enable based on condition
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
func ciOnlyTest() { }
// Disable with reason
@Test(.disabled("Waiting for backend fix"))
func brokenTest() { }
// Platform-specific
@Test(.enabled(if: Platform.current == .macOS))
func macOnlyTest() { }
```
### Bug References
```swift
@Test(.bug("https://github.com/org/repo/issues/123", "Flaky on CI"))
func flakyTest() { }
@Test(.bug("JIRA-456"))
func knownIssueTest() { }
```
### Time Limits
```swift
@Test(.timeLimit(.seconds(5)))
func quickTest() async { }
@Test(.timeLimit(.minutes(1)))
func longerTest() async { }
```
### Serial Execution
By default, tests run in parallel. Use `.serialized` for tests that can't run concurrently:
```swift
@Suite(.serialized)
struct DatabaseMigrationTests {
@Test func migration1() { } // Runs first
@Test func migration2() { } // Runs after migration1
@Test func migration3() { } // Runs after migration2
}
```
## Parameterized Tests
Run the same test with multiple inputs:
```swift
@Test("Validates email format", arguments: [
"[email protected]",
"[email protected]",
"[email protected]"
])
func validEmails(email: String) {
#expect(EmailValidator.isValid(email))
}
@Test("Rejects invalid emails", arguments: [
"not-an-email",
"@missing-local.com",
"missing-domain@"
])
func invalidEmails(email: String) {
#expect(!EmailValidator.isValid(email))
}
```
### Multiple Parameters
```swift
@Test(arguments: [1, 2, 3], ["a", "b"])
func combinations(number: Int, letter: String) {
// Runs for all combinations: (1,"a"), (1,"b"), (2,"a"), (2,"b"), (3,"a"), (3,"b")
#expect(!"\(number)\(letter)".isEmpty)
}
```
### Zipped Parameters
Avoid combinatorial explosion by zipping:
```swift
@Test(arguments: zip([1, 2, 3], ["one", "two", "three"]))
func numberNames(number: Int, name: String) {
// Runs for: (1,"one"), (2,"two"), (3,"three")
#expect(name.count > 0)
}
```
### Collections and Ranges
```swift
@Test(arguments: 1...10)
func testRange(value: Int) {
#expect(value > 0 && value <= 10)
}
@Test(arguments: Set(["apple", "banana", "cherry"]))
func testSet(fruit: String) {
#expect(!fruit.isEmpty)
}
```
## Custom Test Descriptions
Implement `CustomTestStringConvertible` for better output:
```swift
struct User {
let id: Int
let name: String
}
extension User: CustomTestStringConvertible {
var testDescription: String {
"User(\(name))"
}
}
@Test(arguments: [
User(id: 1, name: "Alice"),
User(id: 2, name: "Bob")
])
func userTest(user: User) {
// Output shows "User(Alice)" instead of "User(id: 1, name: \"Alice\")"
#expect(user.id > 0)
}
```
## Parallel Execution
Tests run in parallel by default. Each test gets an independent instance of the suite:
```swift
struct CounterTests {
var counter = 0 // Each test gets its own counter
@Test func increment() {
counter += 1
#expect(counter == 1) // Always passes - isolated instance
}
@Test func decrement() {
counter -= 1
#expect(counter == -1) // Always passes - isolated instance
}
}
```
## Migration from XCTest
### Key Differences
| XCTest | Swift Testing |
|--------|---------------|
| `XCTestCase` subclass | `@Suite` struct/class |
| `func testExample()` | `@Test func example()` |
| `XCTAssertEqual(a, b)` | `#expect(a == b)` |
| `XCTAssertNil(x)` | `#expect(x == nil)` |
| `XCTAssertThrowsError` | `#expect(throws:)` |
| `XCTUnwrap(x)` | `try #require(x)` |
| `setUpWithError()` | `init() throws` |
| `tearDown()` | `deinit` |
| Sequential by default | Parallel by default |
### Coexistence
Both frameworks can run together:
```bash
swift test --enable-swift-testing
```
Don't mix assertions between frameworks in the same test. Migrate incrementally.
### Migration Example
```swift
// Before (XCTest)
class UserTests: XCTestCase {
var sut: UserService!
override func setUpWithError() throws {
sut = UserService()
}
func testCreateUser() throws {
let user = try sut.create(name: "Test")
XCTAssertEqual(user.name, "Test")
XCTAssertNotNil(user.id)
}
}
// After (Swift Testing)
@Suite
struct UserTests {
let sut: UserService
init() {
sut = UserService()
}
@Test func createUser() throws {
let user = try sut.create(name: "Test")
#expect(user.name == "Test")
#expect(user.id != nil)
}
}
```
## Platform Support
| Platform | Status |
|----------|--------|
| macOS | Supported |
| iOS | Supported |
| tvOS | Supported |
| watchOS | Supported |
| visionOS | Supported |
| Linux | Supported |
| Windows | Supported |
## Running Tests
```bash
# Run all tests
swift test
# Run with Swift Testing enabled (for mixed projects)
swift test --enable-swift-testing
# Filter by tag
swift test --filter .tags:networking
# Filter by name
swift test --filter UserTests
```
```
### references/debugging.md
```markdown
# Debugging
## Terminal UI on Linux
For clean terminal UIs that don't pollute scroll history, use the alternate screen buffer:
```swift
// Enter alternate screen and hide cursor
print("\u{001B}[?1049h", terminator: "")
print("\u{001B}[?25l", terminator: "")
fflush(stdout)
defer {
// Show cursor and exit alternate screen
print("\u{001B}[?25h", terminator: "")
print("\u{001B}[?1049l", terminator: "")
fflush(stdout)
}
// For redraws, move cursor home and clear lines individually
print("\u{001B}[H", terminator: "") // Cursor to home position
// Print each line with "\u{001B}[K" at end to clear rest of line
print("\u{001B}[J", terminator: "") // Clear from cursor to end of screen
```
## Debugging GitHub Actions Failures
Use the GitHub CLI to fetch and search logs:
```bash
# Get job logs via API
gh api repos/OWNER/REPO/actions/jobs/JOB_ID/logs > /tmp/logs.txt
# Search for errors
grep -E "error:|\.swift:[0-9]+:[0-9]+: error" /tmp/logs.txt
# Check which commit CI ran on (may differ from branch HEAD for PR merge commits)
gh run view RUN_ID --json headSha,headBranch
```
Note: PR CI runs may test a merge commit that differs from the branch HEAD.
```