# OpenTelemetry Logs for Swift

> Integrate logging with OpenTelemetry Swift, correlate logs with traces, and send structured logs to Uptrace.

![undefined](/devicon/swift-original.svg)This document covers logging integration with OpenTelemetry Swift. The OpenTelemetry Swift Logs API is currently under development. This guide shows how to correlate your existing logs with traces.

## Prerequisites

Before you start, make sure you have [configured OpenTelemetry Swift](/get/opentelemetry-swift) to export data to Uptrace.

If you are not familiar with logs terminology like structured logging or log-trace correlation, read the introduction to [OpenTelemetry Logs](/opentelemetry/logs) first.

## Overview

While the native OpenTelemetry Swift Logs API is being developed, you can still achieve log-trace correlation by:

1. **Using span events** - Add structured events to spans for log-like behavior
2. **Manual trace context injection** - Add trace IDs to your existing logs
3. **OSLog integration** - Use Apple's unified logging with trace context

## Using span events as logs

Span events provide a log-like experience within the context of a trace:

```swift
import OpenTelemetryApi

let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "MyApp",
    instrumentationVersion: "1.0.0"
)

func processRequest() {
    let span = tracer.spanBuilder(spanName: "process-request").startSpan()
    defer { span.end() }

    // Info-level log
    span.addEvent(name: "log", attributes: [
        "log.severity": AttributeValue.string("INFO"),
        "log.message": AttributeValue.string("Starting request processing")
    ])

    // Debug-level log with context
    span.addEvent(name: "log", attributes: [
        "log.severity": AttributeValue.string("DEBUG"),
        "log.message": AttributeValue.string("Fetching user data"),
        "user.id": AttributeValue.string("12345")
    ])

    // Warning log
    span.addEvent(name: "log", attributes: [
        "log.severity": AttributeValue.string("WARN"),
        "log.message": AttributeValue.string("Cache miss, fetching from database")
    ])
}
```

## Manual trace context injection

Add trace context to your existing logging infrastructure:

```swift
import Foundation
import OpenTelemetryApi
import os.log

class TracedLogger {
    private let logger: Logger
    private let subsystem: String
    private let category: String

    init(subsystem: String, category: String) {
        self.subsystem = subsystem
        self.category = category
        self.logger = Logger(subsystem: subsystem, category: category)
    }

    func info(_ message: String, span: Span? = nil) {
        let traceContext = getTraceContext(span: span)
        logger.info("\(message) \(traceContext)")
    }

    func error(_ message: String, error: Error? = nil, span: Span? = nil) {
        let traceContext = getTraceContext(span: span)
        if let error = error {
            logger.error("\(message): \(error.localizedDescription) \(traceContext)")
        } else {
            logger.error("\(message) \(traceContext)")
        }
    }

    func debug(_ message: String, span: Span? = nil) {
        let traceContext = getTraceContext(span: span)
        logger.debug("\(message) \(traceContext)")
    }

    private func getTraceContext(span: Span?) -> String {
        let activeSpan = span ?? OpenTelemetry.instance.contextProvider.activeSpan
        guard let span = activeSpan, span.context.isValid else {
            return ""
        }

        return "[trace_id=\(span.context.traceId.hexString) span_id=\(span.context.spanId.hexString)]"
    }
}

// Usage
let logger = TracedLogger(subsystem: "com.myapp", category: "network")

func fetchData() {
    let span = tracer.spanBuilder(spanName: "fetch-data").startSpan()
    defer { span.end() }

    logger.info("Starting data fetch", span: span)
    // Logs: "Starting data fetch [trace_id=abc123... span_id=def456...]"

    do {
        // ... fetch logic
        logger.info("Data fetched successfully", span: span)
    } catch {
        logger.error("Failed to fetch data", error: error, span: span)
    }
}
```

## OSLog integration

Integrate with Apple's OSLog while maintaining trace context:

```swift
import Foundation
import os.log
import OpenTelemetryApi

extension OSLog {
    static let tracing = OSLog(subsystem: "com.myapp", category: "tracing")
}

struct TracingLogger {
    static func log(
        _ message: StaticString,
        type: OSLogType = .default,
        _ args: CVarArg...
    ) {
        // Get active trace context
        var traceInfo = ""
        if let span = OpenTelemetry.instance.contextProvider.activeSpan,
           span.context.isValid {
            traceInfo = " [trace_id=\(span.context.traceId.hexString)]"
        }

        // Log with trace context
        os_log(message, log: .tracing, type: type, args)

        // Also log trace info separately for correlation
        if !traceInfo.isEmpty {
            os_log("Trace context: %{public}@", log: .tracing, type: type, traceInfo)
        }
    }
}
```

## Structured logging helper

Create a helper for structured log entries:

```swift
import Foundation
import OpenTelemetryApi

struct LogEntry {
    let severity: LogSeverity
    let message: String
    var attributes: [String: AttributeValue] = [:]
    let timestamp: Date

    enum LogSeverity: String {
        case trace = "TRACE"
        case debug = "DEBUG"
        case info = "INFO"
        case warn = "WARN"
        case error = "ERROR"
        case fatal = "FATAL"
    }

    init(severity: LogSeverity, message: String) {
        self.severity = severity
        self.message = message
        self.timestamp = Date()
    }

    mutating func setAttribute(key: String, value: String) {
        attributes[key] = .string(value)
    }

    mutating func setAttribute(key: String, value: Int) {
        attributes[key] = .int(value)
    }

    func emitToSpan(_ span: Span) {
        var eventAttributes = attributes
        eventAttributes["log.severity"] = .string(severity.rawValue)
        eventAttributes["log.message"] = .string(message)

        span.addEvent(name: "log", attributes: eventAttributes, timestamp: timestamp)
    }
}

// Usage
func processOrder(orderId: String) {
    let span = tracer.spanBuilder(spanName: "process-order").startSpan()
    defer { span.end() }

    var logEntry = LogEntry(severity: .info, message: "Processing order")
    logEntry.setAttribute(key: "order.id", value: orderId)
    logEntry.emitToSpan(span)

    // Process order...

    var successLog = LogEntry(severity: .info, message: "Order processed successfully")
    successLog.setAttribute(key: "order.id", value: orderId)
    successLog.setAttribute(key: "processing_time_ms", value: 150)
    successLog.emitToSpan(span)
}
```

## Recording errors as logs

Use semantic conventions for error logging:

```swift
func handleError(_ error: Error, span: Span) {
    span.addEvent(name: "exception", attributes: [
        "exception.type": AttributeValue.string(String(describing: type(of: error))),
        "exception.message": AttributeValue.string(error.localizedDescription),
        "exception.escaped": AttributeValue.bool(false)
    ])

    span.status = .error(description: error.localizedDescription)
}

// Usage
do {
    try riskyOperation()
} catch {
    handleError(error, span: span)
}
```

## Best practices

### Include trace context in all logs

Always include trace context when available:

```swift
func log(_ message: String, level: String = "INFO") {
    var logMessage = "[\(level)] \(message)"

    if let span = OpenTelemetry.instance.contextProvider.activeSpan,
       span.context.isValid {
        logMessage += " trace_id=\(span.context.traceId.hexString)"
        logMessage += " span_id=\(span.context.spanId.hexString)"
    }

    print(logMessage)
}
```

### Use structured attributes

Prefer structured attributes over string interpolation:

```swift
// Good: Structured attributes
span.addEvent(name: "log", attributes: [
    "log.message": .string("User logged in"),
    "user.id": .string(userId),
    "user.role": .string(role)
])

// Bad: String interpolation
span.addEvent(name: "log", attributes: [
    "log.message": .string("User \(userId) with role \(role) logged in")
])
```

### Avoid logging sensitive data

Never log passwords, tokens, or PII:

```swift
// Bad
span.addEvent(name: "log", attributes: [
    "log.message": .string("User authenticated"),
    "password": .string(password)  // Never do this!
])

// Good
span.addEvent(name: "log", attributes: [
    "log.message": .string("User authenticated"),
    "user.id": .string(userId)
])
```

## What's next?

- [Get started](/get/opentelemetry-swift)
- [Learn about OpenTelemetry Swift Tracing API](/get/opentelemetry-swift/tracing)
- [Learn about OpenTelemetry Swift Metrics API](/get/opentelemetry-swift/metrics)
- [Configure Resource Attributes](/get/opentelemetry-swift/resources)
