remove tracking (rollbar + segment.io) (#570)

This commit is contained in:
tamarrow
2016-04-12 12:14:16 -07:00
parent 0ef5482114
commit 4a513f4492
5 changed files with 10 additions and 422 deletions

View File

@@ -39,16 +39,6 @@ Note that the DCOS CLI has tight integration with DCOS and certain
functionality may not work as expected or at all when using it directly with
Mesos and Marathon.
Controlling Analytics Reporting
-------------------------------
The CLI reports usage and exception information to Segment.io for production
usage.
If you wish to turn reporting of analytics off entirely,
you can set the :code:`core.reporting` property to false::
dcos config set core.reporting false
Dependencies
------------

View File

@@ -1,232 +0,0 @@
import json
import sys
import uuid
import dcoscli
import docopt
import rollbar
import six
from dcos import http, util
from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
SEGMENT_IO_CLI_ERROR_EVENT,
SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_PROD,
SEGMENT_URL)
from dcoscli.subcommand import default_doc
from requests.auth import HTTPBasicAuth
logger = util.get_logger(__name__)
session_id = uuid.uuid4().hex
def _track(conf):
"""
Whether or not to send reporting information
:param conf: dcos config file
:type conf: Toml
:returns: whether to send reporting information
:rtype: bool
"""
return dcoscli.version != 'SNAPSHOT' and conf.get('core.reporting', True)
def _segment_track(event, conf, properties):
"""
Send a segment.io 'track' event
:param event: name of event
:type event: string
:param conf: dcos config file
:type conf: Toml
:param properties: event properties
:type properties: dict
:rtype: None
"""
data = {'event': event,
'properties': properties,
'anonymousId': session_id}
_segment_request('track', data)
def _segment_request(path, data):
"""
Send a segment.io HTTP request
:param path: URL path
:type path: str
:param data: json POST data
:type data: dict
:rtype: None
"""
key = SEGMENT_IO_WRITE_KEY_PROD
try:
# Set both the connect timeout and the request timeout to 1s,
# to prevent rollbar from hanging the CLI commands
http.post('{}/{}'.format(SEGMENT_URL, path),
json=data,
auth=HTTPBasicAuth(key, ''),
timeout=(1, 1))
except Exception as e:
logger.exception(e)
def track_err(pool, exit_code, err, conf, cluster_id):
"""
Report error details to analytics services.
:param pool: thread pool
:type pool: ThreadPoolExecutor
:param exit_code: exit code of tracked process
:type exit_code: int
:param err: stderr of tracked process
:type err: str
:param conf: dcos config file
:type conf: Toml
:param cluster_id: dcos cluster id to send to segment
:type cluster_id: str
:rtype: None
"""
if not _track(conf):
return
# Segment.io calls are async, but rollbar is not, so for
# parallelism, we must call segment first.
_segment_track_err(pool, conf, cluster_id, err, exit_code)
_rollbar_track_err(conf, cluster_id, err, exit_code)
def segment_track_cli(pool, conf, cluster_id):
"""
Send segment.io cli event.
:param pool: thread pool
:type pool: ThreadPoolExecutor
:param conf: dcos config file
:type conf: Toml
:param cluster_id: dcos cluster id to send to segment
:type cluster_id: str
:rtype: None
"""
if not _track(conf):
return
props = _base_properties(conf, cluster_id)
pool.submit(_segment_track, SEGMENT_IO_CLI_EVENT, conf, props)
def _segment_track_err(pool, conf, cluster_id, err, exit_code):
"""
Send segment.io error event.
:param pool: thread pool
:type segment: ThreadPoolExecutor
:param conf: dcos config file
:type conf: Toml
:param cluster_id: dcos cluster id to send to segment
:type cluster_id: str
:param err: stderr of tracked process
:type err: str
:param exit_code: exit code of tracked process
:type exit_code: int
:rtype: None
"""
props = _base_properties(conf, cluster_id)
props['err'] = err
props['exit_code'] = exit_code
pool.submit(_segment_track, SEGMENT_IO_CLI_ERROR_EVENT, conf, props)
def _rollbar_track_err(conf, cluster_id, err, exit_code):
"""
Report to rollbar. Synchronous.
:param conf: dcos config file
:type conf: Toml
:param cluster_id: dcos cluster id to send to segment
:type cluster_id: str
:param err: stderr of tracked process
:type err: str
:param exit_code: exit code of tracked process
:type exit_code: int
:rtype: None
"""
rollbar.init(ROLLBAR_SERVER_POST_KEY, 'prod')
props = _base_properties(conf, cluster_id)
props['exit_code'] = exit_code
lines = err.split('\n')
if len(lines) >= 2:
title = lines[-2]
else:
title = err
props['stderr'] = err
try:
rollbar.report_message(title, 'error', extra_data=props)
except Exception as e:
logger.exception(e)
def _command():
""" Return the subcommand used in this dcos process.
:returns: subcommand used in this dcos process
:rtype: str
"""
args = docopt.docopt(default_doc("dcos"),
help=False,
options_first=True)
return args.get('<command>', "") or ""
def _base_properties(conf=None, cluster_id=None):
"""
These properties are sent with every analytics event.
:param conf: dcos config file
:type conf: Toml
:param cluster_id: dcos cluster id to send to segment
:type cluster_id: str
:rtype: dict
"""
if not conf:
conf = util.get_config()
if len(sys.argv) > 1:
cmd = 'dcos ' + _command()
full_cmd = 'dcos ' + ' '.join(sys.argv[1:])
else:
cmd = 'dcos'
full_cmd = 'dcos'
try:
dcos_hostname = six.moves.urllib.parse.urlparse(
conf.get('core.dcos_url')).hostname
except:
logger.exception('Unable to find the hostname of the cluster.')
dcos_hostname = None
conf = [prop for prop in list(conf.property_items())
if prop[0] != "core.dcos_acs_token"]
return {
'cmd': cmd,
'full_cmd': full_cmd,
'dcoscli.version': dcoscli.version,
'python_version': str(sys.version_info),
'config': json.dumps(conf),
'DCOS_HOSTNAME': dcos_hostname,
'CLUSTER_ID': cluster_id
}

View File

@@ -1,8 +0,0 @@
ROLLBAR_SERVER_POST_KEY = '62f87c5df3674629b143a137de3d3244'
SEGMENT_IO_WRITE_KEY_PROD = '51ybGTeFEFU1xo6u10XMDrr6kATFyRyh'
SEGMENT_IO_CLI_EVENT = 'dcos-cli'
SEGMENT_IO_CLI_ERROR_EVENT = 'dcos-cli-error'
SEGMENT_URL = 'https://api.segment.io/v1'
DCOS_PRODUCTION_ENV = 'DCOS_PRODUCTION'

View File

@@ -1,13 +1,11 @@
import os
import signal
import sys
from concurrent.futures import ThreadPoolExecutor
import dcoscli
import docopt
from dcos import constants, emitting, errors, http, mesos, subcommand, util
from dcos.errors import DCOSAuthenticationException, DCOSException
from dcoscli import analytics
from dcos import constants, emitting, errors, http, subcommand, util
from dcos.errors import DCOSException
from dcoscli.subcommand import SubcommandMain, default_doc
logger = util.get_logger(__name__)
@@ -48,39 +46,15 @@ def _main():
if not command:
command = "help"
cluster_id = None
if dcoscli.version != 'SNAPSHOT' and command and \
command not in ["config", "help"]:
try:
cluster_id = mesos.DCOSClient().metadata().get('CLUSTER_ID')
except DCOSAuthenticationException:
raise
except:
msg = 'Unable to get the cluster_id of the cluster.'
logger.exception(msg)
if command in subcommand.default_subcommands():
sc = SubcommandMain(command, args['<args>'])
else:
executable = subcommand.command_executables(command)
sc = subcommand.SubcommandProcess(
executable, command, args['<args>'])
# send args call to segment.io
with ThreadPoolExecutor(max_workers=2) as reporting_executor:
analytics.segment_track_cli(reporting_executor, config, cluster_id)
# the call to retrieve cluster_id must happen before we run the
# subcommand so that if you have auth enabled we don't ask for
# user/pass multiple times (with the text being out of order)
# before we can cache the auth token
if command in subcommand.default_subcommands():
sc = SubcommandMain(command, args['<args>'])
else:
executable = subcommand.command_executables(command)
sc = subcommand.SubcommandProcess(
executable, command, args['<args>'])
exitcode, err = sc.run_and_capture()
if err:
analytics.track_err(
reporting_executor, exitcode, err, config, cluster_id)
return exitcode
exitcode, _ = sc.run_and_capture()
return exitcode
def _config_log_level_environ(log_level):

View File

@@ -1,136 +0,0 @@
import json
import os
from functools import wraps
import dcoscli.analytics
import rollbar
from dcos import constants
from dcoscli.main import main
from mock import patch
ANON_ID = 0
USER_ID = 'test@mail.com'
def _mock(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
with patch('rollbar.init'), \
patch('rollbar.report_message'), \
patch('dcos.http.post'), \
patch('dcos.http.get'), \
patch('dcoscli.analytics.session_id'):
dcoscli.analytics.session_id = ANON_ID
fn()
return wrapper
@_mock
def test_cluster_id_not_sent_on_config_call():
"""Tests that cluster_id is not sent to segment.io on call to config
subcommand
"""
args = ['dcos', 'config', 'show']
with patch('sys.argv', args), \
patch('dcos.mesos.DCOSClient.metadata') as get_cluster_id:
assert main() == 0
assert get_cluster_id.call_count == 0
def test_dont_send_acs_token():
"""Tests that we donn't send acs token"""
args = ['dcos', 'help']
env = _env_reporting()
version = 'release'
with patch('sys.argv', args), \
patch.dict(os.environ, env), \
patch('dcoscli.version', version):
to_send = dcoscli.analytics._base_properties()
config_to_send = json.loads(to_send.get("config"))
assert "core.dcos_acs_token" not in config_to_send
@_mock
def test_no_exc():
'''Tests that a command which does not raise an exception does not
report an exception.
'''
args = ['dcos']
env = _env_reporting()
version = 'release'
with patch('sys.argv', args), \
patch.dict(os.environ, env), \
patch('dcoscli.version', version):
assert main() == 0
assert rollbar.report_message.call_count == 0
@_mock
def test_exc():
'''Tests that a command which does raise an exception does report an
exception.
'''
args = ['dcos']
env = _env_reporting()
version = 'release'
with patch('sys.argv', args), \
patch('dcoscli.version', version), \
patch.dict(os.environ, env), \
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
return_value=(1, "Traceback")), \
patch('dcoscli.analytics._segment_track') as track:
assert main() == 1
assert track.call_count == 2
assert rollbar.report_message.call_count == 1
def _env_reporting():
path = os.path.join('tests', 'data', 'analytics', 'dcos_reporting.toml')
return {constants.DCOS_CONFIG_ENV: path}
@_mock
def test_config_reporting_false():
'''Test that "core.reporting = false" blocks exception reporting.'''
args = ['dcos']
env = _env_no_reporting()
version = 'release'
with patch('sys.argv', args), \
patch('dcoscli.version', version), \
patch.dict(os.environ, env), \
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
return_value=(1, "Traceback")), \
patch('dcoscli.analytics._segment_track') as track:
assert main() == 1
assert track.call_count == 0
def _env_no_reporting():
path = os.path.join('tests', 'data', 'analytics', 'dcos_no_reporting.toml')
return {constants.DCOS_CONFIG_ENV: path}
def test_command_always_returns_str():
"""Test that _command() returns str even if not subcommand specified"""
args = ['dcos']
with patch('sys.argv', args):
assert dcoscli.analytics._command() == ""