Skip to content

Commit 5501d82

Browse files
committed
refactor: change file names
nit nit
1 parent dd02ebd commit 5501d82

13 files changed

Lines changed: 205 additions & 78 deletions

File tree

.pre-commit-config.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v5.0.0
4+
hooks:
5+
- id: check-merge-conflict
6+
- id: end-of-file-fixer
7+
- id: trailing-whitespace
8+
- id: check-yaml
9+
- id: check-toml
10+
11+
- repo: local
12+
hooks:
13+
- id: ruff
14+
name: ruff (tox)
15+
entry: tox -e ruff
16+
language: system
17+
pass_filenames: false
18+
19+
- id: pylint
20+
name: pylint (tox)
21+
entry: tox -e pylint
22+
language: system
23+
pass_filenames: false

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ django-o11y/
3737
│ ├── conftest.py # Shared fixtures, make_config() helper
3838
│ └── Dockerfile # Multi-stage uv build for dev containers
3939
40-
├── docker-compose.dev.yml # Local dev stack (Django + Celery + Redis + task generator)
40+
├── docker-compose.yml # Local dev stack (Django + Celery + Redis + task generator)
4141
├── Makefile # Shortcuts: make dev, make o11y-stack, etc.
4242
└── docs/ # Usage and configuration guides
4343
```
@@ -113,6 +113,7 @@ make dev
113113

114114
Before opening a PR:
115115

116+
0. Run hooks with prek: `prek run --all-files` (config is in `.pre-commit-config.yaml`)
116117
1. Tests pass: `tox -e py312-django52`
117118
2. Lint clean: `tox -e ruff && tox -e pylint`
118119
3. Integration tests pass if you changed signal handlers or Celery setup: `tox -e integration`

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ setup:
44
uv sync --all-extras
55

66
dev:
7-
docker compose -f docker-compose.dev.yml up --build --watch
7+
docker compose up --build --watch
88

99
dev-stop:
10-
docker compose -f docker-compose.dev.yml stop
10+
docker compose stop
1111

1212
dev-logs:
13-
docker compose -f docker-compose.dev.yml logs -f
13+
docker compose logs -f
1414

1515
o11y-stack:
1616
DJANGO_SETTINGS_MODULE=tests.config.settings.local CELERY_BROKER_URL=$${CELERY_BROKER_URL:-redis://localhost:6379/0} DJANGO_O11Y_LOGGING_FILE_ENABLED=false DJANGO_O11Y_DEV_DB_NAME=$$PWD/dev_db.sqlite3 DJANGO_O11Y_STACK_LOG_DIR=$${DJANGO_O11Y_DEV_TMP_DIR:-/tmp/django-o11y-$${COMPOSE_PROJECT_NAME:-django-o11y}} python manage.py o11y stack start
Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,16 @@ services:
3131
build:
3232
context: .
3333
dockerfile: tests/Dockerfile
34-
entrypoint: >
35-
sh -c "mkdir -p /tmp/django-o11y/prometheus-multiproc/django &&
36-
exec python manage.py runserver 0.0.0.0:8000"
34+
entrypoint: python manage.py runserver 0.0.0.0:8000
3735
environment:
3836
DJANGO_SETTINGS_MODULE: tests.config.settings.local
3937
OTEL_SERVICE_NAME: django-app
40-
PROMETHEUS_MULTIPROC_DIR: /tmp/django-o11y/prometheus-multiproc/django
41-
DJANGO_O11Y_LOGGING_FILE_ENABLED: "true"
4238
DJANGO_O11Y_LOGGING_FILE_PATH: /tmp/django-o11y/django.log
43-
ports:
44-
- "8000:8000"
4539
volumes:
46-
- ${DJANGO_O11Y_DEV_TMP_DIR:-/tmp/django-o11y-${COMPOSE_PROJECT_NAME:-django-o11y}}:/tmp/django-o11y
40+
- ${DJANGO_O11Y_STACK_LOG_MOUNT}:/tmp/django-o11y
4741
- sqlite-data:/data
42+
ports:
43+
- "8000:8000"
4844
extra_hosts:
4945
- "host.docker.internal:host-gateway"
5046
develop:
@@ -67,21 +63,16 @@ services:
6763
build:
6864
context: .
6965
dockerfile: tests/Dockerfile
70-
entrypoint: >
71-
sh -c "mkdir -p /tmp/django-o11y/prometheus-multiproc/celery &&
72-
exec celery -A tests.celery_app worker -l info"
66+
entrypoint: celery -A tests.celery_app worker
7367
environment:
7468
DJANGO_SETTINGS_MODULE: tests.config.settings.local
7569
OTEL_SERVICE_NAME: celery-worker
76-
DJANGO_O11Y_LOGGING_FILE_ENABLED: "true"
7770
DJANGO_O11Y_LOGGING_FILE_PATH: /tmp/django-o11y/celery.log
78-
DJANGO_O11Y_CELERY_METRICS_PORT: "8009"
79-
PROMETHEUS_MULTIPROC_DIR: /tmp/django-o11y/prometheus-multiproc/celery
80-
ports:
81-
- "8009:8009"
8271
volumes:
83-
- ${DJANGO_O11Y_DEV_TMP_DIR:-/tmp/django-o11y-${COMPOSE_PROJECT_NAME:-django-o11y}}:/tmp/django-o11y
72+
- ${DJANGO_O11Y_STACK_LOG_MOUNT}:/tmp/django-o11y
8473
- sqlite-data:/data
74+
ports:
75+
- "8009:8009"
8576
extra_hosts:
8677
- "host.docker.internal:host-gateway"
8778
develop:

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ packages = ["src/django_o11y"]
77

88
[project]
99
name = "django-o11y"
10-
version = "0.5.4"
10+
version = "0.5.5"
1111
description = "Comprehensive OpenTelemetry observability for Django with traces, logs, metrics, and profiling"
1212
readme = "README.md"
1313
requires-python = ">=3.12"
@@ -103,6 +103,7 @@ dev = [
103103
"mypy>=1.19.1",
104104
"pylint>=4.0.5",
105105
"pylint-django>=2.7.0",
106+
"prek>=0.3.4",
106107
"pytest>=9.0.2",
107108
"pytest-cov>=7.0.0",
108109
"pytest-django>=4.12.0",

src/django_o11y/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def ready(self):
4545
return
4646
self._o11y_ready = True
4747

48-
self._configure_tracing(config)
4948
self._configure_logging(config)
49+
self._configure_tracing(config)
5050
self._configure_metrics(config)
5151
self._configure_profiling(config)
5252

src/django_o11y/logging/celery.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Celery worker logging bootstrap helpers."""
2+
3+
import os
4+
from typing import Any
5+
6+
from django_o11y.logging.utils import get_logger
7+
8+
logger = get_logger()
9+
10+
11+
def setup_celery_logging(app: Any) -> None:
12+
"""Configure Celery worker logging defaults and structlog worker step."""
13+
# Keep Django/structlog logging ownership in workers.
14+
app.conf.worker_hijack_root_logger = False
15+
app.conf.worker_redirect_stdouts = False
16+
17+
from django_structlog.celery.steps import DjangoStructLogInitStep
18+
19+
app.steps["worker"].add(DjangoStructLogInitStep)
20+
logger.info(
21+
"celery_worker_step_registered",
22+
step="DjangoStructLogInitStep",
23+
pid=os.getpid(),
24+
)

src/django_o11y/management/commands/stack/docker-compose.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ services:
1111

1212
loki:
1313
image: grafana/loki:3.6.6
14-
command: -config.file=/etc/loki/local-config.yaml
14+
command:
15+
- -config.file=/etc/loki/local-config.yaml
16+
- -distributor.max-line-size=2MB
1517
ports:
1618
- "3100:3100"
1719
volumes:

src/django_o11y/tracing/setup.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Tracing setup and Celery tracing signal integration."""
22

3-
import logging
43
import os
54
import socket
65
from typing import Any
@@ -16,8 +15,10 @@
1615
from opentelemetry.sdk.trace import TracerProvider
1716
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
1817
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
18+
from opentelemetry.trace import ProxyTracerProvider
1919

2020
from django_o11y.config.setup import get_o11y_config
21+
from django_o11y.logging.celery import setup_celery_logging
2122
from django_o11y.logging.utils import get_logger
2223
from django_o11y.tracing.fork import register_post_fork_handler
2324
from django_o11y.tracing.instrumentation import setup_instrumentation
@@ -28,18 +29,30 @@
2829
from django_o11y.utils.process import get_process_identity
2930

3031
logger = get_logger()
31-
provider_logger = logging.getLogger("django_o11y.tracing")
3232

3333
# Track instrumentation per-process to remain fork-safe.
3434
_instrumented_pid: int | None = None
3535
_tracing_initialized_pid: int | None = None
3636

3737

38-
def setup_tracing(config: dict[str, Any]) -> TracerProvider:
38+
def setup_tracing(config: dict[str, Any]) -> Any:
3939
"""Set up OpenTelemetry tracing provider and span processors."""
40+
global _tracing_initialized_pid
41+
4042
service_name = config["SERVICE_NAME"]
4143
tracing_config = config["TRACING"]
4244

45+
existing_provider = trace.get_tracer_provider()
46+
if not isinstance(existing_provider, ProxyTracerProvider):
47+
_tracing_initialized_pid = os.getpid()
48+
logger.debug(
49+
"Tracing provider already configured by %s.%s; skipping override [%s]",
50+
existing_provider.__class__.__module__,
51+
existing_provider.__class__.__name__,
52+
get_process_identity(),
53+
)
54+
return existing_provider
55+
4356
instance_id = config.get("SERVICE_INSTANCE_ID") or (
4457
f"{os.getenv('HOSTNAME', socket.gethostname())}:{os.getpid()}"
4558
)
@@ -76,45 +89,49 @@ def setup_tracing(config: dict[str, Any]) -> TracerProvider:
7689
provider.add_span_processor(BatchSpanProcessor(console_exporter))
7790

7891
trace.set_tracer_provider(provider)
79-
global _tracing_initialized_pid
8092
_tracing_initialized_pid = os.getpid()
81-
provider_logger.info(
93+
logger.info(
8294
"Tracing configured for %s, sending to %s (%.0f%% sampling) [%s]",
8395
service_name,
8496
tracing_config["OTLP_ENDPOINT"],
8597
tracing_config["SAMPLE_RATE"] * 100,
8698
get_process_identity(),
8799
)
88100

89-
profiling_config = config["PROFILING"]
90-
if profiling_config.get("ENABLED"):
91-
is_prefork_parent = (
92-
is_celery_prefork_pool() and not is_celery_fork_pool_worker()
93-
)
94-
if is_prefork_parent:
95-
provider_logger.warning(
96-
"Skipping Pyroscope profile-trace correlation in Celery prefork "
97-
"parent process [%s]. Correlation is initialized in worker "
98-
"child processes post-fork.",
99-
get_process_identity(),
100-
)
101-
return provider
101+
_setup_pyroscope_correlation(provider, config)
102102

103-
try:
104-
from pyroscope.otel import PyroscopeSpanProcessor
103+
return provider
105104

106-
provider.add_span_processor(PyroscopeSpanProcessor())
107-
provider_logger.info(
108-
"Pyroscope span processor added for profile-to-trace correlation [%s]",
109-
get_process_identity(),
110-
)
111-
except ImportError:
112-
provider_logger.debug(
113-
"django_o11y: pyroscope-otel not installed, skipping profile-trace "
114-
"correlation. Install with: pip install django-o11y[profiling]"
115-
)
116105

117-
return provider
106+
def _setup_pyroscope_correlation(
107+
provider: TracerProvider, config: dict[str, Any]
108+
) -> None:
109+
"""Attach Pyroscope span correlation when profiling is enabled."""
110+
if not config["PROFILING"].get("ENABLED"):
111+
return
112+
113+
if is_celery_prefork_pool() and not is_celery_fork_pool_worker():
114+
logger.warning(
115+
"Skipping Pyroscope profile-trace correlation in Celery prefork "
116+
"parent process [%s]. Correlation is initialized in worker "
117+
"child processes post-fork.",
118+
get_process_identity(),
119+
)
120+
return
121+
122+
try:
123+
from pyroscope.otel import PyroscopeSpanProcessor
124+
125+
provider.add_span_processor(PyroscopeSpanProcessor())
126+
logger.info(
127+
"Pyroscope span processor added for profile-to-trace correlation [%s]",
128+
get_process_identity(),
129+
)
130+
except ImportError:
131+
logger.debug(
132+
"django_o11y: pyroscope-otel not installed, skipping profile-trace "
133+
"correlation. Install with: pip install django-o11y[profiling]"
134+
)
118135

119136

120137
def setup_tracing_for_django(config: dict[str, Any]) -> None:
@@ -151,7 +168,7 @@ def setup_tracing_for_django(config: dict[str, Any]) -> None:
151168

152169

153170
def setup_celery_o11y(app: Any, config: dict[str, Any] | None = None) -> None:
154-
"""Set up tracing and worker defaults for Celery tasks."""
171+
"""Set up Celery worker logging and tracing bootstrap."""
155172
global _instrumented_pid
156173

157174
if _instrumented_pid == os.getpid():
@@ -164,13 +181,7 @@ def setup_celery_o11y(app: Any, config: dict[str, Any] | None = None) -> None:
164181
if not celery_config.get("ENABLED", False):
165182
return
166183

167-
# Keep Django/structlog logging ownership in workers.
168-
app.conf.worker_hijack_root_logger = False
169-
app.conf.worker_redirect_stdouts = False
170-
171-
from django_structlog.celery.steps import DjangoStructLogInitStep
172-
173-
app.steps["worker"].add(DjangoStructLogInitStep)
184+
setup_celery_logging(app)
174185

175186
if config.get("TRACING", {}).get("ENABLED") and celery_config.get(
176187
"TRACING_ENABLED", True
@@ -201,7 +212,7 @@ def setup_worker_metrics(celery_config: dict[str, Any]) -> None:
201212

202213
multiproc_dir = os.environ.get("PROMETHEUS_MULTIPROC_DIR")
203214
if not multiproc_dir:
204-
provider_logger.warning(
215+
logger.warning(
205216
"PROMETHEUS_MULTIPROC_DIR is not set; "
206217
"Celery worker metrics server will not be started."
207218
)
@@ -217,7 +228,7 @@ def setup_worker_metrics(celery_config: dict[str, Any]) -> None:
217228
registry = CollectorRegistry()
218229
MultiProcessCollector(registry)
219230
start_http_server(port, registry=registry)
220-
provider_logger.info(
231+
logger.info(
221232
"Celery worker metrics server started on port %d (multiproc dir: %s)",
222233
port,
223234
multiproc_dir,
@@ -244,7 +255,7 @@ def _configure_celery_metrics_events(config: dict[str, Any]) -> None:
244255
app.conf.worker_send_task_events = True
245256
app.conf.task_send_sent_event = True
246257
except Exception: # pragma: no cover
247-
provider_logger.debug(
258+
logger.debug(
248259
"Failed to enable Celery task events in Django/worker process",
249260
exc_info=True,
250261
)

tests/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ RUN --mount=type=cache,target=/root/.cache \
2525
FROM python:3.12-slim
2626

2727
ENV PATH=/app/bin:$PATH \
28-
PYTHONUNBUFFERED=1
28+
PYTHONUNBUFFERED=1 \
29+
DJANGO_O11Y_METRICS_MULTIPROC_BASE_DIR=/tmp/django-wtf-prom-multiproc \
30+
PROMETHEUS_MULTIPROC_DIR=/tmp/django-wtf-prom-multiproc
2931

3032
COPY --from=build /app /app
3133
COPY --from=build /src /app

0 commit comments

Comments
 (0)