Add clearcut metrics tool to log start event

- Import asuite_cc_client lib to log start and exit event.
- By default silence root logger's stream handler since 3p libs
  (asuite_cc_client) may initial root logger no matter what level
  we're using.
- Remove workaround for oauth2client unexpceted warning message.

Bug: 131658799
Bug: 112803893
Test: atest acloud_test --host &
      m acloud & any acloud-dev commands & check the clearcut dashboard

Change-Id: I84ade2863d2f29b1122729f5109f665b6c4e4ac4
diff --git a/Android.bp b/Android.bp
index 65b7b79..ef42197 100644
--- a/Android.bp
+++ b/Android.bp
@@ -73,6 +73,7 @@
         "public/*_test.py",
         "public/actions/*_test.py",
         "internal/lib/*_test.py",
+        "metrics/*.py",
     ],
     libs: [
         "acloud_create",
@@ -83,6 +84,7 @@
         "acloud_proto",
         "acloud_public",
         "acloud_setup",
+        "asuite_cc_client",
         "py-apitools",
         "py-dateutil",
         "py-google-api-python-client",
@@ -180,6 +182,7 @@
          "metrics/*.py",
     ],
     libs: [
+         "asuite_cc_client",
          "asuite_metrics",
     ],
 }
diff --git a/create/create.py b/create/create.py
index 0769593..9dfba02 100644
--- a/create/create.py
+++ b/create/create.py
@@ -183,7 +183,7 @@
             setup.Run(args)
         else:
             print("Please run '#acloud setup' so we can get your host setup")
-            sys.exit()
+            sys.exit(constants.EXIT_BY_USER)
 
 
 def PreRunCheck(args):
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 147cd3a..8850329 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -168,8 +168,7 @@
                 delete.CleanupSSVncviewer(constants.CF_TARGET_VNC_PORT)
 
             else:
-                print("Exiting out")
-                sys.exit()
+                sys.exit(constants.EXIT_BY_USER)
         self._LaunchCvd(cmd)
 
     @staticmethod
diff --git a/create/remote_image_local_instance.py b/create/remote_image_local_instance.py
index 319fa08..08f0255 100644
--- a/create/remote_image_local_instance.py
+++ b/create/remote_image_local_instance.py
@@ -222,8 +222,7 @@
                 if answer.lower() == "y":
                     os.makedirs(download_dir)
                 else:
-                    print("Exiting acloud!")
-                    sys.exit()
+                    sys.exit(constants.EXIT_BY_USER)
 
             stat = os.statvfs(download_dir)
             available_space = stat.f_bavail*stat.f_bsize/(1024)**3
@@ -233,7 +232,6 @@
                                              "available_space":available_space,
                                              "required_space":_REQUIRED_SPACE})
                 if download_dir.lower() == "q":
-                    print("Exiting acloud!")
-                    sys.exit()
+                    sys.exit(constants.EXIT_BY_USER)
             else:
                 return download_dir
diff --git a/internal/constants.py b/internal/constants.py
index b5cb4f2..c5e064b 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -143,3 +143,6 @@
 LOCAL_INS_NAME = "local-instance"
 
 TEMP_ARTIFACTS_FOLDER = "acloud_image_artifacts"
+TOOL_NAME = "acloud"
+EXIT_BY_USER = 1
+EXIT_BY_ERROR = -99
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index 6f527a3..0b496d1 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -874,8 +874,7 @@
             continue
         # Filter out choices
         if choice == 0:
-            print("Exiting acloud.")
-            sys.exit()
+            sys.exit(constants.EXIT_BY_USER)
         if enable_choose_all and choice == max_choice:
             return answer_list
         if choice < 0 or choice > max_choice:
diff --git a/metrics/metrics.py b/metrics/metrics.py
index 5ec7027..1d8b1a3 100644
--- a/metrics/metrics.py
+++ b/metrics/metrics.py
@@ -26,9 +26,24 @@
 
 logger = logging.getLogger(__name__)
 
+# pylint: disable=broad-except
+def LogUsage(argv):
+    """Log acloud start event.
 
-def LogUsage():
-    """Log acloud run."""
+    Log acloud start event and the usage, following are the data we log:
+    - tool_name: All asuite tools are storing event in the same database. This
+      property is provided to distinguish different tools.
+    - command_line: Log all command arguments.
+    - test_references: Should be a list, we record the acloud sub-command.
+      e.g. create/delete/reconnect/..etc. We could use this property as filter
+      criteria to speed up query time.
+    - cwd: User's current working directory.
+    - os: The platform that users are working at.
+
+    Args:
+        argv: A list of system arguments.
+    """
+    # TODO(b131867764): We could remove this metics tool after we apply clearcut.
     try:
         from asuite import asuite_metrics
         asuite_metrics.log_event(_METRICS_URL, dummy_key_fallback=False,
@@ -36,8 +51,19 @@
     except ImportError:
         logger.debug("No metrics recorder available, not sending metrics.")
 
+    #Log start event via clearcut tool.
+    try:
+        from asuite import atest_utils
+        from asuite.metrics import metrics_utils
+        atest_utils.print_data_collection_notice()
+        metrics_utils.send_start_event(tool_name=constants.TOOL_NAME,
+                                       command_line=' '.join(argv),
+                                       test_references=[argv[0]])
+    except Exception as e:
+        logger.debug("Failed to send start event:%s", str(e))
 
 
+#TODO(b131867764): We could remove this metics tool after we apply clearcut.
 def _GetLdap():
     """Return string email username for valid domains only, None otherwise."""
     try:
@@ -52,3 +78,23 @@
     except Exception as e:
         logger.debug("error retrieving email: %s", e)
     return None
+
+# pylint: disable=broad-except
+def LogExitEvent(exit_code, stacktrace="", logs=""):
+    """Log acloud exit event.
+
+    A start event should followed by an exit event to calculate the consuming
+    time. This function will be run at the end of acloud main process or
+    at the init of the error object.
+
+    Args:
+        exit_code: Integer, the exit code of acloud main process.
+        stacktrace: A string of stacktrace.
+        logs: A string of logs.
+    """
+    try:
+        from asuite.metrics import metrics_utils
+        metrics_utils.send_exit_event(exit_code, stacktrace=stacktrace,
+                                      logs=logs)
+    except Exception as e:
+        logger.debug("Failed to send exit event:%s", str(e))
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 25a23e0..55b5735 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -51,6 +51,7 @@
 import logging
 import platform
 import sys
+import traceback
 
 # TODO: Remove this once we switch over to embedded launcher.
 # Exit out if python version is < 2.7.13 due to b/120883119.
@@ -72,13 +73,14 @@
         print("  POSIXLY_CORRECT=1 port -N install python27")
     sys.exit(1)
 
-# Needed to silence oauth2client.
-# This is a workaround to get rid of below warning message:
+# By Default silence root logger's stream handler since 3p lib may initial
+# root logger no matter what level we're using. The acloud logger behavior will
+# be defined in _SetupLogging(). This also could workaround to get rid of below
+# oauth2client warning:
 # 'No handlers could be found for logger "oauth2client.contrib.multistore_file'
-# TODO(b/112803893): Remove this code once bug is fixed.
-OAUTH2_LOGGER = logging.getLogger('oauth2client.contrib.multistore_file')
-OAUTH2_LOGGER.setLevel(logging.CRITICAL)
-OAUTH2_LOGGER.addHandler(logging.FileHandler("/dev/null"))
+DEFAULT_STREAM_HANDLER = logging.StreamHandler()
+DEFAULT_STREAM_HANDLER.setLevel(logging.CRITICAL)
+logging.getLogger().addHandler(DEFAULT_STREAM_HANDLER)
 
 # pylint: disable=wrong-import-position
 from acloud import errors
@@ -86,6 +88,7 @@
 from acloud.create import create_args
 from acloud.delete import delete
 from acloud.delete import delete_args
+from acloud.internal import constants
 from acloud.reconnect import reconnect
 from acloud.reconnect import reconnect_args
 from acloud.list import list as list_instances
@@ -341,7 +344,6 @@
     # Check access.
     # device_driver.CheckAccess(cfg)
 
-    metrics.LogUsage()
     report = None
     if args.which == create_args.CMD_CREATE:
         create.Run(args)
@@ -393,4 +395,19 @@
 
 
 if __name__ == "__main__":
-    main(sys.argv[1:])
+    EXIT_CODE = None
+    EXCEPTION_STACKTRACE = None
+    EXCEPTION_LOG = None
+    metrics.LogUsage(sys.argv[1:])
+    try:
+        EXIT_CODE = main(sys.argv[1:])
+    except Exception as e:
+        EXIT_CODE = constants.EXIT_BY_ERROR
+        EXCEPTION_STACKTRACE = traceback.format_exc()
+        EXCEPTION_LOG = str(e)
+        raise
+    finally:
+        # Log Exit event here to calculate the consuming time.
+        metrics.LogExitEvent(EXIT_CODE,
+                             stacktrace=EXCEPTION_STACKTRACE,
+                             logs=EXCEPTION_LOG)
diff --git a/setup/setup.py b/setup/setup.py
index 8b5eadb..ad498f4 100644
--- a/setup/setup.py
+++ b/setup/setup.py
@@ -95,7 +95,7 @@
     if constants.ENV_ANDROID_BUILD_TOP not in os.environ:
         print("Can't find $%s." % constants.ENV_ANDROID_BUILD_TOP)
         print("Please run '#source build/envsetup.sh && lunch <target>' first.")
-        sys.exit(1)
+        sys.exit(constants.EXIT_BY_USER)
 
     pre_setup_sh = os.path.join(os.environ.get(constants.ENV_ANDROID_BUILD_TOP),
                                 "tools",