Skip to content

Commit 1722546

Browse files
authored
Add instrument_celery method (#322)
1 parent 0758b24 commit 1722546

File tree

12 files changed

+200
-8
lines changed

12 files changed

+200
-8
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ jobs:
6363
echo "203.0.113.0 logfire.dev" | sudo tee -a /etc/hosts
6464
echo "203.0.113.0 logfire-api.pydantic.dev" | sudo tee -a /etc/hosts
6565
echo "203.0.113.0 logfire.pydantic.dev" | sudo tee -a /etc/hosts
66+
- name: Setup services
67+
run: docker-compose up -d
6668
- name: set up python
6769
uses: actions/setup-python@v5
6870
with:

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ lint:
3030
test:
3131
rye run coverage run -m pytest
3232

33+
.PHONY: test-integration # Run the integration tests
34+
test-integration:
35+
rye run coverage run -m pytest -m 'integration'
36+
3337
.PHONY: generate-stubs # Generate stubs for logfire-api
3438
generate-stubs:
3539
rye run generate-stubs

compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
redis:
3+
image: redis:latest
4+
container_name: redis
5+
ports:
6+
- "6379:6379"

docs/integrations/celery.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Celery
22

3-
The [OpenTelemetry Instrumentation Celery][opentelemetry-celery] package can be used to instrument [Celery][celery].
3+
The [`logfire.instrument_celery()`][logfire.Logfire.instrument_celery] method will create a span for every task
4+
executed by your Celery workers.
45

56
## Installation
67

@@ -27,14 +28,13 @@ Below we have a minimal example using Celery. You can run it with `celery -A tas
2728
import logfire
2829
from celery import Celery
2930
from celery.signals import worker_process_init
30-
from opentelemetry.instrumentation.celery import CeleryInstrumentor
3131

3232

3333
logfire.configure()
3434

3535
@worker_process_init.connect(weak=False)
3636
def init_celery_tracing(*args, **kwargs):
37-
CeleryInstrumentor().instrument()
37+
logfire.instrument_celery()
3838

3939
app = Celery("tasks", broker="pyamqp://localhost//") # (1)!
4040

@@ -47,7 +47,8 @@ add.delay(42, 50)
4747

4848
1. Install `pyamqp` with `pip install pyamqp`.
4949

50-
You can read more about the Celery OpenTelemetry package [here][opentelemetry-celery].
50+
The keyword arguments of [`logfire.instrument_celery()`][logfire.Logfire.instrument_celery] are passed to the
51+
[`CeleryInstrumentor().instrument()`][opentelemetry.instrumentation.celery.CeleryInstrumentor] method.
5152

5253
[celery]: https://docs.celeryq.dev/en/stable/
5354
[opentelemetry-celery]: https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/celery/celery.html

logfire/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
2929
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
3030
instrument_httpx = DEFAULT_LOGFIRE_INSTANCE.instrument_httpx
31+
instrument_celery = DEFAULT_LOGFIRE_INSTANCE.instrument_celery
3132
instrument_requests = DEFAULT_LOGFIRE_INSTANCE.instrument_requests
3233
instrument_psycopg = DEFAULT_LOGFIRE_INSTANCE.instrument_psycopg
3334
instrument_django = DEFAULT_LOGFIRE_INSTANCE.instrument_django

logfire/_internal/formatter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _fstring_chunks(
203203
if isinstance(node_value, ast.Constant):
204204
# These are the parts of the f-string not enclosed by `{}`, e.g. 'foo ' in f'foo {bar}'
205205
value = node_value.value
206-
assert type(value) is str # noqa
206+
assert type(value) is str
207207
result.append({'v': value, 't': 'lit'})
208208
new_template += value
209209
else:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from opentelemetry.instrumentation.celery import CeleryInstrumentor
6+
7+
if TYPE_CHECKING:
8+
from typing_extensions import TypedDict, Unpack
9+
10+
class CeleryInstrumentKwargs(TypedDict, total=False):
11+
skip_dep_check: bool
12+
13+
14+
def instrument_celery(**kwargs: Unpack[CeleryInstrumentKwargs]) -> None:
15+
"""Instrument the `celery` module so that spans are automatically created for each task.
16+
17+
See the `Logfire.instrument_celery` method for details.
18+
"""
19+
return CeleryInstrumentor().instrument(**kwargs) # type: ignore[reportUnknownMemberType]

logfire/_internal/main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
from starlette.websockets import WebSocket
7676
from typing_extensions import Unpack
7777

78+
from .integrations.celery import CeleryInstrumentKwargs
7879
from .integrations.flask import FlaskInstrumentKwargs
7980
from .integrations.psycopg import PsycopgInstrumentKwargs
8081
from .integrations.pymongo import PymongoInstrumentKwargs
@@ -1049,6 +1050,18 @@ def instrument_httpx(self, **kwargs: Any):
10491050
self._warn_if_not_initialized_for_instrumentation()
10501051
return instrument_httpx(**kwargs)
10511052

1053+
def instrument_celery(self, **kwargs: Unpack[CeleryInstrumentKwargs]) -> None:
1054+
"""Instrument `celery` so that spans are automatically created for each task.
1055+
1056+
Uses the
1057+
[OpenTelemetry Celery Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/celery/celery.html)
1058+
library.
1059+
"""
1060+
from .integrations.celery import instrument_celery
1061+
1062+
self._warn_if_not_initialized_for_instrumentation()
1063+
return instrument_celery(**kwargs)
1064+
10521065
def instrument_django(
10531066
self,
10541067
is_sql_commentor_enabled: bool | None = None,

pyproject.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ dev-dependencies = [
113113
"opentelemetry-instrumentation-psycopg2",
114114
"opentelemetry-instrumentation-redis",
115115
"opentelemetry-instrumentation-pymongo",
116+
"opentelemetry-instrumentation-celery",
116117
"eval-type-backport",
117118
"requests-mock",
118119
"inline-snapshot",
@@ -133,6 +134,7 @@ dev-dependencies = [
133134
"anthropic>=0.27.0",
134135
"sqlmodel",
135136
"mypy>=1.10.0",
137+
"celery>=5.4.0",
136138
]
137139

138140
[tool.rye.scripts]
@@ -198,11 +200,20 @@ quote-style = "single"
198200
typeCheckingMode = "strict"
199201
reportUnnecessaryTypeIgnoreComment = true
200202
reportMissingTypeStubs = false
201-
exclude = ["docs/**/*.py", "site/**/*.py", ".venv", "venv*", "ignoreme", "out", "logfire-api"]
203+
exclude = [
204+
"docs/**/*.py",
205+
"site/**/*.py",
206+
".venv",
207+
"venv*",
208+
"ignoreme",
209+
"out",
210+
"logfire-api",
211+
]
202212
venvPath = ".venv"
203213

204214
[tool.pytest.ini_options]
205215
xfail_strict = true
216+
markers = ['integration: mark a test as an integration test']
206217
filterwarnings = [
207218
"error",
208219
# fastapi uses deprecated pydantic functions

requirements-dev.lock

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
# features: []
77
# all-features: false
88
# with-sources: false
9-
# generate-hashes: false
109

1110
-e file:.
1211
aiohttp==3.9.5
1312
aiosignal==1.3.1
1413
# via aiohttp
14+
amqp==5.2.0
15+
# via kombu
1516
annotated-types==0.7.0
1617
# via pydantic
1718
anthropic==0.31.2
@@ -31,10 +32,13 @@ attrs==23.2.0
3132
# via aiohttp
3233
babel==2.15.0
3334
# via mkdocs-material
35+
billiard==4.2.0
36+
# via celery
3437
black==24.4.2
3538
# via inline-snapshot
3639
blinker==1.8.2
3740
# via flask
41+
celery==5.4.0
3842
certifi==2024.7.4
3943
# via httpcore
4044
# via httpx
@@ -45,12 +49,22 @@ charset-normalizer==3.3.2
4549
# via requests
4650
click==8.1.7
4751
# via black
52+
# via celery
53+
# via click-didyoumean
54+
# via click-plugins
55+
# via click-repl
4856
# via flask
4957
# via inline-snapshot
5058
# via mkdocs
5159
# via mkdocstrings
5260
# via typer
5361
# via uvicorn
62+
click-didyoumean==0.3.1
63+
# via celery
64+
click-plugins==1.1.1
65+
# via celery
66+
click-repl==0.3.0
67+
# via celery
5468
cloudpickle==3.0.0
5569
colorama==0.4.6
5670
# via griffe
@@ -130,6 +144,8 @@ jinja2==3.1.4
130144
# via mkdocstrings
131145
jiter==0.5.0
132146
# via anthropic
147+
kombu==5.3.7
148+
# via celery
133149
loguru==0.7.2
134150
markdown==3.6
135151
# via mkdocs
@@ -184,6 +200,7 @@ opentelemetry-api==1.25.0
184200
# via opentelemetry-instrumentation-aiohttp-client
185201
# via opentelemetry-instrumentation-asgi
186202
# via opentelemetry-instrumentation-asyncpg
203+
# via opentelemetry-instrumentation-celery
187204
# via opentelemetry-instrumentation-dbapi
188205
# via opentelemetry-instrumentation-django
189206
# via opentelemetry-instrumentation-fastapi
@@ -209,6 +226,7 @@ opentelemetry-instrumentation==0.46b0
209226
# via opentelemetry-instrumentation-aiohttp-client
210227
# via opentelemetry-instrumentation-asgi
211228
# via opentelemetry-instrumentation-asyncpg
229+
# via opentelemetry-instrumentation-celery
212230
# via opentelemetry-instrumentation-dbapi
213231
# via opentelemetry-instrumentation-django
214232
# via opentelemetry-instrumentation-fastapi
@@ -228,6 +246,7 @@ opentelemetry-instrumentation-asgi==0.46b0
228246
# via opentelemetry-instrumentation-fastapi
229247
# via opentelemetry-instrumentation-starlette
230248
opentelemetry-instrumentation-asyncpg==0.46b0
249+
opentelemetry-instrumentation-celery==0.46b0
231250
opentelemetry-instrumentation-dbapi==0.46b0
232251
# via opentelemetry-instrumentation-psycopg
233252
# via opentelemetry-instrumentation-psycopg2
@@ -256,6 +275,7 @@ opentelemetry-semantic-conventions==0.46b0
256275
# via opentelemetry-instrumentation-aiohttp-client
257276
# via opentelemetry-instrumentation-asgi
258277
# via opentelemetry-instrumentation-asyncpg
278+
# via opentelemetry-instrumentation-celery
259279
# via opentelemetry-instrumentation-dbapi
260280
# via opentelemetry-instrumentation-django
261281
# via opentelemetry-instrumentation-fastapi
@@ -299,6 +319,8 @@ platformdirs==4.2.2
299319
pluggy==1.5.0
300320
# via pytest
301321
pre-commit==3.7.1
322+
prompt-toolkit==3.0.47
323+
# via click-repl
302324
protobuf==4.25.3
303325
# via googleapis-common-protos
304326
# via logfire
@@ -330,6 +352,7 @@ pytest==8.3.1
330352
pytest-django==4.8.0
331353
pytest-pretty==1.2.0
332354
python-dateutil==2.9.0.post0
355+
# via celery
333356
# via ghp-import
334357
# via pandas
335358
python-dotenv==1.0.1
@@ -409,19 +432,26 @@ typing-extensions==4.12.2
409432
# via sqlalchemy
410433
# via typer
411434
tzdata==2024.1
435+
# via celery
412436
# via pandas
413437
urllib3==2.2.2
414438
# via requests
415439
uvicorn==0.30.3
416440
# via fastapi
417441
uvloop==0.19.0
418442
# via uvicorn
443+
vine==5.1.0
444+
# via amqp
445+
# via celery
446+
# via kombu
419447
virtualenv==20.26.3
420448
# via pre-commit
421449
watchdog==4.0.1
422450
# via mkdocs
423451
watchfiles==0.22.0
424452
# via uvicorn
453+
wcwidth==0.2.13
454+
# via prompt-toolkit
425455
websockets==12.0
426456
# via uvicorn
427457
werkzeug==3.0.3

0 commit comments

Comments
 (0)