{"id":34075362,"url":"https://github.com/goduni/unilogging","last_synced_at":"2025-12-14T09:33:55.964Z","repository":{"id":296459909,"uuid":"993487871","full_name":"goduni/unilogging","owner":"goduni","description":"A simple library for working with the context of logs.","archived":false,"fork":false,"pushed_at":"2025-10-17T22:15:16.000Z","size":47,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-18T23:41:54.228Z","etag":null,"topics":["context","logging","python","python-logging","structured-logging"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/goduni.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-30T22:04:54.000Z","updated_at":"2025-10-17T22:13:23.000Z","dependencies_parsed_at":"2025-05-31T07:25:41.163Z","dependency_job_id":"90ee09f8-4023-4b92-a9ed-4568933b934a","html_url":"https://github.com/goduni/unilogging","commit_stats":null,"previous_names":["goduni/unilogging"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/goduni/unilogging","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goduni%2Funilogging","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goduni%2Funilogging/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goduni%2Funilogging/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goduni%2Funilogging/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/goduni","download_url":"https://codeload.github.com/goduni/unilogging/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goduni%2Funilogging/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27724753,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-14T02:00:11.348Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["context","logging","python","python-logging","structured-logging"],"created_at":"2025-12-14T09:33:54.609Z","updated_at":"2025-12-14T09:33:55.950Z","avatar_url":"https://github.com/goduni.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# unilogging\n\nA simple library for working with the context of logs.\n\n[![codecov](https://codecov.io/gh/goduni/unilogging/branch/master/graph/badge.svg)](https://codecov.io/gh/goduni/unilogging)\n[![PyPI version](https://img.shields.io/pypi/v/unilogging.svg)](https://pypi.org/project/unilogging)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/unilogging)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/unilogging)\n![GitHub License](https://img.shields.io/github/license/goduni/unilogging)\n![GitHub Repo stars](https://img.shields.io/github/stars/goduni/unilogging)\n[![Telegram](https://img.shields.io/badge/💬-Telegram-blue)](https://t.me/+TvprI2G1o7FmYzRi)\n\n## Quickstart\n\n```bash\npip install unilogging\n```\n\n## What Problem Does It Solve?\n\nMany modern Python applications use Dependency Injection (DI), but logging has traditionally relied on using a global logger (e.g., `logging.getLogger(__name__)`). While this simplifies the use of logging, it also introduces a number of complications.\n\nIf you're developing a web application, there are situations where you need to understand what happened during a specific request (for example, when an error occurs during that request). To do this, it's common to introduce a concept like `request_id`—a unique identifier assigned to an HTTP request, which allows you to trace all logs related to that request.\n\nThis creates a problem: you now need a way to propagate the request_id throughout your application – especially if your application has multiple architectural layers (e.g., infrastructure, service, presentation). Passing request_id (and other context data like `user_id`) explicitly everywhere is problematic, as it leads to a lot of boilerplate and argument duplication in your code, all just to enable proper logging.\n\n\n\n### How does Unilogging solve this?\n\nUnilogging helps structure the logging process using Dependency Injection (DI).\n\nFor each request to your web application, a separate context is created. You can populate this context with data regardless of the application layer – using DI is all you need.\n\nFor example, consider this scenario:\n\n* You generate a `request_id` in a FastAPI middleware (or any other framework you’re using).\n* You write a log entry indicating that a certain action was performed, including relevant data (depending on your use case).\n\nHere’s an example using Unilogging:\n\n```python\n@app.middleware(\"http\")\nasync def request_id_middleware(request, call_next):\n    logger = await request.state.dishka_container.get(Logger)\n    with logger.begin_scope(request_id=uuid.uuid4()):\n        response = await call_next(request)\n        return response\n\n@app.post(\"/order\")\n@inject\nasync def create_order(\n        data: CreateOrderAPIRequest,\n        interactor: FromDishka[CreateOrderInteractor]\n):\n    order_id = await interactor(data)\n    return order_id\n\nclass CreateOrderInteractor:\n    def __init__(self, logger: Logger['CreateOrderInteractor']):\n        self.logger = logger\n    \n    async def __call__(self, data: CreateOrderAPIRequest):\n        order = ...\n        self.logger.info(\"Created order\", order_id=order.id)\n```\n\n```json\n{\"message\": \"Created order\", \"logger_name\": \"module.path.CreateOrderInteractor\", \"request_id\": \"15c71f84-d0ed-49a6-a36e-ea179f0f62ef\", \"order_id\": \"15c71f84-d0ed-49a6-a36e-ea179f0f62ef\"}\n```\n\nYou don’t need to pass all this data between the layers of your application – you simply store it in a context that’s accessible throughout the entire application wherever DI is available.\n\n\n\n### How do other libraries solve this?\n\nHere we’ll look at how other libraries propose to solve this problem.\n\n\n\n### logging (standard library)\n\nDoes not support context, and the only way to pass additional data to logs is:\n\n```python\nlogging.info(\"user logged in\", extra={\"user_id\": user_id})\n```\n\nThe problem here is that you have to pass all the required data explicitly – meaning that every function that logs something must accept all necessary data as arguments.\n\n### loguru\n\nThis is addressed by creating a new logger object that contains the context:\n\n```python\nfrom loguru import logger\n\nlogger_with_request_id = logger.bind(request_id=str(uuid.uuid4))\nlogger_with_request_and_user_id = logger_with_request_id.bind(user_id=user.id)\n```\n\nHowever, you can’t use this in a Dependency Injection approach – it recreates the logger and doesn’t function as a true logging context in the full sense.\n\n\n\n### structlog\n\nThis library handles context using `contextvars`, but there are significant issues with this approach.\nImplementing context via `contextvars` is not entirely safe, as it relies on thread-local storage.\n\n* Context can leak into other requests if you forget to clear all the context variables used during a request.\n* You can lose the request context if you launch an operation in a separate thread within that request.\n\nFor synchronous applications, this may be acceptable, but you still need to manually clear the entire context at the start of each new request. In asynchronous applications, the context must remain within the boundaries of `await`, since coroutines execute within a single thread.\n\nWorking with context looks like this:\n\n\n```python\nlog = structlog.get_logger()\n\nclear_contextvars()\nbind_contextvars(a=1, b=2)\nlog.info(\"hello\")\n```\n```\nevent='hello' a=1 b=2\n```\n\n\n\n## Features\n\n### Logging Contexts and Integration with Dishka\n\nOne of the main features of Unilogging is the ability to conveniently pass values into a context, the data from which can later be used by your formatter. This is similar to the extra argument in Python's standard logging.\n\nUnilogging offers new possibilities with a more convenient API. You can populate the context with data at various stages of your application's execution, and logger classes below will pick up this context at any level of the application. This works within the REQUEST-scope. \n\nHere’s an example to illustrate – a middleware in a FastAPI application that generates a request_id and adds it to the context.\n\n```python\n@app.middleware(\"http\")\nasync def request_id_middleware(request, call_next):\n    logger = await request.state.dishka_container.get(Logger)\n    with logger.begin_scope(request_id=uuid.uuid4()):\n        response = await call_next(request)\n        return response\n```\n\n\n\n### Generic logger name or your own factory (Integration with Dishka)\n\nYou can retrieve a logger from the DI container as follows:\n\n```python\nclass SomeClass:\n    def __init__(self, logger: Logger['SomeClass']):\n        ...\n```\n\nIn this case, when using the standard integration with Dishka, a new logger will be created with the name `your_module.path_to_class.SomeClass`. If you don’t need this, you can avoid using a generic logger – in that case, the logger name will be `unilogging.Logger`, or you can pass your own factory into the integration.\n\nThe default logger factory in the provider is used so that you can supply your own factory with custom logic for creating standard loggers – for example, if you want logger names to be generated based on different criteria. However, your factory must conform to the `StdLoggerFactory` protocol.\n\nYour factory should follow the protocol below:\n\n```python\nclass StdLoggerFactory(Protocol):\n    def __call__(self, generic_type: type, default_name: str = ...) -\u003e logging.Logger:\n        ...\n```\n\nThen you can pass it like this:\n\n```python\nUniloggingProvider(std_logger_factory=your_factory)\n```\n\n\n\n### Templating – Injecting values from the context\n\nYou can use the built-in log record formatting provided by the library. At the stage of passing the record to the standard logger, it formats the message using `format_map`, injecting the entire current context. This feature is typically used when your logs are output in JSON format.\n\n```python\nwith logger.begin_context(user_id=user.id):\n    logger.info(\"User {user_id} logged in using {auth_method} auth method\", auth_method=\"telegram\")\n```\n```\nINFO:unilogging.Logger:User 15c71f84-d0ed-49a6-a36e-ea179f0f62ef logged in using telegram auth method\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoduni%2Funilogging","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoduni%2Funilogging","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoduni%2Funilogging/lists"}