Skip to content

Commit 4474bb6

Browse files
improvements & fix
Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
1 parent 7335632 commit 4474bb6

1 file changed

Lines changed: 84 additions & 25 deletions

File tree

Sources/NextcloudKit/Log/NKLogFileManager.swift

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44

55
import Foundation
66

7-
/// Defines the severity level of a log message.
8-
/// Defines the level of log verbosity.
7+
// Defines the severity level of a log message.
8+
// Defines the level of log verbosity.
99
public enum NKLogLevel: Int, CaseIterable, Identifiable, Comparable {
10-
/// Logging is disabled.
10+
// Logging is disabled.
1111
case disabled = 0
1212

13-
/// Logs basic request lifecycle for developers (request started, response result).
13+
// Logs basic request lifecycle for developers (request started, response result).
1414
case compact = 1
1515

16-
/// Logs important info such as result content, errors.
16+
// Logs important info such as result content, errors.
1717
case normal = 2
1818

19-
/// Logs detailed debug info like headers and bodies.
19+
// Logs detailed debug info like headers and bodies.
2020
case verbose = 3
2121

2222
// Needed for Picker
@@ -38,7 +38,7 @@ public enum NKLogLevel: Int, CaseIterable, Identifiable, Comparable {
3838
}
3939
}
4040

41-
/// Type for writes a emonji in writeLog(tag: ...)
41+
/// Type for writes a emoji in writeLog(tag: ...)
4242
public enum NKLogTagEmoji: String {
4343
case error = "[ERROR]"
4444
case success = "[SUCCESS]"
@@ -83,6 +83,10 @@ public final class NKLogFileManager {
8383
private let rotationQueue = DispatchQueue(label: "LogRotationQueue")
8484
private let fileManager = FileManager.default
8585

86+
/// Cache for dynamic format strings, populated at runtime. Thread-safe via serial queue.
87+
private static var cachedDynamicFormatters: [String: DateFormatter] = [:]
88+
private static let formatterAccessQueue = DispatchQueue(label: "com.yourapp.dateformatter.cache")
89+
8690
// MARK: - Initialization
8791

8892
private init(logLevel: NKLogLevel = .normal) {
@@ -99,8 +103,7 @@ public final class NKLogFileManager {
99103

100104
/// Sets configuration parameters for the logger.
101105
/// - Parameters:
102-
/// - logLevel: The log level.
103-
///
106+
/// - logLevel: The NKLogLevel { disabled .. verbose }
104107
private func setConfiguration(logLevel: NKLogLevel) {
105108
self.logLevel = logLevel
106109
}
@@ -142,13 +145,13 @@ public final class NKLogFileManager {
142145
/// Writes a tagged log message with a specific log level.
143146
/// - Parameters:
144147
/// - tag: A custom tag to classify the log message (e.g. "SYNC", "AUTH").
145-
/// - typeTag: the type tag .info, .debug, .warning, .error, .success ..
148+
/// - emoji: .info, .debug, .warning, .error, .success ..
146149
/// - message: The log message content.
147-
public func writeLog(tag: String, emonji: NKLogTagEmoji, message: String) {
150+
public func writeLog(tag: String, emoji: NKLogTagEmoji, message: String) {
148151
guard !tag.isEmpty else { return }
149152

150153
let taggedMessage = "[\(tag.uppercased())] \(message)"
151-
writeLog(taggedMessage, emonji: emonji)
154+
writeLog(taggedMessage, emoji: emoji)
152155
}
153156

154157
/// Writes a log message with an optional typeTag to determine console emoji.
@@ -157,16 +160,16 @@ public final class NKLogFileManager {
157160
///
158161
/// - Parameters:
159162
/// - message: The log message to record.
160-
/// - typeTag: Optional log type tag to determine console emoji (e.g. [INFO], [ERROR]).
161-
public func writeLog(_ message: String?, emonji: NKLogTagEmoji? = nil) {
163+
/// - emoji: Optional type to determine console emoji (e.g. [INFO], [ERROR]).
164+
public func writeLog(_ message: String?, emoji: NKLogTagEmoji? = nil) {
162165
guard logLevel != .disabled, let message = message else { return }
163166

164167
let fileTimestamp = Self.stableTimestampString()
165168
let consoleTimestamp = Self.localizedTimestampString()
166169
let fileLine = "\(fileTimestamp) \(message)\n"
167170

168171
// Determine which emoji to display in console
169-
let emoji = emonji.map { emojiColored($0.rawValue) } ?? emojiColored(message)
172+
let emoji = emoji.map { emojiColored($0.rawValue) } ?? emojiColored(message)
170173

171174
// Visual message with inline replacements
172175
let visualMessage = message
@@ -253,33 +256,89 @@ public final class NKLogFileManager {
253256
}
254257
}
255258

256-
// MARK: - Date Helpers
259+
// MARK: - Cached DateFormatters
257260

258-
private static func currentDateString() -> String {
261+
/// Cached formatter for "yyyy-MM-dd". Uses current calendar, locale, and time zone.
262+
private static let cachedCurrentDateFormatter: DateFormatter = {
259263
let formatter = DateFormatter()
260264
formatter.calendar = Calendar.current
261265
formatter.locale = Locale.current
262266
formatter.timeZone = TimeZone.current
263267
formatter.dateFormat = "yyyy-MM-dd"
264-
return formatter.string(from: Date())
265-
}
268+
return formatter
269+
}()
266270

267-
private static func stableTimestampString() -> String {
271+
/// Cached formatter for "yyyy-MM-dd HH:mm:ss". Uses en_US_POSIX locale and Gregorian calendar for stable output.
272+
private static let cachedStableTimestampFormatter: DateFormatter = {
268273
let formatter = DateFormatter()
269274
formatter.calendar = Calendar(identifier: .gregorian)
270275
formatter.locale = Locale(identifier: "en_US_POSIX")
271276
formatter.timeZone = TimeZone.current
272277
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
273-
return formatter.string(from: Date())
274-
}
278+
return formatter
279+
}()
275280

276-
private static func localizedTimestampString() -> String {
281+
/// Cached formatter using `.short` dateStyle and `.medium` timeStyle with current calendar and locale.
282+
private static let cachedLocalizedTimestampFormatter: DateFormatter = {
277283
let formatter = DateFormatter()
278284
formatter.calendar = Calendar.current
279285
formatter.locale = Locale.current
280286
formatter.timeZone = TimeZone.current
281287
formatter.dateStyle = .short
282288
formatter.timeStyle = .medium
283-
return formatter.string(from: Date())
289+
return formatter
290+
}()
291+
292+
/// Returns a cached `DateFormatter` instance for the given format string.
293+
/// Formatters are created on-demand and reused to improve performance.
294+
private static func cachedFormatter(for format: String) -> DateFormatter {
295+
return formatterAccessQueue.sync {
296+
if let formatter = cachedDynamicFormatters[format] {
297+
return formatter
298+
}
299+
300+
let formatter = DateFormatter()
301+
formatter.dateFormat = format
302+
formatter.calendar = Calendar(identifier: .gregorian)
303+
formatter.locale = Locale(identifier: "en_US_POSIX")
304+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
305+
cachedDynamicFormatters[format] = formatter
306+
return formatter
307+
}
284308
}
285-
}
309+
310+
/// Converts a `String` into a `Date` using a cached formatter for the specified format.
311+
/// - Parameters:
312+
/// - string: The date string to convert.
313+
/// - format: The format pattern (e.g., "EEE, dd MMM y HH:mm:ss zzz").
314+
/// - Returns: A `Date` object if parsing succeeds; otherwise `nil`.
315+
public func convertDate(_ string: String, format: String) -> Date? {
316+
let formatter = Self.cachedFormatter(for: format)
317+
return formatter.date(from: string)
318+
}
319+
320+
/// Converts a `Date` to a `String` using a cached formatter for the specified format.
321+
/// - Parameters:
322+
/// - date: The `Date` to format.
323+
/// - format: The format string (e.g., "yyyy-MM-dd HH:mm:ss").
324+
/// - Returns: The formatted date string.
325+
public func convertDate(_ date: Date, format: String) -> String {
326+
let formatter = Self.cachedFormatter(for: format)
327+
return formatter.string(from: date)
328+
}
329+
330+
/// Returns today's date string in "yyyy-MM-dd" format using a cached formatter.
331+
private static func currentDateString() -> String {
332+
return cachedCurrentDateFormatter.string(from: Date())
333+
}
334+
335+
/// Returns a stable timestamp string in "yyyy-MM-dd HH:mm:ss" format using a cached formatter.
336+
private static func stableTimestampString() -> String {
337+
return cachedStableTimestampFormatter.string(from: Date())
338+
}
339+
340+
/// Returns a localized timestamp string using short date and medium time styles.
341+
private static func localizedTimestampString() -> String {
342+
return cachedLocalizedTimestampFormatter.string(from: Date())
343+
}
344+
}

0 commit comments

Comments
 (0)