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.
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
}
}