Swift 5日志写入代码在macOS Yosemite失效问题排查求助
咱们先梳理下你的日志程序在macOS Yosemite(10.10)上失效的核心原因,再给出针对性的修复方案——毕竟新老系统的API行为、权限规则确实存在不少差异:
1. 目录创建的隐藏失败问题
你的defaultDirectory()方法里创建目录时用了:
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
withIntermediateDirectories: false意味着只有所有上级目录都存在时,才能创建最后一级Logs目录。但首次运行时TestingLog目录肯定不存在,这直接导致目录创建失败,后续日志写入自然无法进行。而Mojave可能因为系统权限或默认目录结构的变化,刚好让上级目录存在,所以能正常工作。
2. FileHandle的旧系统兼容性隐患
虽然你先创建了空文件,但FileHandle(forWritingAtPath:)在Yosemite上对文件权限的检查更严格;如果文件创建失败(比如目录不存在),这个方法会返回nil,你的代码直接跳过了写入逻辑,连错误提示都没有。
3. 路径拼接的不规范
用字符串硬拼接路径("\(url.path)/TestingLog/Logs")在旧系统中可能因为路径分隔符、特殊字符等问题出错,更安全的方式是用URL的appendingPathComponent方法来构建路径。
4. 完全缺失的错误日志
你的代码里几乎所有catch块都是空的,这导致你完全看不到Yosemite上到底哪里出了问题,根本没法定位故障。
修复后的完整代码
下面是修正后的Log.swift,解决了上述所有问题,同时保留了你需要的「追加日志、不删除原有内容」的核心需求:
import Foundation open class Log { open var maxFileSize: UInt64 = 102400 open var maxFileCount = 365 ///The directory in which the log files will be written open var directory = Log.defaultDirectory() { didSet { directory = NSString(string: directory).expandingTildeInPath let fileManager = FileManager.default if !fileManager.fileExists(atPath: directory) { do { try fileManager.createDirectory(atPath: directory, withIntermediateDirectories: true, attributes: nil) NSLog("Successfully created log directory at \(directory)") } catch { NSLog("Couldn't create directory at \(directory): \(error.localizedDescription)") } } } } open var currentPath: String { return "\(directory)/\(logName(0))" } ///The name of the log files open var name = "logfile" ///Whether or not logging also prints to the console open var printToConsole = true ///logging singleton open class var logger: Log { struct Static { static let instance: Log = Log() } return Static.instance } //the date formatter var dateFormatter: DateFormatter { let formatter = DateFormatter() formatter.timeStyle = .medium formatter.dateStyle = .medium // 为旧系统指定固定区域,避免日期格式异常 formatter.locale = Locale(identifier: "en_US_POSIX") return formatter } ///write content to the current log file. open func write(_ text: String) { let path = currentPath let fileManager = FileManager.default // 双重保险:确保日志文件的上级目录存在 let dirPath = (path as NSString).deletingLastPathComponent if !fileManager.fileExists(atPath: dirPath) { do { try fileManager.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil) } catch { NSLog("Failed to create log parent directory: \(error.localizedDescription)") return } } // 如果文件不存在,创建空文件 if !fileManager.fileExists(atPath: path) { do { try "".write(toFile: path, atomically: true, encoding: .utf8) } catch { NSLog("Failed to create log file: \(error.localizedDescription)") return } } // 安全打开文件句柄,避免空指针操作 guard let fileHandle = FileHandle(forWritingAtPath: path) else { NSLog("Failed to open file handle for path: \(path)") return } let dateStr = dateFormatter.string(from: Date()) let writeText = "[\(dateStr)]: \(text)\n" defer { fileHandle.closeFile() } // 确保文件一定会被关闭,避免资源泄漏 fileHandle.seekToEndOfFile() if let data = writeText.data(using: .utf8) { fileHandle.write(data) } else { NSLog("Failed to convert log text to UTF-8 data") } // 按需打印到控制台 if printToConsole { print(writeText) } } ///Recursive method call to rename log files func rename(_ index: Int) { let fileManager = FileManager.default let path = "\(directory)/\(logName(index))" let newPath = "\(directory)/\(logName(index+1))" if fileManager.fileExists(atPath: newPath) { rename(index+1) } do { try fileManager.moveItem(atPath: path, toPath: newPath) } catch { NSLog("Failed to rename log file \(path) to \(newPath): \(error.localizedDescription)") } } ///gets the log name func logName(_ num :Int) -> String { return "\(name)-\(num).log" } ///get the default log directory class func defaultDirectory() -> String { let fileManager = FileManager.default // 用URL拼接路径更安全,避免字符串拼接的兼容性问题 guard let libraryURL = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).last else { NSLog("Failed to get library directory URL") return "" } let logDirURL = libraryURL.appendingPathComponent("TestingLog").appendingPathComponent("Logs") let path = logDirURL.path if !fileManager.fileExists(atPath: path) && !path.isEmpty { do { try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) NSLog("Default log directory created at \(path)") } catch { NSLog("Failed to create default log directory: \(error.localizedDescription)") } } return path } } ///Writes content to the current log file public func logw(_ text: String) { Log.logger.write(text) }
关键修复点说明
- 目录创建逻辑优化:把
withIntermediateDirectories改为true,确保上级目录不存在时也能自动创建;同时在write方法里增加了目录存在性检查的双重保险。 - 完善错误日志:所有
catch块都添加了具体的错误信息打印,方便你在Yosemite上调试时快速定位问题。 - FileHandle安全处理:用
guard let检查FileHandle是否成功创建,避免空指针崩溃;同时用defer确保文件一定会被关闭,防止资源泄漏。 - 路径拼接规范:用
URL的appendingPathComponent构建目录路径,彻底避免字符串拼接带来的兼容问题。 - DateFormatter兼容性优化:指定
en_US_POSIX区域,避免旧系统上日期格式的异常显示。
这样修改后,你的日志功能应该能在macOS Yosemite上正常工作,同时完全满足「追加日志、保留原有内容」的需求。
内容的提问来源于stack exchange,提问作者BongJae Jeong

