blob: 3dfa7e1bf9378268da6c4219eceb0b9f7847e865 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/client/analytics_event.h"
#include <cstdlib>
#include "src/developer/debug/shared/logging/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace {
// Searches for the current user name in the given string and replaces it with a literal "$USER".
// If not present, returns the input string unmodified.
std::string ObfuscateUser(const std::string& string) {
const char* username = std::getenv("USER");
if (!username) {
// If the username is not set in the environment, give up.
return string;
}
size_t pos = string.find(username);
const size_t username_len = strlen(username);
std::string ret = string;
while (pos < ret.size()) {
std::string to_end;
size_t replace_end = pos + username_len;
// We have to special case the username being followed immediately by an escaped double-quote,
// which will be treated as the end of the string by std::string::replace below. We don't want
// to lose the rest of the string, so it's saved in a temporary.
if (replace_end < ret.size() && ret[replace_end] == '\"') {
to_end = ret.substr(replace_end);
}
ret.replace(pos, replace_end, "$USER");
if (!to_end.empty()) {
ret.append(to_end);
}
pos = ret.find(username);
}
return ret;
}
} // namespace
namespace zxdb {
// AnalyticsEvent ----------------------------------------------------------------------------------
AnalyticsEvent::AnalyticsEvent(const std::string& name, const std::string& session_id)
: analytics::core_dev_tools::Ga4Event(name) {
// Note: this is a special name, and is processed by analytics differently than typical custom
// parameters. This surfaces in the data as "ga_session_id", which (should) associate it with
// other built-in session metrics.
SetParameter("session_id", session_id);
}
// SessionStarted ----------------------------------------------------------------------------------
SessionStarted::SessionStarted(const std::string& session_id)
: AnalyticsEvent("session_started", session_id) {}
SessionConnected::SessionConnected(const std::string& session_id)
: AnalyticsEvent("session_connected", session_id) {}
// SessionConnected --------------------------------------------------------------------------------
void SessionConnected::SetRemoteType(SessionConnected::RemoteType type) {
std::string remote_type_string;
switch (type) {
case RemoteType::kRemoteAgent:
remote_type_string = "remote agent";
break;
case RemoteType::kLocalAgent:
remote_type_string = "local agent";
break;
case RemoteType::kMinidump:
remote_type_string = "minidump";
break;
}
SetParameter("remote_type", remote_type_string);
}
// SessionEnded ------------------------------------------------------------------------------------
SessionEnded::SessionEnded(const std::string& session_id)
: AnalyticsEvent("session_ended", session_id) {}
void SessionEnded::SetSessionTime(std::chrono::milliseconds session_time) {
SetParameter("session_length_ms", session_time.count());
}
// CommandEvent ------------------------------------------------------------------------------------
CommandEvent::CommandEvent(const std::string& session_id) : AnalyticsEvent("command", session_id) {}
// The CommandReport only fills in verbs, nouns, and switches using the well known strings for the
// defined set of the respective group. We will never receive user information in any of those
// strings. Arguments and error messages however, can contain arbitrary text, which must not contain
// user identifiable data.
void CommandEvent::FromCommandReport(const CommandReport& report) {
SetParameter("verb_id", report.verb_id);
SetParameter("verb", report.verb);
SetParameter("command_group", report.command_group);
SetParameter("noun_count", static_cast<int64_t>(report.nouns.size()));
for (size_t i = 0; i < report.nouns.size(); i++) {
SetParameter(fxl::StringPrintf("noun%zu", i), report.nouns[i].name);
SetParameter(fxl::StringPrintf("noun%zu_id", i), report.nouns[i].id);
SetParameter(fxl::StringPrintf("noun%zu_index", i), report.nouns[i].index);
}
SetParameter("argument_count", static_cast<int64_t>(report.arguments.size()));
for (size_t i = 0; i < report.arguments.size(); i++) {
SetParameter(fxl::StringPrintf("argument%zu", i), ObfuscateUser(report.arguments[i]));
}
SetParameter("switch_count", static_cast<int64_t>(report.switches.size()));
for (size_t i = 0; i < report.switches.size(); i++) {
SetParameter(fxl::StringPrintf("switch%zu", i), report.switches[i].name);
SetParameter(fxl::StringPrintf("switch%zu_id", i), report.switches[i].id);
SetParameter(fxl::StringPrintf("switch%zu_value", i), report.switches[i].value);
}
SetParameter("has_error", report.err.has_error());
SetParameter("error_code", static_cast<int>(report.err.type()));
SetParameter("error_string", ErrTypeToString(report.err.type()));
SetParameter("error_message", ObfuscateUser(report.err.msg()));
}
// ConsoleType -------------------------------------------------------------------------------------
ConsoleType::ConsoleType(const std::string& session_id)
: AnalyticsEvent("console_type", session_id) {}
void ConsoleType::SetConsoleType(Type type) {
std::string console_type_string;
switch (type) {
case Type::kCommandLine: {
console_type_string = "command_line";
break;
}
case Type::kCommandLineEmbedded: {
console_type_string = "command_line_embedded";
break;
}
case Type::kDebugAdapter: {
console_type_string = "debug_adapter";
break;
}
}
SetParameter("type", console_type_string);
}
} // namespace zxdb