feat: add opt-in analytics collection with privacy notice

This commit is contained in:
Paul Gauthier 2024-10-30 13:15:57 -07:00 committed by Paul Gauthier (aider)
parent bd59a8debf
commit 267872b7e4
4 changed files with 64 additions and 21 deletions

View file

@ -17,32 +17,53 @@ posthog_host = "https://us.i.posthog.com"
class Analytics:
# providers
mp = None
ph = None
# saved
user_id = None
permanently_disable = None
asked_opt_in = None
# ephemeral
logfile = None
def __init__(self, enable=False, logfile=None, permanently_disable=False):
def __init__(self, logfile=None, permanently_disable=False):
self.logfile = logfile
self.asked_opt_in = False
self.get_or_create_uuid()
if not enable or self.permanently_disable or permanently_disable:
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
self.disable(permanently_disable)
def enable(self):
if not self.user_id:
self.disable(False)
return
if self.user_id and not self.permanently_disable:
self.mp = Mixpanel(mixpanel_project_token)
self.ph = Posthog(project_api_key=posthog_project_api_key, host=posthog_host)
if self.permanently_disable:
self.disable(False)
return
def disable(self, permanently_disable):
if not self.asked_opt_in:
self.disable(False)
return
self.mp = Mixpanel(mixpanel_project_token)
self.ph = Posthog(project_api_key=posthog_project_api_key, host=posthog_host)
def disable(self, permanently):
self.mp = None
self.ph = None
if permanently_disable and not self.permanently_disable:
if permanently:
self.asked_opt_in = True
self.permanently_disable = True
self.save_data()
def need_to_ask(self):
return not self.asked_opt_in and not self.permanently_disable
def get_data_file_path(self):
data_file = Path.home() / ".aider" / "analytics.json"
data_file.parent.mkdir(parents=True, exist_ok=True)
@ -64,8 +85,8 @@ class Analytics:
self.permanently_disable = data.get("permanently_disable")
self.user_id = data.get("uuid")
self.asked_opt_in = data.get("asked_opt_in", False)
except json.decoder.JSONDecodeError:
pass
except (json.decoder.JSONDecodeError, OSError):
self.disable(permanently=False)
def save_data(self):
data_file = self.get_data_file_path()
@ -75,6 +96,7 @@ class Analytics:
asked_opt_in=self.asked_opt_in,
)
# Allow exceptions; crash if we can't record permanently_disabled=True, etc
data_file.write_text(json.dumps(data, indent=4))
def get_system_info(self):
@ -110,10 +132,10 @@ class Analytics:
properties["aider_version"] = __version__
if self.mp:
self.mp.track(self.user_id, event_name, properties)
self.mp.track(self.user_id, event_name, dict(properties))
if self.ph:
self.ph.capture(self.user_id, event_name, properties)
self.ph.capture(self.user_id, event_name, dict(properties))
if self.logfile:
log_entry = {

View file

@ -430,7 +430,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
args = parser.parse_args(argv)
if args.analytics_disable:
analytics = Analytics(enable=False, permanently_disable=True)
analytics = Analytics(permanently_disable=True)
print("Analytics have been permanently disabled.")
return
@ -495,9 +495,28 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
io = get_io(False)
io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)")
analytics = Analytics(
args.analytics, logfile=args.analytics_log, permanently_disable=args.analytics_disable
)
analytics = Analytics(logfile=args.analytics_log, permanently_disable=args.analytics_disable)
if args.analytics:
if analytics.need_to_ask():
io.tool_output(
"Aider respects your privacy and never collects your code, prompts, chats, keys or"
" any personal info."
)
io.tool_output(f"For more info: {urls.analytics}")
disable = not io.confirm_ask(
"Allow collection of anonymous analytics to help improve aider?"
)
analytics.asked_opt_in = True
if disable:
analytics.disable(permanently=True)
io.tool_output("Analytics have been permanently disabled.")
analytics.save_data()
io.tool_output()
analytics.enable()
analytics.event("launched")
if args.gui and not return_coder:
@ -796,7 +815,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
if args.exit:
return
analytics.event("cli session", main_model=main_model)
analytics.event("cli session", main_model=main_model, edit_format=main_model.edit_format)
thread = threading.Thread(target=load_slow_imports)
thread.daemon = True

View file

@ -11,3 +11,4 @@ large_repos = "https://aider.chat/docs/faq.html#can-i-use-aider-in-a-large-mono-
github_issues = "https://github.com/Aider-AI/aider/issues/new"
git_index_version = "https://github.com/Aider-AI/aider/issues/211"
install_properly = "https://aider.chat/docs/troubleshooting/imports.html"
analytics = "https://aider.chat/docs/more/analytics.html"

View file

@ -5,11 +5,12 @@ nav_order: 500
# Analytics
Aider collects anonymous analytics that are used to help
Aider can collect anonymous analytics to help
improve aider's ability to work with LLMs, edit code and complete user requests.
**Analytics are currently turned off by default**, but are
expected to be turned on by default in
a future release.
## Opt-in
Analytics are only collected if you agree and opt-in.
## Anonymous, no personal info