diff --git a/KiwiPods.xcodeproj/project.pbxproj b/KiwiPods.xcodeproj/project.pbxproj index 1b1d12e..eeff703 100644 --- a/KiwiPods.xcodeproj/project.pbxproj +++ b/KiwiPods.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D69C64D121E334FF00E22013 /* NetworkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69C64D021E334FF00E22013 /* NetworkModel.swift */; }; D6B031612189D172003431C6 /* KiwiPods.h in Headers */ = {isa = PBXBuildFile; fileRef = D6B0315F2189D172003431C6 /* KiwiPods.h */; settings = {ATTRIBUTES = (Public, ); }; }; D6B031692189D2C9003431C6 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B031682189D2C9003431C6 /* NetworkManager.swift */; }; + D6E90B7421E4D5BA0072876C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E90B7321E4D5B90072876C /* Logger.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -50,6 +51,7 @@ D6B0315F2189D172003431C6 /* KiwiPods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KiwiPods.h; sourceTree = ""; }; D6B031602189D172003431C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D6B031682189D2C9003431C6 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; + D6E90B7321E4D5B90072876C /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -153,6 +155,7 @@ isa = PBXGroup; children = ( D6B0315F2189D172003431C6 /* KiwiPods.h */, + D6E90B7221E4D5B90072876C /* Logger */, D66ADF5E21D504E5004499F2 /* ImagePicker */, D66ADF5D21D504D8004499F2 /* Social */, D66ADF3D21D50488004499F2 /* HashTextView */, @@ -172,6 +175,14 @@ path = Networking; sourceTree = ""; }; + D6E90B7221E4D5B90072876C /* Logger */ = { + isa = PBXGroup; + children = ( + D6E90B7321E4D5B90072876C /* Logger.swift */, + ); + path = Logger; + sourceTree = ""; + }; E7DA49B519516020E9531073 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -310,6 +321,7 @@ D66ADF4121D50488004499F2 /* HashTextView.swift in Sources */, D66ADF5821D504B0004499F2 /* ImagePicker.swift in Sources */, D66ADF4F21D504B0004499F2 /* ImagePickerController.swift in Sources */, + D6E90B7421E4D5BA0072876C /* Logger.swift in Sources */, D66ADF5421D504B0004499F2 /* TwitterHelper.swift in Sources */, D66ADF5521D504B0004499F2 /* ImagePickerCell.swift in Sources */, ); diff --git a/KiwiPods/Logger/Logger.swift b/KiwiPods/Logger/Logger.swift new file mode 100755 index 0000000..79eddb0 --- /dev/null +++ b/KiwiPods/Logger/Logger.swift @@ -0,0 +1,234 @@ +// +// Logger.swift +// +// Created by KiwiTech on 08/01/19. +// Copyright © 2019 KiwiTech. All rights reserved. +// + +import Foundation +import UIKit + +/** + * Singleton class to debugPrint custom log messages easier to read and analize. Based in glyphs and log levels + */ +public final class Logger { + + private static let kLogDirectoryName = "Logs" + + // MARK: Singleton Instance + class var shared: Logger { + struct Singleton { + static let instance = Logger() + } + return Singleton.instance + } + + // Enum defining our log levels + public enum Level: Int { + case verbose + case debug + case info + case warning + case error + case severe + + public var description: String { + switch self { + case .verbose: + return "Verbose" + case .debug: + return "Debug" + case .info: + return "Info" + case .warning: + return "Warning" + case .error: + return "Error" + case .severe: + return "Severe" + } + } + + public static let all: [Level] = [.verbose, .debug, .info, .warning, .error, .severe] + + func atLeast(_ level: Level) -> Bool { + return level.rawValue >= rawValue + } + } + + // Configuration settings + private struct Configuration { + var logLevel: Level + + var showLogLevel: Bool + var showThreadName: Bool + var showFunctionName: Bool + var showFileName: Bool + var showLineNumber: Bool + + var writeToFile: Bool + } + + private var configuration: Configuration! + + private init() { + // Set the defaut level to error + configuration = Configuration(logLevel: .error, showLogLevel: true, showThreadName: false, showFunctionName: true, showFileName: true, showLineNumber: true, writeToFile: false) + } + + // A shortcut method to configure the logger. + public class func setup(_ logLevel: Level, showLogLevel: Bool = true, showThreadName: Bool = false, showFunctionName: Bool = true, showFileName: Bool = true, showLineNumber: Bool = true, writeToFile: Bool = false) { + + Logger.shared.configuration = Configuration(logLevel: logLevel, showLogLevel: showLogLevel, showThreadName: showThreadName, showFunctionName: showFunctionName, showFileName: showFileName, showLineNumber: showLineNumber, writeToFile: writeToFile) + } + + // Log a message if the logger's log level is equal to or lower than the specified level. + private class func log(_ message: String, properties: [String: Any]? = nil, level: Level = .debug, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function, writeToFile: Bool! = Logger.shared.configuration.writeToFile) { + + let config = Logger.shared.configuration! + + if config.logLevel.atLeast(level) { + + var appendContents = Logger.formattedMessage(message, properties: properties, level: level, fileName: fileName, line: line, column: column, functionName: functionName) + debugPrint(appendContents) + + if writeToFile { + + let logFilePath = Logger.logFilePath() + if !FileManager.default.fileExists(atPath: logFilePath) { + appendContents = Logger.headerContent() + appendContents + debugPrint("Log file created") + } else { + appendContents = logdebugPrint() + appendContents + } + + appendContents += "\n\n" + + do { + try appendContents.write(toFile: logFilePath, atomically: true, encoding: String.Encoding.utf8) + } catch let error as NSError { + debugPrint("Unable to write : \(error.debugDescription)") + } + } + } + } + + private class func formattedMessage(_ message: String, properties: [String: Any]?, level: Level, fileName: String, line: Int, column: Int, functionName: String) -> String { + + let config = Logger.shared.configuration! + var appendContents = String() + appendContents += "\(Date().toString())" + appendContents += config.showLogLevel ? " [\(level.description)]" : "" + appendContents += config.showFileName ? " [\(sourceFileName(filePath: fileName))]" : "" + appendContents += config.showLineNumber ? " [\(line)]" : "" + + if config.showThreadName { + let name = __dispatch_queue_get_label(nil) + let queuename = String(cString: name, encoding: .utf8) + appendContents += config.showThreadName ? " [\(String(describing: queuename))]" : "" + } + + appendContents += config.showFunctionName ? " \(functionName)" : "" + appendContents += " -> \(message)" + + if let properties = properties { + + appendContents += "\nPROPERTIES ¬ \n" + + for (key, _) in properties { + assert(properties[key] != nil, "Event property cannot be null") + appendContents += "\(key): \"\(String(describing: properties[key]))\"\n" + } + } + + return appendContents + } + + private class func sourceFileName(filePath: String) -> String { + let components = filePath.components(separatedBy: "/") + return components.isEmpty ? "" : components.last! + } + + private class func logDirectoryPath() -> URL { + let url = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]).appendingPathComponent(Logger.kLogDirectoryName) + + if !FileManager.default.fileExists(atPath: url.path) { + + do { + try FileManager.default.createDirectory(atPath: url.path, withIntermediateDirectories: false, attributes: nil) + debugPrint("Log directory created") + } catch let error as NSError { + debugPrint("Unable to create directory : \(error.debugDescription)") + } + } + + return url + } + + private class func todayDate() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.locale = Locale.current + formatter.timeZone = TimeZone.current + return "\(formatter.string(from: Date()))" + } + + private class func logFilePath() -> String { + return "\(Logger.logDirectoryPath().appendingPathComponent(Logger.todayDate()).path).txt" + } + + private class func headerContent() -> String { + return "Log Created: \(Logger.todayDate())\nApp Name: \(String(describing: Bundle.main.object(forInfoDictionaryKey: "CFBundleName")))\nApp Bundle Identifier: \(Bundle.main.bundleIdentifier ?? "Not available")\nDevice: \(UIDevice.current.name) (\(UIDevice.current.systemName) \(UIDevice.current.systemVersion))\nLocalization: \(String(describing: NSLocale.current.languageCode))\n\n" + } + + private class func logdebugPrint() -> String { + var content: String? + let path = Logger.logFilePath() + + if FileManager.default.fileExists(atPath: path) { + do { + content = try? String(contentsOfFile: path, encoding: String.Encoding.utf8) + } + } + + return content ?? "" + } +} + +// MARK: Logging methods +extension Logger { + + public class func verbose(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function, writeToFile: Bool? = nil) { + Logger.log(message, properties: properties, level: .verbose, fileName: fileName, line: line, column: column, functionName: functionName, writeToFile: writeToFile) + } + + public class func debug(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function) { + Logger.log(message, properties: properties, level: .debug, fileName: fileName, line: line, column: column, functionName: functionName) + } + + public class func info(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function, writeToFile: Bool? = nil) { + Logger.log(message, properties: properties, level: .info, fileName: fileName, line: line, column: column, functionName: functionName, writeToFile: writeToFile) + } + + public class func warning(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function) { + Logger.log(message, properties: properties, level: .warning, fileName: fileName, line: line, column: column, functionName: functionName) + } + + public class func error(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function, writeToFile: Bool? = nil) { + Logger.log(message, properties: properties, level: .error, fileName: fileName, line: line, column: column, functionName: functionName, writeToFile: writeToFile) + } + + public class func severe(_ message: String, properties: [String: Any]? = nil, fileName: String = #file, line: Int = #line, column: Int = #column, functionName: String = #function) { + Logger.log(message, properties: properties, level: .severe, fileName: fileName, line: line, column: column, functionName: functionName, writeToFile: true) + } +} + +internal extension Date { + + func toString(format: String? = "MMM dd, yyyy") -> String { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = TimeZone.current + dateFormatter.dateFormat = format + return dateFormatter.string(from: self) + } +}