You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:20:28