Add local logging to file.

PiperOrigin-RevId: 184161119
diff --git a/src/Tulsi/EventLogger.swift b/src/Tulsi/EventLogger.swift
index ebd53d5..266f39f 100644
--- a/src/Tulsi/EventLogger.swift
+++ b/src/Tulsi/EventLogger.swift
@@ -21,8 +21,16 @@
   private let verboseLevel: TulsiMessageLevel
   private var observer: NSObjectProtocol? = nil
 
-  init(verboseLevel: TulsiMessageLevel) {
+  private let logFile: FileHandle? = nil
+
+  init(verboseLevel: TulsiMessageLevel, logToFile: Bool=false) {
     self.verboseLevel = verboseLevel
+
+    if logToFile, let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String {
+      self.logFile = EventLogger.createLogFile(appName)
+    } else {
+      self.logFile = nil
+    }
   }
 
   deinit {
@@ -36,9 +44,7 @@
                                                       object: nil,
                                                       queue: nil) {
       [weak self] (notification: Notification) in
-        guard let item = LogMessage(notification: notification),
-            let verboseLevel = self?.verboseLevel,
-            verboseLevel.logRank.rawValue >= item.level.logRank.rawValue else {
+        guard let item = LogMessage(notification: notification) else {
           return
         }
         self?.logItem(item)
@@ -47,8 +53,56 @@
 
   // MARK: - Private methods
 
+  private static func createLogFile(_ appName: String) -> FileHandle? {
+    let fileManager = FileManager.default
+    guard let folder = fileManager.urls(for: .applicationSupportDirectory,
+                                        in: .userDomainMask).first else {
+      return
+    }
+
+    let tulsiFolder = folder.appendingPathComponent(appName)
+    var isDirectory: ObjCBool = false
+    let fileExists = fileManager.fileExists(atPath: tulsiFolder.path, isDirectory: &isDirectory)
+
+    if !fileExists || !isDirectory.boolValue {
+      do {
+        try fileManager.createDirectory(at: tulsiFolder,
+                                        withIntermediateDirectories: true,
+                                        attributes: nil)
+      } catch {
+        print("failed to create logging folder at \"\(tulsiFolder)\".")
+        return
+      }
+    }
+
+    let dateFormatter = DateFormatter()
+    dateFormatter.dateFormat = "yyyyMMdd_HHmmss"
+    let date = Date()
+    let dateString = dateFormatter.string(from: date)
+
+    let logFileUrl = tulsiFolder.appendingPathComponent("log_\(dateString).txt")
+    do {
+      fileManager.createFile(atPath: logFileUrl.path, contents: nil, attributes: nil)
+      try self.logFile = FileHandle(forWritingTo: logFileUrl)
+    } catch {
+      print("error creating log file at \"\(logFileUrl.path)\".")
+      return
+    }
+  }
+
   private func logItem(_ item: LogMessage) {
     let level: String = item.level.rawValue
+
+    let logString = "[\(level)] \(item.message)\n"
+    if let logFile = self.logFile, let logData = logString.data(using: .utf8) {
+      logFile.seekToEndOfFile()
+      logFile.write(logData)
+    }
+
+    guard self.verboseLevel.logRank.rawValue >= item.level.logRank.rawValue else {
+      return
+    }
+
     let details: String
     if let itemDetails = item.details {
       details = " \(itemDetails)"
diff --git a/src/Tulsi/TulsiCommandlineParser.swift b/src/Tulsi/TulsiCommandlineParser.swift
index 80b7710..f654ea3 100644
--- a/src/Tulsi/TulsiCommandlineParser.swift
+++ b/src/Tulsi/TulsiCommandlineParser.swift
@@ -42,6 +42,7 @@
     let buildStartupOptions: String?
     let buildOptions: String?
     let buildTargets: [String]?
+    let logToFile: Bool
 
     init() {
       bazel = nil
@@ -56,6 +57,7 @@
       buildStartupOptions = nil
       buildOptions = nil
       buildTargets = nil
+      logToFile = false
     }
 
     init(dict: [String: Any]) {
@@ -87,6 +89,7 @@
       buildStartupOptions = dict[TulsiCommandlineParser.ParamBuildStartupOptions] as? String
       buildOptions = dict[TulsiCommandlineParser.ParamBuildOptions] as? String
       buildTargets = dict[TulsiCommandlineParser.ParamBuildTargetLong] as? [String]
+      logToFile = dict[TulsiCommandlineParser.ParamLogToFile] as? Bool == true
     }
   }
 
@@ -122,6 +125,8 @@
   static let ParamBuildTargetShort = "-t"
   static let ParamBuildTargetLong = "--target"
 
+  static let ParamLogToFile = "--log-to-file"
+
   let arguments: Arguments
   let commandlineSentinalFound: Bool
 
@@ -254,6 +259,9 @@
           storeValueAt(i, forArgument: TulsiCommandlineParser.ParamBuildTargetLong, append: true)
           i += 1
 
+      case TulsiCommandlineParser.ParamLogToFile:
+        parsedArguments[TulsiCommandlineParser.ParamLogToFile] = true as AnyObject?
+
         default:
           print("Ignoring unknown option \"\(arg)\"")
       }
@@ -302,6 +310,8 @@
         "    Overrides \(ParamQuietLong) if both are present. ",
         "  \(ParamQuietLong): Hide all debug info messages (warning: may also hide some error details).",
         "  \(ParamAdditionalPathFilters) \"<paths>\": Space-delimited source filters to be included in the generated project.",
+        "  \(ParamLogToFile): Enable logging to ~/Library/Application Support/Tulsi/*.txt.",
+        "       Ignores specified verbosity; everything will be logged to file.",
         ""
     ]
     print(usage.joined(separator: "\n") + "\n")
diff --git a/src/Tulsi/main.swift b/src/Tulsi/main.swift
index 8958310..557c66a 100644
--- a/src/Tulsi/main.swift
+++ b/src/Tulsi/main.swift
@@ -19,7 +19,8 @@
   // Parse the commandline parameters to see if the app should operate in headless mode or not.
   let commandlineParser = TulsiCommandlineParser()
 
-  let consoleLogger = EventLogger(verboseLevel: commandlineParser.arguments.verboseLevel)
+  let consoleLogger = EventLogger(verboseLevel: commandlineParser.arguments.verboseLevel,
+                                  logToFile: commandlineParser.arguments.logToFile)
   consoleLogger.startLogging()
 
   if !commandlineParser.commandlineSentinalFound {