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
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ actions:
head:
- - meta
- name: "google-site-verification"
content: "hQCR5w2tmeuOvYIYXsOYU3u4kLNwT86lnqltANYlRQ0"
content: "hQCR5w2tmeuOvYIYXsOYU3u4kLNwT86lnqltANYlRQ0" # pragma: allowlist secret
- - meta
- name: "msvalidate.01"
content: "97DC185FE0A2F5B123861F0790FDFB26"
content: "97DC185FE0A2F5B123861F0790FDFB26" # pragma: allowlist secret
- - meta
- name: "yandex-verification"
content: "9b105f7c58cbc920"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why pragma: allowlist secret is here?

As I see, we already exclude this file from detect-secrets pre-commit hook check. Is some other linter checks fails on this lines?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even with exclusion in .pre-commit-config.yaml, still seeing this without pragmas

Detect secrets...........................................................Failed
- hook id: detect-secrets
- exit code: 1

ERROR: Potential secrets about to be committed to git repo!

Secret Type: Base64 High Entropy String
Location:    docs\README.md:17

Secret Type: Hex High Entropy String
Location:    docs\README.md:20

Secret Type: Hex High Entropy String
Location:    docs\README.md:23

Possible mitigations:
  - For information about putting your secrets in a safer place, please ask in
    #security
  - Mark false positives with an inline `pragma: allowlist secret` comment

If a secret has already been committed, visit
https://help.github.com/articles/removing-sensitive-data-from-a-repository

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats strange...
I checked on my fork and anything seems to be fine:

(taskiq)  git pull
Already up to date.
(taskiq)  git st
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
(taskiq)  pre-commit install
pre-commit installed at .git/hooks/pre-commit
(taskiq)  pre-commit run detect-secrets --all-files
Detect secrets...........................................................Passed

Are you sure that you run poetry install --all-extras && pre-commit install after rebase? I don't really know that else can be a problem

Copy link
Contributor

@danfimov danfimov Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case - it's not a blocker to merge this MR. I will deal with it later if we don't find the root cause of this strange pre-commit hook behaviour.

content: "9b105f7c58cbc920" # pragma: allowlist secret
highlights:
- features:
- title: Production ready
Expand Down
204 changes: 201 additions & 3 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ pycron = "^3.0.0"
taskiq_dependencies = ">=1.3.1,<2"
anyio = ">=4"
packaging = ">=19"
# For opentelemetry instrumentation
opentelemetry-api = { version = "^1.38.0", optional = true }
opentelemetry-instrumentation = { version = "^0.59b0", optional = true}
opentelemetry-semantic-conventions = { version = "^0.59b0", optional = true}
# For prometheus metrics
prometheus_client = { version = "^0", optional = true }
# For ZMQBroker
Expand Down Expand Up @@ -69,10 +73,12 @@ pytest-mock = "^3.11.1"
tzlocal = "^5.0.1"
types-tzlocal = "^5.0.1.1"
types-pytz = "^2023.3.1.1"
opentelemetry-test-utils = "^0.59b0"

[tool.poetry.extras]
zmq = ["pyzmq"]
uv = ["uvloop"]
opentelemetry = ["opentelemetry-api", "opentelemetry-instrumentation", "opentelemetry-semantic-conventions"]
metrics = ["prometheus_client"]
reload = ["watchdog", "gitignore-parser"]
orjson = ["orjson"]
Expand All @@ -86,6 +92,9 @@ taskiq = "taskiq.__main__:main"
worker = "taskiq.cli.worker.cmd:WorkerCMD"
scheduler = "taskiq.cli.scheduler.cmd:SchedulerCMD"

[tool.poetry.plugins.opentelemetry_instrumentor]
taskiq = "taskiq.instrumentation:TaskiqInstrumentor"

[tool.mypy]
strict = true
ignore_missing_imports = true
Expand Down
138 changes: 138 additions & 0 deletions taskiq/instrumentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""
Instrument `taskiq`_ to trace Taskiq applications.
.. _taskiq: https://pypi.org/project/taskiq/
Usage
-----
* Run instrumented task
.. code:: python
import asyncio
from taskiq import InMemoryBroker, TaskiqEvents, TaskiqState
from taskiq.instrumentation import TaskiqInstrumentor
broker = InMemoryBroker()
@broker.on_event(TaskiqEvents.WORKER_STARTUP)
async def startup(state: TaskiqState) -> None:
TaskiqInstrumentor().instrument()
@broker.task
async def add(x, y):
return x + y
async def main():
await broker.startup()
await my_task.kiq(1, 2)
await broker.shutdown()
if __name__ == "__main__":
asyncio.run(main())
API
---
"""

from __future__ import annotations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like everything works fine without this import. Maybe we can remove it?

As far as I can see, there are no other places in the taskiq repo where __future__ is used.


import logging
from typing import TYPE_CHECKING, Any, Callable, Collection, Optional
from weakref import WeakSet as _WeakSet

try:
import opentelemetry # noqa: F401
except ImportError as exc:
raise ImportError(
"Cannot instrument. Please install 'taskiq[opentelemetry]'.",
) from exc


from opentelemetry.instrumentation.instrumentor import ( # type: ignore[attr-defined]
BaseInstrumentor,
)
from opentelemetry.instrumentation.utils import unwrap
from opentelemetry.metrics import MeterProvider
from opentelemetry.trace import TracerProvider
from wrapt import wrap_function_wrapper

from taskiq import AsyncBroker
from taskiq.middlewares.opentelemetry_middleware import OpenTelemetryMiddleware

if TYPE_CHECKING:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have an empty if statement here?

pass

logger = logging.getLogger("taskiq.opentelemetry")


class TaskiqInstrumentor(BaseInstrumentor):
"""OpenTelemetry instrumentor for Taskiq."""

_instrumented_brokers: _WeakSet[AsyncBroker] = _WeakSet()

def __init__(self) -> None:
super().__init__()
self._middleware = None

def instrument_broker(
self,
broker: AsyncBroker,
tracer_provider: Optional[TracerProvider] = None,
meter_provider: Optional[MeterProvider] = None,
) -> None:
"""Instrument broker."""
if not hasattr(broker, "_is_instrumented_by_opentelemetry"):
broker._is_instrumented_by_opentelemetry = False # type: ignore[attr-defined] # noqa: SLF001

if not getattr(broker, "is_instrumented_by_opentelemetry", False):
broker.middlewares.insert(
0,
OpenTelemetryMiddleware(
tracer_provider=tracer_provider,
meter_provider=meter_provider,
),
)
broker._is_instrumented_by_opentelemetry = True # type: ignore[attr-defined] # noqa: SLF001
if broker not in self._instrumented_brokers:
self._instrumented_brokers.add(broker)
else:
logger.warning(
"Attempting to instrument taskiq broker while already instrumented",
)

def uninstrument_broker(self, broker: AsyncBroker) -> None:
"""Uninstrument broker."""
broker.middlewares = [
middleware
for middleware in broker.middlewares
if not isinstance(middleware, OpenTelemetryMiddleware)
]
broker._is_instrumented_by_opentelemetry = False # type: ignore[attr-defined] # noqa: SLF001
self._instrumented_brokers.discard(broker)

def instrumentation_dependencies(self) -> Collection[str]:
"""This function tells which library this instrumentor instruments."""
return ("taskiq >= 0.0.1",)

def _instrument(self, **kwargs: Any) -> None:
def broker_init(
init: Callable[[Any], Any],
broker: AsyncBroker,
args: Any,
kwargs: Any,
) -> None:
result = init(*args, **kwargs)
self.instrument_broker(broker)
return result

wrap_function_wrapper("taskiq", "AsyncBroker.__init__", broker_init)

def _uninstrument(self, **kwargs: Any) -> None:
instances_to_uninstrument = list(self._instrumented_brokers)
for broker in instances_to_uninstrument:
self.uninstrument_broker(broker)
self._instrumented_brokers.clear()
unwrap(AsyncBroker, "__init__")
Loading