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: class Analytics:
# providers
mp = None mp = None
ph = None ph = None
# saved
user_id = None user_id = None
permanently_disable = None permanently_disable = None
asked_opt_in = None
# ephemeral
logfile = None logfile = None
def __init__(self, enable=False, logfile=None, permanently_disable=False): def __init__(self, logfile=None, permanently_disable=False):
self.logfile = logfile self.logfile = logfile
self.asked_opt_in = False
self.get_or_create_uuid() 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) self.disable(permanently_disable)
def enable(self):
if not self.user_id:
self.disable(False)
return return
if self.user_id and not self.permanently_disable: if self.permanently_disable:
self.mp = Mixpanel(mixpanel_project_token) self.disable(False)
self.ph = Posthog(project_api_key=posthog_project_api_key, host=posthog_host) 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.mp = None
self.ph = None self.ph = None
if permanently_disable and not self.permanently_disable:
if permanently:
self.asked_opt_in = True
self.permanently_disable = True self.permanently_disable = True
self.save_data() 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): def get_data_file_path(self):
data_file = Path.home() / ".aider" / "analytics.json" data_file = Path.home() / ".aider" / "analytics.json"
data_file.parent.mkdir(parents=True, exist_ok=True) data_file.parent.mkdir(parents=True, exist_ok=True)
@ -64,8 +85,8 @@ class Analytics:
self.permanently_disable = data.get("permanently_disable") self.permanently_disable = data.get("permanently_disable")
self.user_id = data.get("uuid") self.user_id = data.get("uuid")
self.asked_opt_in = data.get("asked_opt_in", False) self.asked_opt_in = data.get("asked_opt_in", False)
except json.decoder.JSONDecodeError: except (json.decoder.JSONDecodeError, OSError):
pass self.disable(permanently=False)
def save_data(self): def save_data(self):
data_file = self.get_data_file_path() data_file = self.get_data_file_path()
@ -75,6 +96,7 @@ class Analytics:
asked_opt_in=self.asked_opt_in, 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)) data_file.write_text(json.dumps(data, indent=4))
def get_system_info(self): def get_system_info(self):
@ -110,10 +132,10 @@ class Analytics:
properties["aider_version"] = __version__ properties["aider_version"] = __version__
if self.mp: 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: 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: if self.logfile:
log_entry = { 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) args = parser.parse_args(argv)
if args.analytics_disable: if args.analytics_disable:
analytics = Analytics(enable=False, permanently_disable=True) analytics = Analytics(permanently_disable=True)
print("Analytics have been permanently disabled.") print("Analytics have been permanently disabled.")
return 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 = get_io(False)
io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)") io.tool_warning("Terminal does not support pretty output (UnicodeDecodeError)")
analytics = Analytics( analytics = Analytics(logfile=args.analytics_log, permanently_disable=args.analytics_disable)
args.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") analytics.event("launched")
if args.gui and not return_coder: 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: if args.exit:
return 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 = threading.Thread(target=load_slow_imports)
thread.daemon = True 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" github_issues = "https://github.com/Aider-AI/aider/issues/new"
git_index_version = "https://github.com/Aider-AI/aider/issues/211" git_index_version = "https://github.com/Aider-AI/aider/issues/211"
install_properly = "https://aider.chat/docs/troubleshooting/imports.html" 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 # 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. 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 ## Opt-in
a future release.
Analytics are only collected if you agree and opt-in.
## Anonymous, no personal info ## Anonymous, no personal info