Skip to content
Open
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dependencies = [
"rich>=14.1.0",
"sentry-arroyo>=2.47.0",
"sentry-options==1.2.10",
"sentry-sdk>=2.36.0",
"sentry-sdk>=2.70.0",
"sortedcontainers>=2.4.0",
"taskbroker-client>=0.20.26,<1",
"typing-extensions>=4.15.0",
Expand Down
22 changes: 16 additions & 6 deletions src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pathlib import Path
from typing import Any, Iterator, cast

import sentry_sdk
import sentry_sdk.traces

from objectstore_client import (
Client as ObjectstoreClient,
Expand Down Expand Up @@ -138,11 +138,21 @@ def process_message(
with contextlib.ExitStack() as stack:
stack.enter_context(request_context(artifact_id=artifact_id))
processing_start = time.monotonic()
scope = stack.enter_context(sentry_sdk.new_scope())
scope.set_tag("launchpad.project_id", project_id)
scope.set_tag("launchpad.organization_id", organization_id)
scope.set_tag("launchpad.preprod_artifact_id", artifact_id)
stack.enter_context(scope.start_transaction(op="subprocess", name="launchpad.process_message"))
scope = stack.enter_context(sentry_sdk.isolation_scope())
sentry_sdk.traces.new_trace()
artifact_attributes = {
"launchpad.project_id": project_id,
"launchpad.organization_id": organization_id,
"launchpad.preprod_artifact_id": artifact_id,
}
# Tags apply to errors, attributes apply to spans and logs
scope.set_tags(artifact_attributes)
scope.set_attributes(artifact_attributes)
stack.enter_context(
sentry_sdk.traces.start_span(
name="launchpad.process_message", attributes={"sentry.op": "subprocess"}, parent_span=None
)
)
statsd.increment("artifact.processing.started")
logger.info(f"Processing artifact {artifact_id} (project: {project_id}, org: {organization_id})")
try:
Expand Down
26 changes: 15 additions & 11 deletions src/launchpad/artifacts/android/aab.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping
from launchpad.parsers.android.icon.proto_xml_drawable_parser import (
Expand Down Expand Up @@ -41,7 +41,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
self._dex_mapping: DexMapping | None = None
self._universal_apk: APK | None = None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_manifest(self) -> AndroidManifest:
if self._manifest is not None:
return self._manifest
Expand All @@ -61,7 +61,7 @@ def get_manifest(self) -> AndroidManifest:
self._manifest = ProtoXmlUtils.proto_xml_to_android_manifest(manifest_buffer, proto_res_tables)
return self._manifest

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_resource_tables(self) -> list[ProtobufResourceTable]: # type: ignore[override]
if self._resource_table is not None:
return [self._resource_table]
Expand All @@ -79,25 +79,27 @@ def get_resource_tables(self) -> list[ProtobufResourceTable]: # type: ignore[ov
self._resource_table = ProtobufResourceTable(arsc_buffer)
return [self._resource_table]

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_primary_apks(self, device_spec: DeviceSpec = DeviceSpec()) -> list[APK]:
if self._primary_apks is not None:
return self._primary_apks

apks_dir = create_temp_directory("apks-")
try:
bundletool = Bundletool()
with sentry_sdk.start_span(op="aab", description="aab.build_primary_apks"):
with sentry_sdk.traces.start_span(name="aab.build_primary_apks", attributes={"sentry.op": "aab"}):
bundletool.build_apks(bundle_path=self.path, output_dir=apks_dir, device_spec=device_spec)

apks = []
for apk_path in apks_dir.glob("*.apk"):
tmp_dir = Path(tempfile.mkdtemp())
new_apk_path = tmp_dir / apk_path.name
with sentry_sdk.start_span(op="file.copy", description="aab.copy_primary_apk") as span:
span.set_data("apk.size", apk_path.stat().st_size)
with sentry_sdk.traces.start_span(
name="aab.copy_primary_apk", attributes={"sentry.op": "file.copy"}
) as span:
span.set_attribute("apk.size", apk_path.stat().st_size)
shutil.copyfile(apk_path, new_apk_path)
with sentry_sdk.start_span(op="aab", description="aab.initialize_primary_apk"):
with sentry_sdk.traces.start_span(name="aab.initialize_primary_apk", attributes={"sentry.op": "aab"}):
apks.append(
APK(
new_apk_path,
Expand All @@ -109,10 +111,12 @@ def get_primary_apks(self, device_spec: DeviceSpec = DeviceSpec()) -> list[APK]:
self._primary_apks = apks
return apks
finally:
with sentry_sdk.start_span(op="file.cleanup", description="aab.cleanup_primary_apks"):
with sentry_sdk.traces.start_span(
name="aab.cleanup_primary_apks", attributes={"sentry.op": "file.cleanup"}
):
cleanup_directory(apks_dir)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_universal_apk(self, apk_dir: Path, device_spec: DeviceSpec = DeviceSpec()) -> APK:
if self._universal_apk is not None:
return self._universal_apk
Expand Down Expand Up @@ -152,7 +156,7 @@ def get_dex_mapping(self) -> DexMapping | None:
self._dex_mapping = DexMapping(dex_mapping_buffer)
return self._dex_mapping

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_icon(self) -> bytes | None:
manifest = self.get_manifest()
if manifest.application is None:
Expand Down
46 changes: 22 additions & 24 deletions src/launchpad/artifacts/android/apk.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from typing import IO, Callable, Iterator

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping
from launchpad.parsers.android.icon.binary_xml_drawable_parser import (
Expand Down Expand Up @@ -40,7 +40,7 @@ def __init__(
super().__init__(path, cleanup=cleanup)
self._dex_mapping = dex_mapping
self._zip_provider = ZipProvider(path)
with sentry_sdk.start_span(op="file.extract", description="apk.extract"):
with sentry_sdk.traces.start_span(name="apk.extract", attributes={"sentry.op": "file.extract"}):
self._extract_dir = self._zip_provider.extract_to_temp_directory()
self._manifest: AndroidManifest | None = None
self._resource_table: BinaryResourceTable | None = None
Expand All @@ -54,7 +54,7 @@ def raw_file(self) -> Iterator[IO[bytes]]:
finally:
f.close()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_manifest(self) -> AndroidManifest:
if self._manifest is not None:
return self._manifest
Expand All @@ -74,7 +74,7 @@ def get_manifest(self) -> AndroidManifest:
self._manifest = AxmlUtils.binary_xml_to_android_manifest(manifest_buffer, binary_res_tables)
return self._manifest

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_resource_tables(self) -> list[BinaryResourceTable]: # type: ignore[override]
if self._resource_table is not None:
return [self._resource_table]
Expand All @@ -94,7 +94,7 @@ def get_resource_tables(self) -> list[BinaryResourceTable]: # type: ignore[over
self._resource_table = BinaryResourceTable(arsc_buffer)
return [self._resource_table]

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_class_definitions(self) -> list[ClassDefinition]:
if self._class_definitions is not None:
return self._class_definitions
Expand Down Expand Up @@ -122,25 +122,23 @@ def get_class_definitions(self) -> list[ClassDefinition]:
logger.error(f"Failed to read DEX file {dex_file}: {e}")
continue

if (span := sentry_sdk.get_current_span()) is not None:
span.set_data("dex_stats.file_count", dex_stats.file_count)
span.set_data("dex_stats.total_bytes", dex_stats.total_bytes)
span.set_data("dex_stats.read_duration_ms", dex_stats.read_duration_ms)
span.set_data("dex_stats.parser.duration_ms", dex_stats.parser_duration_ms)
span.set_data("dex_stats.parser.method_duration_ms", dex_stats.parser_method_duration_ms)
span.set_data("dex_stats.parser.field_duration_ms", dex_stats.parser_field_duration_ms)
span.set_data("dex_stats.parser.class_count", dex_stats.parser_class_count)
span.set_data("dex_stats.parser.method_count", dex_stats.parser_method_count)
span.set_data("dex_stats.parser.field_count", dex_stats.parser_field_count)
span.set_data(
"dex_stats.parser.method_annotation_scan_steps",
dex_stats.parser_method_annotation_scan_steps,
if (span := sentry_sdk.traces.get_current_span()) is not None:
span.set_attributes(
{
"dex_stats.file_count": dex_stats.file_count,
"dex_stats.total_bytes": dex_stats.total_bytes,
"dex_stats.read_duration_ms": dex_stats.read_duration_ms,
"dex_stats.parser.duration_ms": dex_stats.parser_duration_ms,
"dex_stats.parser.method_duration_ms": dex_stats.parser_method_duration_ms,
"dex_stats.parser.field_duration_ms": dex_stats.parser_field_duration_ms,
"dex_stats.parser.class_count": dex_stats.parser_class_count,
"dex_stats.parser.method_count": dex_stats.parser_method_count,
"dex_stats.parser.field_count": dex_stats.parser_field_count,
"dex_stats.parser.method_annotation_scan_steps": dex_stats.parser_method_annotation_scan_steps,
"dex_stats.parser.field_annotation_scan_steps": dex_stats.parser_field_annotation_scan_steps,
"dex_stats.parser.mapping_present": dex_stats.parser_mapping_present,
}
)
span.set_data(
"dex_stats.parser.field_annotation_scan_steps",
dex_stats.parser_field_annotation_scan_steps,
)
span.set_data("dex_stats.parser.mapping_present", dex_stats.parser_mapping_present)

return self._class_definitions

Expand All @@ -151,7 +149,7 @@ def get_apksigner_certs(self) -> str:
apksigner = Apksigner()
return apksigner.get_certs(self.path)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_icon(self) -> bytes | None:
manifest = self.get_manifest()
if manifest.application is None:
Expand Down
4 changes: 2 additions & 2 deletions src/launchpad/artifacts/android/zipped_aab.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping

Expand All @@ -25,7 +25,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
def get_manifest(self) -> AndroidManifest:
return self.get_aab().get_manifest()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_aab(self) -> AAB:
if self._aab is not None:
return self._aab
Expand Down
4 changes: 2 additions & 2 deletions src/launchpad/artifacts/android/zipped_apk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping

Expand All @@ -24,7 +24,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
def get_manifest(self) -> AndroidManifest:
return self.get_primary_apk().get_manifest()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_primary_apk(self) -> APK:
if self._primary_apk is not None:
return self._primary_apk
Expand Down
26 changes: 13 additions & 13 deletions src/launchpad/artifacts/apple/zipped_xcarchive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Any, List, NamedTuple

import lief
import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.apple.crushed_png import decode_crushed_png
from launchpad.utils.logging import get_logger
Expand Down Expand Up @@ -77,7 +77,7 @@ def __init__(self, path: Path) -> None:
def get_extract_dir(self) -> SafeDirectory:
return self._extract_dir

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_plist(self) -> dict[str, Any]:
if self._plist is not None:
return self._plist
Expand All @@ -94,7 +94,7 @@ def get_plist(self) -> dict[str, Any]:
except Exception as e:
raise RuntimeError("Failed to parse Info.plist") from e

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_archive_plist(self) -> dict[str, Any] | None:
"""Get the archive-level Info.plist (not the app bundle's Info.plist)."""
if self._archive_plist is not None:
Expand All @@ -118,7 +118,7 @@ def get_archive_plist(self) -> dict[str, Any] | None:
logger.debug(f"Failed to parse archive Info.plist at {plist_path}", exc_info=True)
return None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_icon(self) -> bytes | None:
"""Get the primary app icon, decoded from crushed PNG format."""
icon_info = self.get_icon_info()
Expand Down Expand Up @@ -173,7 +173,7 @@ def icon_priority(path: Path) -> tuple[int, int]:
logger.warning(f"No icon files found for CFBundleIconFiles: {icon_info.primary_icon_files}")
return None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_icon_info(self) -> AppIconInfo:
"""Extract icon information from Info.plist."""
plist = self.get_plist()
Expand Down Expand Up @@ -201,7 +201,7 @@ def get_icon_info(self) -> AppIconInfo:

return AppIconInfo(primary_icon_name, primary_icon_files, alternate_icon_names)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def generate_ipa(self, output_path: Path):
"""Generate an IPA file

Expand Down Expand Up @@ -254,7 +254,7 @@ def generate_ipa(self, output_path: Path):
except FileNotFoundError:
raise RuntimeError("zip command not found. This tool is required for IPA generation.")

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_provisioning_profile(self) -> dict[str, Any] | None:
if self._provisioning_profile is not None:
return self._provisioning_profile
Expand All @@ -278,7 +278,7 @@ def get_provisioning_profile(self) -> dict[str, Any] | None:
logger.debug(f"No embedded.mobileprovision found at {mobileprovision_path}")
return None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_binary_path(self) -> Path | None:
app_bundle_path = self.get_app_bundle_path()
plist = self.get_plist()
Expand All @@ -288,7 +288,7 @@ def get_binary_path(self) -> Path | None:

return app_bundle_path.resolve(executable_name)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_bundle_path(self) -> SafeDirectory:
"""Get the path to the .app bundle."""
if self._app_bundle_path is not None:
Expand All @@ -302,12 +302,12 @@ def get_app_bundle_path(self) -> SafeDirectory:

raise FileNotFoundError(f"No .app bundle found in {self._extract_dir}")

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_main_binary_uuid(self) -> str | None:
main_binary_path = self._get_main_binary_path()
return self._extract_binary_uuid(main_binary_path)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_all_binary_paths(self) -> List[BinaryInfo]:
"""Find all binaries in the app bundle and their corresponding dSYM files.

Expand Down Expand Up @@ -366,7 +366,7 @@ def get_all_binary_paths(self) -> List[BinaryInfo]:

return binaries

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_asset_catalog_details(self, relative_path: Path) -> List[AssetCatalogElement]:
"""Get the details of an asset catalog file (Assets.car) by returning the
parsed JSON from ParsedAssets."""
Expand Down Expand Up @@ -580,7 +580,7 @@ def _extract_binary_uuid(self, binary_path: Path) -> str | None:
logger.exception(f"Failed to extract UUID from binary {binary_path}")
return None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def _find_dsym_files(self) -> dict[str, DsymInfo]:
"""Find all dSYM bundles and map them by binary UUID.

Expand Down
4 changes: 2 additions & 2 deletions src/launchpad/artifacts/artifact_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from zipfile import BadZipFile, ZipFile

import sentry_sdk
import sentry_sdk.traces

from .android.aab import AAB
from .android.apk import APK
Expand All @@ -20,7 +20,7 @@ class ArtifactFactory:
"""Factory for creating artifacts from paths."""

@staticmethod
@sentry_sdk.trace
@sentry_sdk.traces.trace
def from_path(path: Path) -> Artifact:
"""Create appropriate Artifact from file path.

Expand Down
Loading
Loading