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
63 changes: 36 additions & 27 deletions devservices/commands/up.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from sentry_sdk import capture_exception
from sentry_sdk import logger as sentry_logger
from sentry_sdk import set_context
from sentry_sdk import start_span
from sentry_sdk.traces import start_span

from devservices.constants import CONFIG_FILE_NAME
from devservices.constants import DEPENDENCY_CONFIG_VERSION
Expand Down Expand Up @@ -133,7 +133,8 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
) as status:
local_runtime_dependency_names = set()
with start_span(
op="service.dependencies.check", name="Check local runtime dependencies"
name="Check local runtime dependencies",
attributes={"sentry.op": "service.dependencies.check"},
) as span:
services_with_local_runtime = state.get_services_by_runtime(
ServiceRuntime.LOCAL
Expand All @@ -149,13 +150,14 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
status.warning(
f"Skipping '{service_with_local_runtime}' as it is set to run locally"
)
span.set_data("service_name", service.name)
span.set_data("mode", mode)
span.set_data(
span.set_attribute("service_name", service.name)
span.set_attribute("mode", mode)
span.set_attribute(
"local_runtime_dependency_count", len(local_runtime_dependency_names)
)
span.set_data(
"local_runtime_dependency_names", local_runtime_dependency_names
span.set_attribute(
"local_runtime_dependency_names",
sorted(local_runtime_dependency_names),
)

context_name = f"local_runtime_dependencies.{service.name}"
Expand Down Expand Up @@ -219,11 +221,12 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
)
status.warning(f"Continuing with service '{service.name}'")
with start_span(
op="service.containers.start", name="Start service containers"
name="Start service containers",
attributes={"sentry.op": "service.containers.start"},
) as span:
span.set_data("service_name", service.name)
span.set_data("mode", mode)
span.set_data("exclude_local", exclude_local)
span.set_attribute("service_name", service.name)
span.set_attribute("mode", mode)
span.set_attribute("exclude_local", exclude_local)
try:
bring_up_docker_compose_services(
service,
Expand All @@ -238,10 +241,11 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
status.failure(f"Failed to start {service.name}: {dce.stderr}")
exit(1)
with start_span(
op="service.supervisor.start", name="Start supervisor programs"
name="Start supervisor programs",
attributes={"sentry.op": "service.supervisor.start"},
) as span:
span.set_data("service_name", service.name)
span.set_data("supervisor_programs", supervisor_programs)
span.set_attribute("service_name", service.name)
span.set_attribute("supervisor_programs", supervisor_programs)
try:
bring_up_supervisor_programs(service, supervisor_programs, status)
except SupervisorError as se:
Expand All @@ -257,14 +261,15 @@ def _install_service_dependencies(
service: Service, mode: str, status: Status, offline: bool = False
) -> set[InstalledRemoteDependency]:
with start_span(
op="service.dependencies.install", name="Install dependencies"
name="Install dependencies",
attributes={"sentry.op": "service.dependencies.install"},
) as span:
status.info(
"Using cached dependencies" if offline else "Retrieving dependencies"
)
span.set_data("service_name", service.name)
span.set_data("mode", mode)
span.set_data("offline", offline)
span.set_attribute("service_name", service.name)
span.set_attribute("mode", mode)
span.set_attribute("offline", offline)

update_mode = (
DependencyUpdateMode.OFFLINE if offline else DependencyUpdateMode.FORCE
Expand All @@ -275,7 +280,7 @@ def _install_service_dependencies(
update_mode,
modes=[mode],
)
span.set_data("remote_dependency_count", len(remote_dependencies))
span.set_attribute("remote_dependency_count", len(remote_dependencies))
return remote_dependencies
except DependencyError as de:
capture_exception(de)
Expand All @@ -295,7 +300,8 @@ def _install_service_dependencies(

def _create_devservices_network() -> None:
with start_span(
op="service.network.create", name="Create devservices network"
name="Create devservices network",
attributes={"sentry.op": "service.network.create"},
) as span:
try:
subprocess.run(
Expand All @@ -304,17 +310,19 @@ def _create_devservices_network() -> None:
stderr=subprocess.DEVNULL,
check=True,
)
span.set_data("network_created", True)
span.set_attribute("network_created", True)
except subprocess.CalledProcessError:
# Network already exists, ignore the error
span.set_data("network_created", False)
span.set_attribute("network_created", False)


def _pull_dependency_images(
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
) -> None:
with start_span(op="service.images.pull", name="Pull dependency images") as span:
span.set_data("command", cmd.full_command)
with start_span(
name="Pull dependency images", attributes={"sentry.op": "service.images.pull"}
) as span:
span.set_attribute("command", cmd.full_command)
run_cmd(cmd.full_command, current_env, retries=4)
for dependency in cmd.services:
status.info(f"Pulled image for {dependency}")
Expand All @@ -324,10 +332,11 @@ def _bring_up_dependency(
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
) -> None:
with start_span(
op="service.containers.up", name="Bring up dependency containers"
name="Bring up dependency containers",
attributes={"sentry.op": "service.containers.up"},
) as span:
span.set_data("command", cmd.full_command)
span.set_data("services", cmd.services)
span.set_attribute("command", cmd.full_command)
span.set_attribute("services", cmd.services)
for dependency in cmd.services:
status.info(f"Starting {dependency}")
run_cmd(cmd.full_command, current_env)
Expand Down
51 changes: 33 additions & 18 deletions devservices/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
import os
import platform
from importlib import metadata
from typing import Any

from sentry_sdk import VERSION as SENTRY_SDK_VERSION
from sentry_sdk import capture_exception
from sentry_sdk import flush
from sentry_sdk import init
from sentry_sdk import set_context
from sentry_sdk import set_tag
from sentry_sdk import set_user
from sentry_sdk import start_transaction
from sentry_sdk.integrations.argv import ArgvIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.traces import start_span
from sentry_sdk.types import Event
from sentry_sdk.types import Hint

Expand Down Expand Up @@ -49,6 +51,8 @@
disable_sentry = os.environ.get("DEVSERVICES_DISABLE_SENTRY", default="0") == "1"
logging.basicConfig(level=logging.INFO)
current_version = metadata.version("devservices")
# devservices supports both sentry-sdk 2.x and 3.x
sentry_sdk_major_version = int(SENTRY_SDK_VERSION.split(".")[0])

error_trace_ids = set()

Expand All @@ -64,25 +68,26 @@ def before_send_error(event: Event, hint: Hint) -> Event:
return event


def before_send_transaction(event: Event, hint: Hint) -> Event:
"""Manually sets the status of a transaction.

This function is used as a before_send_transaction callback for Sentry to mark transaction status
as unknown if they don't correspond to errors we care about.
"""
if event["contexts"]["trace"]["trace_id"] not in error_trace_ids:
event["contexts"]["trace"]["status"] = "unknown"
return event


if not disable_sentry:
# sentry-sdk 3.x renamed LoggingIntegration's sentry_logs_level option to level
# and captures Sentry logs by default, which 2.x only does with capture_sentry_logs
if sentry_sdk_major_version >= 3:
logging_integration_options: dict[str, Any] = {"level": logging.DEBUG}
else:
logging_integration_options = {
"sentry_logs_level": logging.DEBUG,
"capture_sentry_logs": True,
}

init(
dsn="https://56470da7302c16e83141f62f88e46449@o1.ingest.us.sentry.io/4507946704961536",
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
trace_lifecycle="stream",
Comment thread
sentrivana marked this conversation as resolved.
Resolved
profile_session_sample_rate=1.0,
profile_lifecycle="trace",
integrations=[
ArgvIntegration(),
LoggingIntegration(sentry_logs_level=logging.DEBUG),
LoggingIntegration(**logging_integration_options),
],
# By default the Sentry SDK loads integrations for all relevant libraries in
# the venv. When running devservices in the seer directory we end up loading
Expand All @@ -91,10 +96,8 @@ def before_send_transaction(event: Event, hint: Hint) -> Event:
# actually using those libraries. By disabling this behaviour we can reduce
# startup time for devservices.
auto_enabling_integrations=False,
enable_logs=True,
environment=sentry_environment,
before_send=before_send_error,
before_send_transaction=before_send_transaction,
release=current_version,
)
username = getpass.getuser()
Expand Down Expand Up @@ -178,8 +181,20 @@ def main() -> None:
logger.setLevel(logging.DEBUG)

if args.command:
with start_transaction(op="command", name=args.command):
args.func(args)
exc: BaseException | None = None
with start_span(name=args.command, attributes={"sentry.op": "command"}) as span:
try:
args.func(args)
except BaseException as e:
exc = e

if span.trace_id in error_trace_ids:
span.status = "error"
else:
span.status = "ok"

if exc is not None:
raise exc
else:
parser.print_help()

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ requires-python = ">=3.11"
dependencies = [
"pyyaml",
"sentry-devenv",
"sentry-sdk",
"sentry-sdk>=2.68.0,<4",
"packaging",
"supervisor>=4.3.0",
]
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading