Swift能否实现栈追踪方法自动日志记录,同时支持自定义日志?
Swift 自动方法入栈日志+手动日志共存方案
这个需求完全可行,下面给你两种实用的实现思路,既能自动记录方法入栈日志,又保留手动添加自定义内容的能力:
1. 基于 Objective-C 运行时的方法交换方案
Swift 可以借助 Objective-C 运行时的方法交换(Method Swizzling)特性,自动拦截方法调用,在方法执行前插入日志逻辑。这种方案适合带有 @objc 标记或继承自 NSObject 的类。
实现步骤:
- 定义一个用于标记的空类,在其
load方法中完成方法交换逻辑 - 交换目标方法的实现,在原方法执行前自动调用 Logger 记录入栈日志
- 保留原 Logger 的手动调用接口,支持添加自定义消息或数据
示例代码:
import Foundation // 自动日志标记类,通过load方法完成方法交换 @objcMembers class AutoLogMarker: NSObject { static func load() { guard let originalMethod = class_getInstanceMethod(NSObject.self, #selector(NSObject.perform(_:with:))), let swizzledMethod = class_getInstanceMethod(NSObject.self, #selector(NSObject.swizzled_perform(_:with:))) else { return } method_exchangeImplementations(originalMethod, swizzledMethod) } @objc private func swizzled_perform(_ aSelector: Selector, with anArgument: Any?) { // 自动记录方法入栈日志 let className = NSStringFromClass(type(of: self)) let methodName = NSStringFromSelector(aSelector) Logger.log(fileName: className, functionName: methodName, lineNumber: #line) // 调用原方法逻辑 self.swizzled_perform(aSelector, with: anArgument) } } // 你的Logger类(保留手动调用能力) class Logger { static func log(fileName: String = #file, functionName: String = #function, lineNumber: Int = #line, customMessage: String? = nil) { var logContent = "[\(fileName.components(separatedBy: "/").last ?? "")] \(functionName) 第\(lineNumber)行" if let message = customMessage { logContent += " | 自定义内容:\(message)" } print(logContent) } } // 使用示例 @AutoLogMarker class TestService: NSObject { func handleRequest(param: [String: Any]) { // 手动添加自定义日志 Logger.log(customMessage: "接收到参数:\(param)") } } let service = TestService() service.handleRequest(param: ["id": 1001, "name": "test"])
2. 纯 Swift 属性包装器方案
如果不想依赖 Objective-C 运行时,可以用属性包装器包装方法,在调用方法时自动触发日志逻辑,更适合纯 Swift 环境。
实现步骤:
- 定义一个
@AutoLogged属性包装器,用于包裹需要自动日志的方法 - 在包装器的
wrappedValue中,先执行日志记录,再调用原方法 - 原 Logger 类保留手动调用接口,支持自定义内容
示例代码:
import Foundation // 属性包装器:自动记录方法入栈日志 @propertyWrapper struct AutoLogged<Input, Output> { private let originalFunc: (Input) -> Output init(wrappedValue: @escaping (Input) -> Output) { self.originalFunc = wrappedValue } var wrappedValue: (Input) -> Output { return { input in // 获取方法名称(可通过反射优化,去掉包装器相关的冗余字符) let funcName = String(describing: self.originalFunc).components(separatedBy: "(")[0] Logger.log(functionName: funcName, customMessage: "方法入栈") return self.originalFunc(input) } } } // 你的Logger类(保留手动调用能力) class Logger { static func log(fileName: String = #file, functionName: String = #function, lineNumber: Int = #line, customMessage: String? = nil) { var logContent = "[\(fileName.components(separatedBy: "/").last ?? "")] \(functionName) 第\(lineNumber)行" if let message = customMessage { logContent += " | 自定义内容:\(message)" } print(logContent) } } // 使用示例 class TestManager { @AutoLogged func processData(data: Data) { Logger.log(customMessage: "数据长度:\(data.count)字节") } } let manager = TestManager() manager.processData(data: "test content".data(using: .utf8)!)
注意事项
- 运行时方案需要类支持 Objective-C 交互,纯 Swift 类需添加
@objc或继承NSObject;属性包装器方案无此限制,但获取方法名时可能需要额外处理冗余字符 - 两种方案都能完美保留手动调用
Logger.log()添加自定义消息或数据的能力,完全覆盖你的需求
内容的提问来源于stack exchange,提问作者Islam.Ibrahim
相关产品推荐
相关产品推荐

