44
55import 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.
99public 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: ...)
4242public 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