{"id":16728764,"url":"https://github.com/hukkin/loga","last_synced_at":"2025-04-10T11:07:25.748Z","repository":{"id":39887432,"uuid":"291271865","full_name":"hukkin/loga","owner":"hukkin","description":"Automated logging for Python","archived":false,"fork":false,"pushed_at":"2025-03-31T17:18:10.000Z","size":512,"stargazers_count":4,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T10:08:06.347Z","etag":null,"topics":["decorators","graylog","logging","python"],"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/hukkin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-29T12:59:36.000Z","updated_at":"2024-06-06T15:28:16.000Z","dependencies_parsed_at":"2023-12-18T19:07:49.649Z","dependency_job_id":"1e9b13da-dc06-4d9b-b437-b13d2c0e321f","html_url":"https://github.com/hukkin/loga","commit_stats":null,"previous_names":["hukkinj1/loga"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hukkin%2Floga","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hukkin%2Floga/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hukkin%2Floga/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hukkin%2Floga/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hukkin","download_url":"https://codeload.github.com/hukkin/loga/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248208207,"owners_count":21065199,"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","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":["decorators","graylog","logging","python"],"created_at":"2024-10-12T23:11:38.633Z","updated_at":"2025-04-10T11:07:25.731Z","avatar_url":"https://github.com/hukkin.png","language":"Python","readme":"[![Build Status](https://github.com/hukkin/loga/workflows/Tests/badge.svg?branch=master)](https://github.com/hukkin/loga/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush)\n[![codecov.io](https://codecov.io/gh/hukkin/loga/branch/master/graph/badge.svg)](https://codecov.io/gh/hukkin/loga)\n[![PyPI version](https://img.shields.io/pypi/v/loga)](https://pypi.org/project/loga)\n\n# `@loga`: automated logging for Python\n\n\u003c!--- Don't edit the version line below manually. Let bump2version do it for you. --\u003e\n\n\u003e Version 1.0.0\n\n\u003e You find Python's builtin `logging` module repetitive, tedious and ugly,\n\u003e and the logs you do write with it clash with your otherwise awesome style.\n\u003e `loga` is here to help: it automates the boring stuff, simplifies the tricky stuff, hooks up effortlessly to\n\u003e [graylog](https://www.graylog.org/),\n\u003e and keeps an eye out for privacy and security if you need it to.\n\n**Table of Contents**\n\n\u003c!-- mdformat-toc start --slug=github --no-anchors --maxlevel=6 --minlevel=2 --\u003e\n\n- [Install](#install)\n- [Setup](#setup)\n- [Usage](#usage)\n  - [Loga as decorator](#loga-as-decorator)\n  - [Custom messages](#custom-messages)\n  - [Logging without decorators](#logging-without-decorators)\n  - [Methods](#methods)\n  - [Context managers](#context-managers)\n- [Limitations](#limitations)\n\n\u003c!-- mdformat-toc end --\u003e\n\n## Install\n\n```bash\npip install loga\n```\n\nTo install with Graylog support, do:\n\n```bash\npip install loga[graylog]\n```\n\n## Setup\n\nTo get started, import and instantiate the main class, ideally somewhere at the core of your project.\nIf you have a module with multiple files, do the initial configuration in the main `__init__.py`,\nor in a file called `log.py`,\nso you can import the same, ready-set-up logger easily.\n\nFor example, if your app was called `tester`, you could add the following to `tester/__init__.py`:\n\n```python\nfrom loga import Loga\n\n# all setup values are optional\nloga = Loga(\n    facility=\"tester\",  # name of program logging the message\n    graylog_address=(\"0.0.0.0\", 9999),  # address for graylog (ip, port)\n    do_print=True,  # print each log to console\n    do_write=True,  # write each log to file\n    logfile=\"mylog.txt\",  # custom path to logfile\n    truncation=1000,  # longest possible value in extra data\n    private_data={\"password\"},  # set of sensitive args/kwargs\n)\n```\n\n## Usage\n\nIn other parts of the project, you can then access the configured logger instance with:\n\n```python\nfrom tester import loga\n```\n\n### Loga as decorator\n\nYou can use `@loga` as a decorator on any callable: a class, on its method, or on function.\nOn classes, it will log every method; on methods and functions it will log the call signature, return and errors.\nThe central idea behind `loga` is that you can simply decorate every class in your project,\nas well as any important standalone functions,\nand have comprehensive, standardised information about your project's internals without any extra labour.\n\nIf a method within a decorated class is called too often,\nor if you don't need to keep an eye on it,\nyou can use `@loga.ignore` to ignore it.\nAlso available is `@loga.errors`, which will only log exceptions, not calls and returns.\n\nFor an example use-case, let's make a simple class that multiplies two numbers, but only if a password is supplied.\nWe will ignore logging of the boring authentication system.\n\n```python\n@loga\nclass Multiplier:\n    def __init__(self, base):\n        self.base = base\n\n    def multiply(self, n, password):\n        \"\"\"\n        Multiply by the number given during initialisation--requires password\n        \"\"\"\n        self.authenticated = self._do_authentication(password)\n        if not self.authenticated:\n            raise ValueError(\"Not authenticated!\")\n        return self.base * n\n\n    @loga.ignore\n    def _do_authentication(self, password):\n        \"\"\"Not exactly Fort Knox\"\"\"\n        return password == \"tOpSeCrEt\"\n```\n\nFirst, let's use it properly, with our secret password passed in:\n\n```python\nmult = Multiplier(50)\nresult = mult.multiply(50, \"tOpSeCrEt\")\nassert result == 2500  # True\n```\n\nWe'll get some nice text in the console:\n\n```\n11.05 2018 17:14:54 *Called Multiplier.multiply(n=50, password='******')\n11.05 2018 17:14:54 *Returned from Multiplier.multiply(n=50, password='******') with int (2500)\n```\n\nNotice that our private argument `password` was successfully obscured, even without us naming the argument when we called the method.\nIf you used `do_write=True`, this log will also be in your specified log file, also with password obscured.\n\n```python\nresult = mult.multiply(7, \"password123\")\n```\n\nHere an error will raise, a log will be generated, and we'll get extra info in the console, including traceback:\n\n```\n11.05 2018 17:19:43 *Called Multiplier.multiply(n=7, password='******')\n11.05 2018 17:19:43 *Errored during Multiplier.multiply(n=7, password='******') with ValueError \"Not authenticated!\"    20 -- see below:\nTraceback (most recent call last):\n  File \"/Users/danny/work/loga/loga/loga.py\", line 137, in full_decoration\n    response = function(*args, **kwargs)\n  File \"tester.py\", line 13, in multiply\n    raise ValueError('Not authenticated!')\nValueError: Not authenticated!\n```\n\nIf you're using [graypy](https://github.com/severb/graypy/),\nyou'll get a lot of extra goodness,\nsuch as key-value pairs for call signatures, timestamps, arguments, return values, exception information, and so on.\n\n### Custom messages\n\nWhen configuring `loga`, you can use your own message format for the auto-generated logs.\nThere are four keys, one for each autolog type:\n\n```python\nloga = Loga(\n    called=\"Log before callable is run\",\n    returned=\"Log for return from {call_signature} at {timestamp}\",\n    returned_none=\"Log when the return value of the callable is None\",\n    errored=\"Log string on exception: {exception_type}\",\n)\n\n\n@loga\ndef test():\n    pass\n```\n\nIf you pass `None` for any of these keyword arguments, logs of that time will be completely suppressed.\nIf you do not provide a value for `returned_none`, `loga` will use the value you provided for `returned`, or fall back to its own default.\n\nNotice, in the example above, you can include particular format strings in the log message.\nCurrently supported are:\n\n- `call_signature`: the callable name and its arguments and keyword arguments\n- `callable`: the `__qualname__` of the decorated object\n- `params`: comma separated key value pairs for arguments passed\n- `log_level`: the log level associated with this log\n- `timestamp`: time at time of logging\n- `couplet`: `uuid.uuid1()` for the called and returned/errored pair\n- `number_of_params`: total `args + kwargs` as int\n- `decorated`: always `True`\n\nThe `errored` log additionally supports:\n\n- `exception_type`: `ValueError`, `AttributeError`, etc.\n- `exception_msg`: details about the thrown exception\n- `traceback`: exception traceback\n\nAnd the `returned` and `returned_none` logs support:\n\n- `return_value`: the object returned by the callable\n- `return_type`: type of returned object\n\nAdding more such strings is trivial; submit an issue if there is something else you need.\n\n### Logging without decorators\n\nFor logging manually, `loga` provides methods similar to the logging functions of the `logging` standard library:\n`loga.log`, `loga.debug`, `loga.info`, `loga.warning`, `loga.error`, and `loga.critical`.\nThe methods use the configuration that has already been defined.\nThe main method `loga.log` takes three parameters:\n\n```python\nlevel = 50\nmsg = \"Message to log\"\nextra = dict(some=\"data\", that=\"will\", be=\"logged\")\nloga.log(level, msg, extra)\n# console: 11.05 2018 17:36:24 Message to log  50\n# extra_data in log file if `do_print` setting is True\n```\n\nMethods `loga.debug`, `loga.info`, `loga.warning`, `loga.error` and `loga.critical` are convenience methods for setting the log level.\nFor instance,\n\n```python\nloga.warning(\"A message\", dict(some=\"data\"))\n```\n\nis equivalent to\n\n```python\nloga.log(logging.WARNING, \"A message\", dict(some=\"data\"))\n```\n\nwhere `logging.WARNING` is an integer constant imported from the standard library.\n\nThe advantage of using `loga` for these kinds of logs is that `loga` will make the extra data more readable and truncate very large strings.\nMore importantly, you also still get whatever extras you've configured, like obfuscation of private data, or writing to console/file.\n\n### Methods\n\nYou can also start and stop logging with `loga.start()` and `loga.stop()`, at any point in your code, though by default, error logs will still get through.\nIf you want to suppress errors too, you can pass in `allow_errors=False`.\n\n### Context managers\n\nYou can suppress logs using a context manager.\nErrors are allowed here by default too:\n\n```python\nwith loga.pause(allow_errors=False):\n    do_something()\n```\n\n## Limitations\n\n`loga` uses Python's standard library (`logging`) to generate logs.\nThere are some gotchas when using it:\nfor instance, in terms of the extra data that can be passed in, key names for this extra data cannot clash with some internal names used within the `logging` module (`message`, `args`, etc.).\nTo get around this, you'll get a warning that your data contains a bad key name, and it will be changed (i.e. from `message` to `protected_message`).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhukkin%2Floga","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhukkin%2Floga","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhukkin%2Floga/lists"}