apps: Add support for processing brillo logs

Add support for processing brillo logs.  Brillo logs are similar to
ChromeOS netlogs with the exception of the log line timestamp, section
markers and some minor formatting in individual lines.

BUG=b:25672573
TEST=manually tested and ran grunt test

Change-Id: Ief291e73be3eb6c077f5a8bddd0fce4fff147d73
diff --git a/log_helper.js b/log_helper.js
index 8c7179f..5c92729 100644
--- a/log_helper.js
+++ b/log_helper.js
@@ -33,14 +33,32 @@
  */
 logHelper.MULTILINE_LOG_END = /---------- END ----------/;
 
+/**
+ * RegExp for brillo log start.
+ * @type {RegExp}
+ */
+logHelper.BRILLO_LOG_START = /--------- beginning of main/;
 
 /**
  * RegExp for log timestamp.
+ * Sample timestamp from ChomeOS net.log:
+ *   2015-10-28T15:40:30.879026-05:00
  * @type {RegExp}
  */
 logHelper.TIME_FORMAT =
     /\d{4}-\d+-\d+T\d{2}:\d{2}:\d{2}.\d{6}(-|\+)(\d{2}):(\d{2})/;
 
+/**
+ * RegExp for brillo log timestamp.  Due to an issue with multiple times being
+ * logged, also eliminates log lines that appear to be from the kernel.
+ * Sample valid timestamp from Brillo log:
+ *   10-30 22:49:30.243   238   238
+ * Sample timestamp to skip from Brillo log:
+ *   10-30 22:49:30.243     0     0
+ * @type {RegExp}
+ */
+logHelper.BRILLO_TIME_FORMAT =
+    /(\d{2}-\d{2}.\d{2}:\d{2}:\d{2}.\d{3})(?: +[1-9][0-9]* +[1-9][0-9]*)/;
 
 /**
  * Method to get time offset from log line.
@@ -50,6 +68,11 @@
  * @public
  */
 logHelper.getOffsetMS = function(timeMatchResults) {
+  if (timeMatchResults.length == 2) {
+    // Brillo logs omit time zone.  Assume all lines in same time zone.
+    return 0;
+  }
+
   // first get minutes
   var offset = parseInt(timeMatchResults[3]);
   // next get hours
@@ -122,6 +145,15 @@
       console.log('found the syslog section!: ' + logHolder.syslog.length);
       return i + logHolder.syslog.length;
     }
+  },
+  'brillo': {
+    re: logHelper.BRILLO_LOG_START,
+    handler: function(logHolder, logLines, i) {
+      logHolder.fileType = 'brillo_log';
+      logHolder.brillolog = logHelper.getSubLog(logLines, i);
+      console.log('found a brillo log!: ' + logHolder.brillolog.length);
+      return i + logHolder.brillolog.length;
+    }
   }
 };
 
@@ -157,6 +189,10 @@
       // supplied file is syslog
       logHolder.fileType = 'syslog';
       logHolder.syslog = logLines;
+    } else if (netlogSummary.brillologTypeCheck(logLines)) {
+      // supplied file is a brillo log
+      logHolder.fileType = 'brillolog';
+      logHolder.brillolog = logLines;
     } else {
       console.log('file type unknown');
     }
@@ -181,6 +217,10 @@
     }
     var time = logLines[i].match(logHelper.TIME_FORMAT);
     if (time == null) {
+      console.log('try Brillo time format...');
+      time = logLines[i].match(logHelper.BRILLO_TIME_FORMAT);
+    }
+    if (time == null) {
       console.log('processing sublog, log line time was null');
     } else {
       subLog.push(logLines[i]);
diff --git a/netlog_summary.js b/netlog_summary.js
index 4ebf43a..4c4e2ee 100644
--- a/netlog_summary.js
+++ b/netlog_summary.js
@@ -389,7 +389,7 @@
     }
   },
   'supplicant_state': {
-    re: / wpa_supplicant\[.*\]: .*: State: (.*) -> (.*)/,
+    re: / wpa_supplicant\[?.*\]?: .*: State: (.*) -> (.*)/,
     handler: function(processingState, result) {
       var oldState = result[1].toUpperCase();
       var newState = result[2].toUpperCase();
@@ -442,13 +442,22 @@
   var timeCheck;
   var result = null;
   var processingState = new ProcessingState(logSummary, null, time, null);
+  var logType = 'netlog';
 
   for (var i = 0; i < logLines.length; i++) {
     // do we have a start time?
     time = logLines[i].match(logHelper.TIME_FORMAT);
     processingState.time = time;
     if (time == null) {
-      continue;
+      if (time = logLines[i].match(logHelper.BRILLO_TIME_FORMAT)) {
+        time[0] = time[1];
+        processingState.time = time;
+        logType = 'brillo';
+      } else {
+        console.log('logline: ', logLines[i]);
+        console.log('not a valid time...  continue');
+        continue;
+      }
     }
     logHelper.getOffsetMS(time);
     timeCheck = Date.parse(time[0]);
@@ -476,7 +485,7 @@
     var tempLogText = {text: logLines[i],
                        tag: time[0],
                        type: 'basic',
-                       file: 'netlog',
+                       file: logType,
                        ts: timeCheck};
     result = null;
     for (var taggedLine in netlogSummary.taggedLines) {
@@ -516,7 +525,25 @@
  */
 netlogSummary.netlogTypeCheck = function(logLines) {
   for (var i = 0; i < logLines.length; i++) {
-    if (netlogSummary.taggedLines['service_state'].re.test(logLines[i])) {
+    if (netlogSummary.taggedLines['service_state'].re.test(logLines[i]) &&
+        logHelper.TIME_FORMAT.test(logLines[i])) {
+      return true;
+    }
+  }
+  return false;
+};
+
+/**
+ * This method processes each log line in the supplied String array to determine
+ * if the log is a brillolog.
+ *
+ * @param {String[]} logLines String array for lines in a log.
+ * @return {boolean} Returns true is the log is a netlog, false otherwise.
+ */
+netlogSummary.brillologTypeCheck = function(logLines) {
+  for (var i = 0; i < logLines.length; i++) {
+    if (netlogSummary.taggedLines['service_state'].re.test(logLines[i]) &&
+        logHelper.BRILLO_TIME_FORMAT.test(logLines[i])) {
       return true;
     }
   }
diff --git a/process_log.js b/process_log.js
index 58d9aad..f9b3cca 100644
--- a/process_log.js
+++ b/process_log.js
@@ -55,6 +55,11 @@
         return;
       }
 
+      if (logHolder.brillolog) {
+        logHolder.brillolog = netlogSummary.processLogLines(logHolder.brillolog,
+                                                            logSummary);
+      }
+
       if (logHolder.netlog) {
         logHolder.netlog = netlogSummary.processLogLines(logHolder.netlog,
                                                          logSummary);
@@ -64,11 +69,11 @@
         logHolder.syslog = syslogSummary.processLogLines(logHolder.syslog);
       }
 
-      if (logHolder.netlog) {
+      if (logHolder.netlog || logHolder.brillolog) {
         displayLogSummary(logSummary);
         displayServiceSummary(logSummary);
       }
-      if (logHolder.syslog || logHolder.netlog) {
+      if (logHolder.syslog || logHolder.netlog || logHolder.brillolog) {
         displayLog(logHolder);
       } else {
         displayError();
@@ -404,6 +409,14 @@
   var netLength = 0;
   if (logHolder.netlog)
     netLength = logHolder.netlog.length;
+  if (logHolder.brillolog) {
+    if (logHolder.netlog) {
+      console.error('processor holds netlog and brillo log - error!');
+      return;
+    }
+    netLength = logHolder.brillolog.length;
+    logHolder.netlog = logHolder.brillolog;
+  }
   var sysLength = 0;
   if (logHolder.syslog)
     sysLength = logHolder.syslog.length;
diff --git a/service_summary.css b/service_summary.css
index ae13ba0..47ff323 100644
--- a/service_summary.css
+++ b/service_summary.css
@@ -10,6 +10,7 @@
 .serviceState { background-color: #FFC966; }
 .netlog { background-color: #b0c4de; }
 .syslog { background-color: #663399; }
+.brillo { background-color: #b0deca; }
 .netevt { background-color: steelblue; }
 
 .legend {
diff --git a/test/spec/LogHelperSpec.js b/test/spec/LogHelperSpec.js
index 97c60de..8960685 100644
--- a/test/spec/LogHelperSpec.js
+++ b/test/spec/LogHelperSpec.js
@@ -56,6 +56,20 @@
     expect(logHolder.fileType).toEqual('netlog');
   });
 
+  it('should detect brillo logs', function() {
+    var brilloLog = [
+      '10-30 22:47:54.351   238   238 I /system/bin/shill: ' +
+          'INFO:service.cc(407)] Service 2: state Associating -> Idle',
+      '10-30 22:47:54.352   238   238 I /system/bin/shill: ' +
+          '[INFO:manager.cc(1390)] Service 2 updated; ' +
+          'state: Idle failure Unknown',
+      '01-12 00:52:22.343     0     0 I wlan    : ' +
+          'connection failed with 00:00:00:00:00:00 reason:1 and Status:9'
+    ];
+    var logHolder = logHelper.detectFileType(brilloLog);
+    expect(logHolder.fileType).toEqual('brillolog');
+  });
+
   it('should detect an unknown file type', function() {
     var randomFile = [
         'This is not a real log',