Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions posthog/tasks/test/test_usage_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -3152,10 +3152,11 @@ def test_logs_per_sdk_usage_metrics(self, billing_task_mock: MagicMock, posthog_
)

lines = ""
lines += self._logs_records_json(self.org_1_team_1.id, "web", 3)
lines += self._logs_records_json(self.org_1_team_1.id, "posthog-ios", 2)
lines += self._logs_records_json(self.org_1_team_1.id, "posthog-android", 1)
lines += self._logs_records_json(self.org_1_team_1.id, "posthog-ruby", 7)
lines += self._logs_records_json(self.org_1_team_2.id, "posthog-react-native", 4)
# A server SDK and an infra log (no telemetry.sdk.name) must not be counted.
lines += self._logs_records_json(self.org_1_team_2.id, "posthog-node", 5)
lines += self._logs_records_json(self.org_1_team_2.id, None, 6)
# Team 3 has log records but no app_metrics2 row, so the pre-filter excludes it entirely.
Expand All @@ -3174,9 +3175,9 @@ def test_logs_per_sdk_usage_metrics(self, billing_task_mock: MagicMock, posthog_
# telemetry.sdk.name are not counted; flutter ships no logs yet; team 5 has log records but
# no app_metrics2 row, so the pre-filter drops it entirely.
expected_counts: dict[str, tuple[dict, dict[str, int]]] = {
"org": (org_1_report, {"ios": 2, "react_native": 4, "android": 1, "flutter": 0}),
"team 3": (org_1_report["teams"]["3"], {"ios": 2, "android": 1, "react_native": 0}),
"team 4": (org_1_report["teams"]["4"], {"react_native": 4, "ios": 0}),
"org": (org_1_report, {"web": 3, "ios": 2, "react_native": 4, "android": 1, "flutter": 0, "ruby": 7}),
"team 3": (org_1_report["teams"]["3"], {"web": 3, "ios": 2, "android": 1, "react_native": 0, "ruby": 7}),
"team 4": (org_1_report["teams"]["4"], {"react_native": 4, "ios": 0, "web": 0, "ruby": 0}),
"team 5": (org_1_report["teams"]["5"], {"ios": 0}),
}
for scope, (counters, per_sdk) in expected_counts.items():
Expand Down
21 changes: 14 additions & 7 deletions posthog/tasks/usage_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,12 @@ class UsageReportCounters:
logs_retention_90d_mb_in_period: int
# Per-SDK split of logs_records_in_period, which on its own has no SDK dimension. Keyed off the
# telemetry.sdk.name resource attribute each SDK sets on every record. See SDK_TELEMETRY_NAMES.
# Web (browser) is intentionally absent: posthog-js doesn't set telemetry.sdk.name on logs yet.
web_logs_records_in_period: int
ios_logs_records_in_period: int
react_native_logs_records_in_period: int
android_logs_records_in_period: int
flutter_logs_records_in_period: int
ruby_logs_records_in_period: int


# Instance metadata to be included in overall report
Expand Down Expand Up @@ -2020,15 +2021,16 @@ def get_teams_with_logs_records_in_period(
)


# Maps the `telemetry.sdk.name` resource attribute (the mobile SDK package name, set on every log
# record) to the report field suffix used in `UsageReportCounters` / `_get_all_usage_data` keys.
# The browser SDK (posthog-js) is omitted: it doesn't set telemetry.sdk.name on logs (it only sets
# the OTLP scope name), so a "web" entry here would never match. Add it once posthog-js is fixed.
# Maps the `telemetry.sdk.name` resource attribute (the SDK identifier, set on every log record) to
# the report field suffix used in `UsageReportCounters` / `_get_all_usage_data` keys. posthog-js sets
# `web`; posthog-rails sets `posthog-ruby`; the mobile SDKs set their package name.
Comment thread
turnipdabeets marked this conversation as resolved.
SDK_TELEMETRY_NAMES: dict[str, str] = {
"web": "web",
"posthog-ios": "ios",
"posthog-react-native": "react_native",
"posthog-android": "android",
"posthog-flutter": "flutter",
"posthog-ruby": "ruby",
}


Expand All @@ -2043,8 +2045,9 @@ def get_teams_with_sdk_logs_records_in_period(
Returns log record counts grouped by team and PostHog SDK, for the given period.

The result is keyed by the short SDK suffix used on `UsageReportCounters`
(`ios`, `react_native`, `android`, `flutter`); each value is a list of
`(team_id, count)` tuples ready for `convert_team_usage_rows_to_dict`.
(the values of `SDK_TELEMETRY_NAMES` — `web`, `ios`, `react_native`,
`android`, `flutter`, `ruby`); each value is a list of `(team_id, count)`
tuples ready for `convert_team_usage_rows_to_dict`.

`team_ids_with_logs` must be the team_ids that produced any log records in the same period
(typically the result of `get_teams_with_logs_records_in_period`). It's used as a primary-key
Expand Down Expand Up @@ -2487,10 +2490,12 @@ def _get_all_usage_data(period_start: datetime, period_end: datetime) -> dict[st
"teams_with_logs_retention_30d_bytes_in_period": logs_retention_by_tier["30d"],
"teams_with_logs_retention_90d_bytes_in_period": logs_retention_by_tier["90d"],
"teams_with_logs_records_in_period": logs_records_rows,
"teams_with_web_logs_records_in_period": sdk_logs_by_suffix["web"],
"teams_with_ios_logs_records_in_period": sdk_logs_by_suffix["ios"],
"teams_with_react_native_logs_records_in_period": sdk_logs_by_suffix["react_native"],
"teams_with_android_logs_records_in_period": sdk_logs_by_suffix["android"],
"teams_with_flutter_logs_records_in_period": sdk_logs_by_suffix["flutter"],
"teams_with_ruby_logs_records_in_period": sdk_logs_by_suffix["ruby"],
}


Expand Down Expand Up @@ -2666,10 +2671,12 @@ def _get_team_report(all_data: dict[str, Any], team: Team) -> UsageReportCounter
logs_retention_90d_mb_in_period=int(
all_data["teams_with_logs_retention_90d_bytes_in_period"].get(team.id, 0) // 1_000_000
),
web_logs_records_in_period=all_data["teams_with_web_logs_records_in_period"].get(team.id, 0),
ios_logs_records_in_period=all_data["teams_with_ios_logs_records_in_period"].get(team.id, 0),
react_native_logs_records_in_period=all_data["teams_with_react_native_logs_records_in_period"].get(team.id, 0),
android_logs_records_in_period=all_data["teams_with_android_logs_records_in_period"].get(team.id, 0),
flutter_logs_records_in_period=all_data["teams_with_flutter_logs_records_in_period"].get(team.id, 0),
ruby_logs_records_in_period=all_data["teams_with_ruby_logs_records_in_period"].get(team.id, 0),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ def _canned_query_payload(query_name: str, team_a_id: int, team_b_id: int, *extr
return {"count": [(team_b_id, 100)], "read_bytes": [(team_b_id, 5_000_000)]}
if query_name == "sdk_logs_records":
return {
"web": [(team_a_id, 3)],
"ios": [(team_a_id, 4)],
"react_native": [(team_b_id, 6)],
"android": [],
"flutter": [],
"ruby": [(team_a_id, 7)],
}
if query_name == "logs_retention_bytes":
return {
Expand Down Expand Up @@ -235,6 +237,11 @@ async def test_aggregate_writes_chunks_and_manifest(minio_workflow_ctx: Workflow
assert by_org[str(org_b.id)]["api_queries_query_count"] == 100
assert by_org[str(org_b.id)]["api_queries_bytes_read"] == 5_000_000

# sdk_logs_records multi-key fan-out reaches the per-SDK log counters.
assert by_org[str(org_a.id)]["web_logs_records_in_period"] == 3
assert by_org[str(org_a.id)]["ios_logs_records_in_period"] == 4
assert by_org[str(org_a.id)]["ruby_logs_records_in_period"] == 7

# has_non_zero_usage is computed and present on every line.
assert by_org[str(org_a.id)]["has_non_zero_usage"] is True
assert by_org[str(org_b.id)]["has_non_zero_usage"] is True
Expand Down
2 changes: 2 additions & 0 deletions posthog/temporal/usage_report/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,12 @@ class QuerySpec:
fn=_sdk_logs_records,
output="multi",
multi_keys_mapping={
"web": "teams_with_web_logs_records_in_period",
Comment thread
turnipdabeets marked this conversation as resolved.
"ios": "teams_with_ios_logs_records_in_period",
"react_native": "teams_with_react_native_logs_records_in_period",
"android": "teams_with_android_logs_records_in_period",
"flutter": "teams_with_flutter_logs_records_in_period",
"ruby": "teams_with_ruby_logs_records_in_period",
},
),
QuerySpec(
Expand Down
Loading