{"id":31775928,"url":"https://github.com/kolitiri/contextlogger","last_synced_at":"2025-10-10T05:20:31.858Z","repository":{"id":54389740,"uuid":"319105994","full_name":"kolitiri/contextlogger","owner":"kolitiri","description":"A logging boilerplate enhanced by the use of contextvars","archived":false,"fork":false,"pushed_at":"2021-02-21T15:49:12.000Z","size":39,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-15T12:09:52.316Z","etag":null,"topics":["context","context-log","logging","logging-context","python","python-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/kolitiri.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-06T18:46:13.000Z","updated_at":"2024-10-15T06:39:17.000Z","dependencies_parsed_at":"2022-08-13T14:10:54.639Z","dependency_job_id":null,"html_url":"https://github.com/kolitiri/contextlogger","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kolitiri/contextlogger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kolitiri%2Fcontextlogger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kolitiri%2Fcontextlogger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kolitiri%2Fcontextlogger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kolitiri%2Fcontextlogger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kolitiri","download_url":"https://codeload.github.com/kolitiri/contextlogger/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kolitiri%2Fcontextlogger/sbom","scorecard":{"id":566374,"data":{"date":"2025-08-11","repo":{"name":"github.com/kolitiri/contextlogger","commit":"340359ae124079ff53e6ab84cc2aca42a7131c55"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/14 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T15:05:10.088Z","repository_id":54389740,"created_at":"2025-08-20T15:05:10.089Z","updated_at":"2025-08-20T15:05:10.089Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002885,"owners_count":26083468,"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-10-10T02:00:06.843Z","response_time":62,"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","context-log","logging","logging-context","python","python-logging"],"created_at":"2025-10-10T05:20:28.418Z","updated_at":"2025-10-10T05:20:31.848Z","avatar_url":"https://github.com/kolitiri.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Context Logger\n\n1. [Description](#description)\n2. [Requirements](#requirements)\n3. [Installation](#installation)\n4. [Usage](#usage)\n5. [Tutorial](#tutorial)\n    * [Logging configuration](#logging-configuration)\n    * [Logging without context](#logging-without-context)\n    * [Logging with static context](#logging-with-static-context)\n    * [Logging with dynamic context](#logging-with-dymanic-context)\n    * [Logging with context in multiple modules](#logging-multiple)\n    * [Structured logging](#structured-logging)\n6. [Version Updates](#versionupdates)\n7. [Contributions](#contributions)\n\n\n# Description \u003ca name=\"description\"\u003e\u003c/a\u003e\n\nA simple logger that leverages the power of the [contextvars](https://docs.python.org/3/library/contextvars.html) library to inject contextual details in your logs.\n\nIt exposes a simple interface, similar to the standard logging.[Logger](https://docs.python.org/3/library/logging.html) and it supports **Structured** logging for more advanced use cases.\n\n**Source Code**: https://github.com/kolitiri/contextlogger\n\n# Requirements \u003ca name=\"requirements\"\u003e\u003c/a\u003e\nPython 3.7+\n\n# Installation \u003ca name=\"installation\"\u003e\u003c/a\u003e\n```python\npip install contextlogger\n```\n\n# Usage \u003ca name=\"usage\"\u003e\u003c/a\u003e\nThis is a bare minimum example to get you started. (Check the tutorial below for some more realistic scenarios)\n\n```python\n\"\"\" my_app.py \"\"\"\nimport asyncio\nimport logging\nfrom uuid import uuid4\n\nfrom contextlogger import CLogVars, CLogVar, getCLogger\n\n\n# Create and configure a CLogger instance\nclogger = getCLogger(__name__)\nconsole_handler = logging.StreamHandler()\nclogger.addHandler(console_handler)\nclogger.setLevel('DEBUG')\n\n# Create a CLogVars container with static or dynamic context variables\nclogger.clogvars = CLogVars(\n    static=CLogVar(name='static'),\n    request_id=CLogVar(name='request_id', setter=lambda: str(uuid4())),\n)\n\nasync def my_func():\n    # Set the value of your static variable\n    clogger.setvar('static', value=1)\n\n    # Set the value of your dynamic variable\n    clogger.setvar('request_id')\n\n    clogger.info(f\"Hello World!\")\n\n\nasync def main():\n    await asyncio.gather(my_func())\n\nif __name__ == '__main__':\n    asyncio.run(main())\n```\n\n### Output\n```\n{'static': 1, 'request_id': '7e643fe2-bc7a-498c-a0fe-66ae58c671da'} Hello World!\n```\n\n# Tutorial \u003ca name=\"tutorial\"\u003e\u003c/a\u003e\nThis should be as simple as it gets!\n\nLet's assume that we have a package with the following structure:\n```\nmy_app/\n│\n├── my_app/\n│   ├── __init__.py\n│   ├── runner.py\n├── main.py\n```\n\nwhere your **main.py** is simply running the two tasks defined in your runner.py module.\n\n```python\n\"\"\" main.py \"\"\"\nimport asyncio\n\nfrom my_app.runner import task1, task2\n\n\nasync def main():\n\tawait asyncio.gather(\n\t\ttask1(),\n\t\ttask2(),\n\t)\nasyncio.run(main())\n```\n\n```python\n\"\"\" runner.py \"\"\"\nasync def task1():\n\tpass\n\nasync def task2():\n\tpass\n```\n\n## Logging configuration \u003ca name=\"description\"\u003e\u003c/a\u003e\n\nIn the **\\_\\_init\\_\\_.py** module of your project setup your logger.\nThis will be very similar to the way you would normally configure a regular logger from the standard library.\n```python\n\"\"\" __init__.py \"\"\"\nimport logging\nfrom logging.handlers import TimedRotatingFileHandler\nimport os\n\nimport contextlogger\n\n# Create a CLogger instance\nclogger = contextlogger.getCLogger(__name__)\n\n# The the logging level\nclogger.setLevel('DEBUG')\n\n# Create a logging formatter\nlogging_format = \"%(asctime)s %(levelname)s %(name)s %(message)s\"\nformatter = logging.Formatter(logging_format)\n\n# Create handlers for console logger\nconsole_handler = logging.StreamHandler()\nconsole_handler.setFormatter(formatter)\nclogger.addHandler(console_handler)\n\n# Create handlers for file logger\nLOG_DIR = 'logs'\nAPP = 'MY-APP'\nif not os.path.exists(LOG_DIR):\n\tos.makedirs(LOG_DIR)\n\nfile_handler = TimedRotatingFileHandler(f\"{LOG_DIR}/{APP}.log\", when=\"midnight\", interval=1)\nfile_handler.setFormatter(formatter)\nfile_handler.suffix = \"%Y%m%d\"\nclogger.addHandler(file_handler)\n```\nSo far, the only thing that we have done differently is that instead of using the **getLogger** function of the standard logging library, we used the **getCLogger** function from the contextlogger library.\n\n## Logging without context \u003ca name=\"logging-without-context\"\u003e\u003c/a\u003e\n\nOnce your logger configuration is set, you can use your logger in your **runner.py** file (or whatever file you choose as your entry point)\n```python\n\"\"\" runner.py \"\"\"\nfrom my_app import clogger\n\nasync def task1():\n\tclogger.info(f\"Hello from {task1.__name__}\")\n\nasync def task2():\n\tclogger.info(f\"Hello from {task2.__name__}\")\n```\n\nAs expected, if you run your **main.py** the output of the clogger will be:\n```\n2020-12-06 18:07:27,008 INFO my_app Hello from task1\n2020-12-06 18:07:27,009 INFO my_app Hello from task2\n```\n\n## Logging with static context \u003ca name=\"logging-with-static-context\"\u003e\u003c/a\u003e\n\nNow lets add some context to our logging.\n\nWe can do that by adding a custom UserDict (CLogVars) of **CLogVar** (context log variables) to our logger.\n\nLet's add a 'static' attribute... Not very useful but why not!\n\n```python\n\"\"\" runner.py \"\"\"\nfrom contextlogger import CLogVars, CLogVar\nfrom my_app import clogger\n\nclogger.clogvars = CLogVars(\n\tstatic=CLogVar(name='static'),\n)\n\nasync def task1():\n    # Set our 'static' value for task1\n\tclogger.setvar('static', value=1)\n\n\tclogger.info(f\"Hello from {task1.__name__}\")\n\nasync def task2():\n    # Set our 'static' value for task2\n\tclogger.setvar('static', value=2)\n\n\tclogger.info(f\"Hello from {task2.__name__}\")\n```\nAnd voila! Now the output of the clogger includes our static values:\n\n```\n2020-12-06 18:12:07,505 INFO my_app {'static': 1} Hello from task1\n2020-12-06 18:12:07,505 INFO my_app {'static': 2} Hello from task2\n```\n\n## Logging with dynamic context \u003ca name=\"logging-with-dymanic-context\"\u003e\u003c/a\u003e\n\nOk but that was not very handy, right? Let's do something more realistic. Let's pretend that this is our FastApi application and we want to add a 'request_id' to every request we receive.\n\nNow, things get interesting! Our CLogVar can also accept a 'setter' argument which is a function that generates a new uuid every time we enter a new context. Every time we call the **setvar** without a value, it will try to find a **setter** to do the job!\n\n```python\nfrom uuid import uuid4\n\nfrom contextlogger import CLogVars, CLogVar\nfrom my_app import clogger\n\nclogger.clogvars = CLogVars(\n\tstatic=CLogVar(name='static'),\n\trequest_id=CLogVar(name='request_id', setter=lambda: str(uuid4())),\n)\n\nasync def task1():\n\t# Set our 'static' value for task1\n\tclogger.setvar('static', value=1)\n\t\n\t# Set our request_id value for task1\n\tclogger.setvar('request_id')\n\n\tclogger.info(f\"Hello from {task1.__name__}\")\n\nasync def task2():\n    # Set our 'static' value for task2\n\tclogger.setvar('static', value=2)\n\t\n\t# Set our 'request_id' value for task2\n\tclogger.setvar('request_id')\n\n\tclogger.info(f\"Hello from {task2.__name__}\")\n```\n\nAnd here we are, with something a lot more useful than just a static value:\n```\n2020-12-06 18:21:17,626 INFO my_app {'request_id': 'd3961bd9-f701-4222-ad32-f204e9eb968a', 'static': 1} Hello from task1\n2020-12-06 18:21:17,626 INFO my_app {'request_id': '6d4cdab2-e24b-481b-b54b-12c6ee9bcc1b', 'static': 2} Hello from task2\n```\n\n## Logging with context in multiple modules \u003ca name=\"logging-multiple\"\u003e\u003c/a\u003e\n\nFinally, let's add an extra module just for the sake of it.\n\nNow our directory will look like:\n```\nmy_app/\n│\n├── my_app/\n│   ├── __init__.py\n│   ├── runner.py\n│   ├── another_module.py\n├── main.py\n```\n\nwhere our **another_module.py** simply imports the logger and uses it in a function:\n```python\nfrom my_app import clogger\n\n\ndef test():\n\tclogger.info(f\"Hello from another_module\")\n```\n\nNow, if we call our test function inside the task1 of our **runner.py**:\n\n```python\nfrom uuid import uuid4\n\nfrom contextlogger import CLogVars, CLogVar\nfrom my_app import clogger\nfrom my_app.another_module import test\n\nclogger.clogvars = CLogVars(\n\tstatic=CLogVar(name='static'),\n\trequest_id=CLogVar(name='request_id', setter=lambda: str(uuid4())),\n)\n\nasync def task1():\n\t# Set our 'static' value for task1\n\tclogger.setvar('static', value=1)\n\t\n\t# Set our request_id value for task1\n\tclogger.setvar('request_id')\n\n\tclogger.info(f\"Hello from {task1.__name__}\")\n\t\n\ttest()\n\nasync def task2():\n    # Set our 'static' value for task2\n\tclogger.setvar('static', value=2)\n\t\n\t# Set our 'request_id' value for task2\n\tclogger.setvar('request_id')\n\n\tclogger.info(f\"Hello from {task2.__name__}\")\n```\n\nwe should see that the log line that is printed inside our **another_module.py** has the same request_id and static values with the log line that is printed in task1. And this is expected since they belong to the same context.\n\n```\n2020-12-06 18:33:36,634 INFO my_app {'request_id': '1eff5e40-4b05-4cd1-bd9c-edbee85d2995', 'static': 1} Hello from task1\n2020-12-06 18:33:36,635 INFO my_app {'request_id': '1eff5e40-4b05-4cd1-bd9c-edbee85d2995', 'static': 1} Hello from another_module\n2020-12-06 18:33:36,635 INFO my_app {'request_id': 'ec68779f-46f6-4ea0-a003-9ddb053887e1', 'static': 2} Hello from task2\n```\n\n## Structured logging \u003ca name=\"structured-logging\"\u003e\u003c/a\u003e\n\nIf you prefer structured logging, you can create the **clogger** instance using the 'structured' argument, which will cause the message to be printed in a structured format.\n\nThen, just change your formatter accordingly.\n\n```python\n\n\"\"\" __init__.py \"\"\"\nimport logging\nfrom logging.handlers import TimedRotatingFileHandler\nimport os\n\n\nimport contextlogger\n\n# Create a CLogger instance\nclogger = contextlogger.getCLogger(__name__, structured=True)\n\n# Create a logging formatter\nlogging_format = \"{'time': '%(asctime)s', 'level': '%(levelname)s', 'name': '%(name)s', %(message)s}\"\nformatter = logging.Formatter(logging_format)\n\n# Create handlers for console logger\nconsole_handler = logging.StreamHandler()\nconsole_handler.setFormatter(formatter)\nclogger.addHandler(console_handler)\n\n# Create handlers for file logger\nLOG_DIR = 'logs'\nAPP = 'MY-APP'\nif not os.path.exists(LOG_DIR):\n\tos.makedirs(LOG_DIR)\n\nfile_handler = TimedRotatingFileHandler(f\"{LOG_DIR}/{APP}.log\", when=\"midnight\", interval=1)\nfile_handler.setFormatter(formatter)\nfile_handler.suffix = \"%Y%m%d\"\nclogger.addHandler(file_handler)\n```\nThe output result will become:\n\n```\n{'time': '2020-12-11 15:52:11,487', 'level': 'INFO', 'name': 'my_app', 'msg': 'Hello from task1', 'static': '1', 'request_id': 'cc75cb8f-f267-4406-b49c-fc2196a686c6'}\n{'time': '2020-12-11 15:52:11,487', 'level': 'INFO', 'name': 'my_app', 'msg': 'Hello from another_module', 'static': '1', 'request_id': 'cc75cb8f-f267-4406-b49c-fc2196a686c6'}\n{'time': '2020-12-11 15:52:11,487', 'level': 'INFO', 'name': 'my_app', 'msg': 'Hello from task2', 'static': '2', 'request_id': '7117cdb4-a0dd-4e12-89a5-756a03d7f8b1'}\n```\n\n# Version Updates  \u003ca name=\"versionupdates\"\u003e\u003c/a\u003e\n**1.0.0**: Introduces breaking changes. CLogger.clogvars has been converted from a List to a custom UserDict to maintain consistency between get/set functionality.\n\n# Contributions  \u003ca name=\"contributions\"\u003e\u003c/a\u003e\nIf you want to contribute to the package, please have a look at the CONTRIBUTING.md file for some basic instructions.\nFeel free to reach me in my email or my twitter account, which you can find in my github profile!\n\n# License\nThis project is licensed under the terms of the MIT license.\n\n# Authors\nChristos Liontos","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkolitiri%2Fcontextlogger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkolitiri%2Fcontextlogger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkolitiri%2Fcontextlogger/lists"}