diff --git a/frontend-components/tables/index.html b/frontend-components/tables/index.html
index 980c760bb06..0649b65fc0b 100644
--- a/frontend-components/tables/index.html
+++ b/frontend-components/tables/index.html
@@ -18,17 +18,8 @@
const data = JSON.parse(window.json_data);
const log_id = data?.log_id || "";
- const posthog_obj = data?.posthog || {};
-
if (log_id != "" && log_id != "REPLACE_ME") {
- if (posthog_obj?.user_id) {
- posthog.identify(posthog_obj.user_id, {
- email: posthog_obj.email,
- });
- posthog.alias(posthog_obj.user_id, log_id);
- } else {
- posthog.identify(log_id);
- }
+ posthog.identify(log_id);
}
posthog.capture("table", {
diff --git a/openbb_terminal/base_helpers.py b/openbb_terminal/base_helpers.py
index eb8317a2c69..2b74460f464 100644
--- a/openbb_terminal/base_helpers.py
+++ b/openbb_terminal/base_helpers.py
@@ -4,6 +4,7 @@ import os
from typing import Any, List, Literal, Optional
from dotenv import load_dotenv
+from posthog import Posthog
from rich.console import Console
from openbb_terminal.core.config.paths import (
@@ -16,6 +17,11 @@ console = Console()
menus = Literal["", "featflags", "settings"]
+openbb_posthog = Posthog(
+ "phc_vhssDAMod5qIplznQ75Kdgz4aB1qPFmeVmfEOZ4hkRw",
+ host="https://app.posthog.com",
+)
+
def handle_error(name: str, default: Any, menu: menus = ""):
"""Handles the error by returning the default value and printing an
diff --git a/openbb_terminal/core/log/generation/formatter_with_exceptions.py b/openbb_terminal/core/log/generation/formatter_with_exceptions.py
index 752737a2351..24ac648ac38 100644
--- a/openbb_terminal/core/log/generation/formatter_with_exceptions.py
+++ b/openbb_terminal/core/log/generation/formatter_with_exceptions.py
@@ -75,8 +75,10 @@ class FormatterWithExceptions(logging.Formatter):
@staticmethod
def mock_home_directory(text: str) -> str:
- user_home_directory = str(HOME_DIRECTORY)
- text_mocked = text.replace(user_home_directory, "MOCKING_USER_PATH")
+ user_home_directory = str(HOME_DIRECTORY.as_posix())
+ text_mocked = text.replace("\\\\", "/").replace(
+ user_home_directory, "MOCKING_USER_PATH"
+ )
return text_mocked
diff --git a/openbb_terminal/core/models/profile_model.py b/openbb_terminal/core/models/profile_model.py
index 9cbe215ddd2..4db43c45ded 100644
--- a/openbb_terminal/core/models/profile_model.py
+++ b/openbb_terminal/core/models/profile_model.py
@@ -2,6 +2,7 @@ from typing import Dict
from pydantic.dataclasses import dataclass
+from openbb_terminal.base_helpers import openbb_posthog
from openbb_terminal.core.models import BaseModel
@@ -34,6 +35,7 @@ class ProfileModel(BaseModel):
self.email = email
self.username = self.email[: self.email.find("@")]
self.remember = remember
+ openbb_posthog.identify(self.uuid, {"email": self.email})
def get_uuid(self) -> str:
"""Get uuid.
diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index a503e4aaece..e3f4b6b616c 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -29,24 +29,22 @@
capture_pageview: false,
loaded: function (posthog) {
const log_id = window.plotly_figure?.log_id || "";
- const posthog_obj = window.plotly_figure?.posthog || {};
if (log_id != "" && log_id != "REPLACE_ME") {
- if (posthog_obj?.user_id) {
- console.log("aliasing", posthog_obj.user_id, log_id);
- posthog.identify(posthog_obj.user_id, { email: posthog_obj.email });
- posthog.alias(posthog_obj.user_id, log_id);
- } else {
- posthog.identify(log_id);
- }
- console.log("identified", log_id);
+ posthog.identify(log_id);
}
posthog.onFeatureFlags(function () {
- if (!posthog.isFeatureEnabled("record-pywry", {send_event: false})) {
+ if (
+ !posthog.isFeatureEnabled("record-pywry", { send_event: false })
+ ) {
posthog.stopSessionRecording();
}
- if (!posthog.isFeatureEnabled("collect-logs-pywry", {send_event: false})) {
+ if (
+ !posthog.isFeatureEnabled("collect-logs-pywry", {
+ send_event: false,
+ })
+ ) {
posthog.opt_out_capturing();
} else if (posthog.has_opted_out_capturing()) {
posthog.opt_in_capturing();
@@ -288,7 +286,10 @@
}
});
}
- if (globals.cmd_src != "" && window.plotly_figure?.posthog?.collect_logs) {
+ if (
+ globals.cmd_src != "" &&
+ window.plotly_figure?.posthog?.collect_logs
+ ) {
posthog.capture("chart", {
INFO: {
command: globals.cmd_src,
diff --git a/openbb_terminal/core/plots/table.html b/openbb_terminal/core/plots/table.html
index 01792886174..6dd72e9950d 100644
--- a/openbb_terminal/core/plots/table.html
+++ b/openbb_terminal/core/plots/table.html
@@ -18,17 +18,8 @@
const data = JSON.parse(window.json_data);
const log_id = data?.log_id || "";
- const posthog_obj = data?.posthog || {};
-
if (log_id != "" && log_id != "REPLACE_ME") {
- if (posthog_obj?.user_id) {
- posthog.identify(posthog_obj.user_id, {
- email: posthog_obj.email,
- });
- posthog.alias(posthog_obj.user_id, log_id);
- } else {
- posthog.identify(log_id);
- }
+ posthog.identify(log_id);
}
posthog.capture("table", {
diff --git a/openbb_terminal/loggers.py b/openbb_terminal/loggers.py
index 4265ad537ae..0f66070c20c 100644
--- a/openbb_terminal/loggers.py
+++ b/openbb_terminal/loggers.py
@@ -2,12 +2,16 @@
__docformat__ = "numpy"
# IMPORTATION STANDARD
+import atexit
+import json
import logging
+import re
import sys
import time
import uuid
from pathlib import Path
-from typing import Optional
+from platform import platform, python_version
+from typing import Any, Dict, Optional
# IMPORTATION THIRDPARTY
try:
@@ -18,6 +22,7 @@ else:
WITH_GIT = True
# IMPORTATION INTERNAL
+from openbb_terminal.base_helpers import openbb_posthog
from openbb_terminal.core.log.generation.directories import get_log_dir
from openbb_terminal.core.log.generation.formatter_with_exceptions import (
FormatterWithExceptions,
@@ -31,7 +36,10 @@ from openbb_terminal.core.log.generation.settings import (
LogSettings,
Settings,
)
-from openbb_terminal.core.log.generation.user_logger import get_user_uuid
+from openbb_terminal.core.log.generation.user_logger import (
+ NO_USER_PLACEHOLDER,
+ get_user_uuid,
+)
from openbb_terminal.core.session.current_system import (
get_current_system,
set_current_system,
@@ -88,6 +96,104 @@ def get_commit_hash(use_env=True) -> str:
return commit_hash
+class PosthogHandler(logging.Handler):
+ """Posthog Handler"""
+
+ def __init__(self, settings: Settings):
+ super().__init__()
+ self.settings = settings
+ self.app_settings = settings.app_settings
+ self.logged_in = False
+ atexit.register(openbb_posthog.shutdown)
+
+ def emit(self, record: logging.LogRecord):
+ try:
+ self.send(record=record)
+ except Exception:
+ self.handleError(record)
+
+ def log_to_dict(self, log_info: str) -> dict:
+ """Log to dict"""
+ log_regex = r"(KEYS|PREFERENCES|SYSTEM|CMD|QUEUE): (.*)"
+ log_dict: Dict[str, Any] = {}
+
+ for log in re.findall(log_regex, log_info):
+ log_dict[log[0]] = json.loads(log[1])
+
+ sdk_regex = r"({\"INPUT\":.*})"
+ if sdk_dict := re.findall(sdk_regex, log_info):
+ log_dict["SDK"] = json.loads(sdk_dict[0])
+
+ return log_dict
+
+ def send(self, record: logging.LogRecord):
+ """Send log record to Posthog"""
+
+ app_settings = self.app_settings
+
+ level_name = logging.getLevelName(record.levelno)
+ log_line = FormatterWithExceptions.filter_log_line(text=record.getMessage())
+
+ log_extra = self.extract_log_extra(record=record)
+ log_extra.update(dict(level=level_name, message=log_line))
+ event_name = f"log_{level_name.lower()}"
+
+ if log_dict := self.log_to_dict(log_info=log_line):
+ event_name = f"log_{list(log_dict.keys())[0].lower()}"
+
+ log_extra = {**log_extra, **log_dict}
+ log_extra.pop("message", None)
+
+ if re.match(r"^(START|END|INPUT:)", log_line):
+ return
+
+ if not self.logged_in and get_user_uuid() != NO_USER_PLACEHOLDER:
+ self.logged_in = True
+ openbb_posthog.alias(get_user_uuid(), app_settings.identifier)
+
+ openbb_posthog.capture(
+ app_settings.identifier,
+ event_name,
+ properties=log_extra,
+ )
+
+ def extract_log_extra(self, record: logging.LogRecord) -> Dict[str, Any]:
+ """Extract log extra from record"""
+
+ log_extra: Dict[str, Any] = {
+ "appName": self.app_settings.name,
+ "appId": self.app_settings.identifier,
+ "sessionId": self.app_settings.session_id,
+ "commitHash": self.app_settings.commit_hash,
+ "platform": platform(),
+ "pythonVersion": python_version(),
+ "terminalVersion": current_system.VERSION,
+ }
+
+ if get_user_uuid() != NO_USER_PLACEHOLDER:
+ log_extra["userId"] = get_user_uuid()
+
+ if hasattr(record, "extra"):
+ log_extra = {**log_extra, **record.extra}
+
+ if record.exc_info:
+ log_extra["exception"] = {
+ "type": str(record.exc_info[0]),
+ "value": str(record.exc_info[1]),
+ "traceback": self.format(record),
+ }
+
+ return log_extra
+
+
+def add_posthog_handler(settings: Settings):
+ app_settings = settings.app_settings
+ handler = PosthogHandler(settings=settings)
+ formatter = FormatterWithExceptions(app_settings=app_settings)
+ handler.setFormatter(formatter)
+ logging.getLogger().addHandler(handler)
+
+
def add_stdout_handler(settings: Settings):
app_settings = settings.app_settings
handler = logging.StreamHandler(sys.stdout)
@@ -153,6 +259,12 @@ def setup_handlers(settings: Settings):
FormatterWithExceptions.LOGFORMAT.replace("|", "-"),
)
+ if (
+ not any([current_system.TEST_MODE, current_system.LOGGING_SUPPRESS])
+ and current_system.LOG_COLLECT
+ ):
+ add_posthog_handler(settings=settings)
+
def setup_logging(
app_name: Optional[str] = None,
diff --git a/openbb_terminal/parent_classes.py b/openbb_terminal/parent_classes.py
index 95187fbae89..de1c1e7c396 100644
--- a/openbb_terminal/parent_classes.py
+++ b/openbb_terminal/parent_classes.py
@@ -300,10 +300,9 @@ class BaseController(metaclass=ABCMeta):
if self.queue:
joined_queue = self.COMMAND_SEPARATOR.join(self.queue)
if not self.contains_keys(joined_queue):
+ queue = {"path": self.PATH, "queue": joined_queue}
logger.info(
- "QUEUE: {'path': '%s', 'queue': '%s'}",
- self.PATH,
- joined_queue,
+ "QUEUE: %s", json.dumps(queue, default=str, ensure_ascii=False)
)
def log_cmd_and_queue(
diff --git a/poetry.lock b/poetry.lock
index 557d23fba1e..207b8b95791 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -3948,6 +3948,18 @@ build = ["blurb", "twine", "wheel"]
docs = ["sphinx"]
test = ["pytest (<5.4)", "pytest-cov"]
+[[package]]
+name = "monotonic"
+version = "1.6"
+description = "An implementation of time.monotonic() for Python 2 & < 3.3"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"},
+ {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"},
+]
+
[[package]]
name = "more-itertools"
version = "9.1.0"
@@ -5111,6 +5123,30 @@ setuptools = ">=38.6.0,<50.0.0 || >50.0.0"
statsmodels = ">=0.13.2"
urllib3 = "*"
+[[package]]
+name = "posthog"
+version = "3.0.1"
+description = "Integrate PostHog into any python application."
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+ {file = "posthog-3.0.1-py2.py3-none-any.whl", hash = "sha256:9c7f92fecc713257d4b2710d05b456569c9156fbdd3e85655ba7ba5ba6c7b3ae"},
+ {file = "posthog-3.0.1.tar.gz", hash = "sha256:57d2791ff5752ce56ba0f9bb8876faf3ca9208f1c2c6ceaeb5a2504c34493767"},
+]
+
+[package.dependencies]
+backoff = ">=1.10.0"
+monotonic = ">=1.5"
+python-dateutil = ">2.1"
+requests = ">=2.7,<3.0"
+six = ">=1.5"
+
+[package.extras]
+dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"]
+sentry = ["django", "sentry-sdk"]
+test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest"]
+
[[package]]
name = "praw"
version = "7.7.0"
@@ -9656,4 +9692,4 @@ optimization = ["Riskfolio-Lib"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8,<3.11, !=3.9.7"
-content-hash = "d2bc3a75f2aff5123a842c656d6e8a9d51fa829433dc0cb1a02deb1d5c6967d9"
+content-hash = "29f4161609dbefbe373d25a2f561b1a3d37072236e805195150c802e4eb4108f"
diff --git a/pyproject.toml b/pyproject.toml
index a3a301f2765..89f9e3c92be 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -114,6 +114,7 @@ setuptools-rust = {version = "^1.5.2", optional = true}
transformers = {version = "^4.26.1", optional = true}
yt-dlp = {version = "^2023.2.17", optional = true}
pydantic = "^1.10.5"
+posthog = "^3.0.1"
diff --git a/requirements-full.txt b/requirements-full.txt
index b4e35ccb566..509b2522798 100644
--- a/requirements-full.txt
+++ b/requirements-full.txt
@@ -154,6 +154,7 @@ mccabe==0.7.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and p
mdit-py-plugins==0.2.8 ; python_version >= "3.8" and python_version < "3.11" and python_full_version != "3.9.7"
mistune==2.0.5 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mock==4.0.3 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
+monotonic==1.6 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
more-itertools==9.1.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mplfinance==0.12.9b7 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mstarpy==0.0.4 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
@@ -203,6 +204,7 @@ plotly==5.13.1 ; python_version >= "3.8" and python_full_version != "3.9.7" and
pluggy==1.0.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
pmaw==3.0.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
pmdarima==2.0.3 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
+posthog==3.0.1 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
praw==7.7.0 ; python_version >= "3.8" and python_version < "3.11" and python_full_version != "3.9.7"
prawcore==2.3.0 ; python_version >= "3.8" and python_version < "3.11" and python_full_version != "3.9.7"
pre-commit==2.21.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
diff --git a/requirements.txt b/requirements.txt
index 9490ac55b3f..51d46cd9938 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -109,6 +109,7 @@ matplotlib-inline==0.1.6 ; python_version >= "3.8" and python_full_version != "3
matplotlib==3.7.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mccabe==0.7.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mistune==2.0.5 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
+monotonic==1.6 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
more-itertools==9.1.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mplfinance==0.12.9b7 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
mstarpy==0.0.4 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
@@ -142,6 +143,7 @@ pkgutil-resolve-name==1.3.10 ; python_version >= "3.8" and python_version < "3.9
platformdirs==3.1.1 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
plotly==5.13.1 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
pmaw==3.0.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
+posthog==3.0.1 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"
praw==7.7.0 ; python_version >= "3.8" and python_version < "3.11" and python_full_version != "3.9.7"
prawcore==2.3.0 ; python_version >= "3.8" and python_version < "3.11" and python_full_version != "3.9.7"
prometheus-client==0.16.0 ; python_version >= "3.8" and python_full_version != "3.9.7" and python_version < "3.11"