Pulse

Powerful logging system for Apple platforms. Provides log and network request recording and inspection functionality with real-time viewing and sharing capabilities. Modern solution addressing wide range of uses in development, debugging, and production monitoring.

loggingSwiftiOSnetworkdebuggingApple

Library

Pulse

Overview

A powerful logging system for Apple platforms that provides log and URLSession network request recording and inspection functionality directly from iOS apps. Built with SwiftUI as a framework, it offers real-time viewing and sharing capabilities as a modern solution. Logs are stored locally on devices, addressing wide-ranging use cases in development, debugging, and production monitoring, enabling QA teams and beta testers to easily view and share logs.

Details

Pulse 2025 edition continues evolving as an advanced logging framework supporting iOS 17.0 and later. It features event recording from URLSession and dependent frameworks (like Alamofire and Get) with direct app integration through PulseUI views. The core functionality PulseCore is available across all Apple platforms (iOS, macOS, watchOS, tvOS), providing log management through persistent storage. Integration with Pulse Pro, a dedicated macOS app, enables real-time log viewing and advanced analysis capabilities.

Key Features

  • Integrated Log System: Unified management of logs and network requests
  • SwiftUI Integration: Direct app integration through native UI components
  • Local Storage: Persistent log storage based on CoreData
  • Real-time Display: Immediate log confirmation through Pulse Pro integration
  • Privacy Protection: Logs retained within device, no external transmission
  • QA Support: On-device log verification and bug report attachment functionality

Pros and Cons

Pros

  • Direct integration into iOS apps with immediate use in test builds
  • Excellent integration with URLSession and major networking libraries like Alamofire
  • QA team support for on-device log verification and bug report creation
  • Privacy and security protection through local storage
  • Advanced real-time monitoring and analysis through Pulse Pro integration
  • Intuitive operation through SwiftUI native UI

Cons

  • Relatively new platform requirement of iOS 17.0 and later
  • Limited to Apple ecosystem without cross-platform support
  • Network-focused logging requires additional setup for general log management
  • Integration challenges with unified log management systems in large enterprises
  • Additional cost of Pulse Pro and requirement for macOS environment
  • Learning curve for complex configuration and customization

Reference Pages

Code Examples

Basic Setup and SwiftLog Integration

import Logging
import Pulse
import PulseLogHandler

// Bootstrap SwiftLog with PulseLogHandler
LoggingSystem.bootstrap { label in
    return PersistentLogHandler(label: label, store: LoggerStore.shared)
}

// Log recording using SwiftLog API
let logger = Logger(label: "com.example.component")

logger.info("Something notable happened")
logger.error("An error occurred", metadata: ["error": "\(error)"])
logger.debug("Debug information", metadata: ["userId": "uid-123"])

Direct LoggerStore Usage

import Pulse

// Direct message storage
LoggerStore.shared.storeMessage(
    label: "auth",
    level: .debug,
    message: "Will login user",
    metadata: ["userId": .string("uid-1")]
)

LoggerStore.shared.storeMessage(
    label: "network",
    level: .info,
    message: "API request completed",
    metadata: [
        "endpoint": .string("/api/users"),
        "responseTime": .string("150ms"),
        "statusCode": .string("200")
    ]
)

// Error log recording
LoggerStore.shared.storeMessage(
    label: "database",
    level: .error,
    message: "Database connection failed",
    metadata: [
        "error": .string("Connection timeout"),
        "retryCount": .string("3")
    ]
)

Network Logging with URLSessionProxy

import Pulse

// Enable Pulse only in debug builds
#if DEBUG
let session: URLSessionProtocol = URLSessionProxy(configuration: .default)
#else
let session: URLSessionProtocol = URLSession(configuration: .default)
#endif

// Same API as regular URLSession usage
let url = URL(string: "https://api.example.com/users")!
let task = session.dataTask(with: url) { data, response, error in
    if let error = error {
        print("Request failed: \(error)")
        return
    }
    
    if let data = data {
        print("Response data: \(data.count) bytes")
    }
}
task.resume()

// More advanced request example
var request = URLRequest(url: URL(string: "https://api.example.com/posts")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer token123", forHTTPHeaderField: "Authorization")

let postData = """
{
    "title": "New Post",
    "content": "This is a new blog post",
    "authorId": 123
}
""".data(using: .utf8)
request.httpBody = postData

let postTask = session.dataTask(with: request) { data, response, error in
    // Pulse automatically logs request/response
    if let httpResponse = response as? HTTPURLResponse {
        print("Status code: \(httpResponse.statusCode)")
    }
}
postTask.resume()

PulseUI Integration

import SwiftUI
import PulseUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("My App")
                    .font(.largeTitle)
                
                NavigationLink(destination: ConsoleView()) {
                    Text("Open Pulse Console")
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
            }
        }
    }
}

// Dedicated debug screen
struct DebugView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Log Console", destination: ConsoleView())
                NavigationLink("Network Monitor", destination: NetworkInspectorView())
                NavigationLink("Log Sharing", destination: LogSharingView())
            }
            .navigationTitle("Debug Tools")
        }
    }
}

// ConsoleView with custom configuration
struct CustomConsoleView: View {
    var body: some View {
        ConsoleView(store: LoggerStore.shared)
            .navigationTitle("App Logs")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Clear") {
                        LoggerStore.shared.removeAll()
                    }
                }
            }
    }
}

Application Integration and Configuration

import SwiftUI
import Pulse

@main
struct MyApp: App {
    init() {
        // Initialize Pulse at app launch
        #if DEBUG
        setupPulseLogging()
        #endif
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
    
    private func setupPulseLogging() {
        // Custom LoggerStore configuration
        let store = LoggerStore.shared
        
        // Automatic old log deletion (7 days)
        store.configuration.maximumAge = 7 * 24 * 60 * 60 // 7 days in seconds
        
        // Log level configuration
        store.configuration.minimumLevel = .debug
        
        // Initial log message
        store.storeMessage(
            label: "app.lifecycle",
            level: .info,
            message: "Application launched",
            metadata: [
                "version": .string(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"),
                "build": .string(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown")
            ]
        )
    }
}

// Feature-specific log wrapper
class AppLogger {
    private static let authLogger = Logger(label: "com.myapp.auth")
    private static let networkLogger = Logger(label: "com.myapp.network")
    private static let uiLogger = Logger(label: "com.myapp.ui")
    
    static func logAuthentication(_ message: String, metadata: [String: Any] = [:]) {
        let pulseMetadata = metadata.mapValues { Logger.MetadataValue.string(String(describing: $0)) }
        authLogger.info("\(message)", metadata: pulseMetadata)
    }
    
    static func logNetworkRequest(_ url: String, method: String, statusCode: Int? = nil) {
        var metadata: [String: Logger.MetadataValue] = [
            "url": .string(url),
            "method": .string(method)
        ]
        
        if let statusCode = statusCode {
            metadata["statusCode"] = .string("\(statusCode)")
        }
        
        networkLogger.info("Network request", metadata: metadata)
    }
    
    static func logUIEvent(_ event: String, screen: String) {
        uiLogger.debug("UI Event", metadata: [
            "event": .string(event),
            "screen": .string(screen)
        ])
    }
}

File Upload and Log Export

import Pulse

// Log sharing functionality
class LogSharingManager {
    static func exportLogs() {
        let store = LoggerStore.shared
        
        // Export logs as file
        let documentsPath = FileManager.default.urls(for: .documentDirectory, 
                                                   in: .userDomainMask).first!
        let logFileURL = documentsPath.appendingPathComponent("app_logs.txt")
        
        // Get logs in string format
        var logContent = "=== Application Logs ===\n"
        logContent += "Export Date: \(Date())\n\n"
        
        // Get recent messages
        let messages = store.allMessages()
        for message in messages.suffix(100) { // Latest 100 entries
            logContent += "[\(message.createdAt)] \(message.level): \(message.text)\n"
        }
        
        do {
            try logContent.write(to: logFileURL, atomically: true, encoding: .utf8)
            
            // Present share action sheet
            presentShareSheet(with: logFileURL)
        } catch {
            print("Log export error: \(error)")
        }
    }
    
    private static func presentShareSheet(with url: URL) {
        let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
        
        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let window = windowScene.windows.first {
            window.rootViewController?.present(activityVC, animated: true)
        }
    }
}

// QA feedback functionality
class QAFeedbackManager {
    static func generateBugReport(description: String) {
        let store = LoggerStore.shared
        
        // Bug report metadata
        let deviceInfo = [
            "device": UIDevice.current.model,
            "os": UIDevice.current.systemVersion,
            "app_version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
        ]
        
        // Record bug report log
        store.storeMessage(
            label: "qa.bug_report",
            level: .critical,
            message: "Bug Report: \(description)",
            metadata: deviceInfo.mapValues { .string($0) }
        )
        
        // Generate report with recent logs
        exportLogsForBugReport(description: description, deviceInfo: deviceInfo)
    }
    
    private static func exportLogsForBugReport(description: String, deviceInfo: [String: String]) {
        // Bug report-specific log export implementation
        let reportContent = generateDetailedReport(description: description, deviceInfo: deviceInfo)
        
        // Save as file and share
        saveAndShareReport(content: reportContent)
    }
    
    private static func generateDetailedReport(description: String, deviceInfo: [String: String]) -> String {
        var report = "=== Bug Report ===\n"
        report += "Report Date: \(Date())\n"
        report += "Description: \(description)\n\n"
        
        report += "=== Device Information ===\n"
        for (key, value) in deviceInfo {
            report += "\(key): \(value)\n"
        }
        
        report += "\n=== Related Logs ===\n"
        // Include recent error and warning level logs
        let store = LoggerStore.shared
        let recentErrorLogs = store.allMessages().filter { 
            $0.level == .error || $0.level == .critical || $0.level == .warning 
        }.suffix(20)
        
        for log in recentErrorLogs {
            report += "[\(log.createdAt)] \(log.level): \(log.text)\n"
        }
        
        return report
    }
    
    private static func saveAndShareReport(content: String) {
        // Implementation: Save and share report
    }
}