{"id":37071921,"url":"https://github.com/strigo/wryte","last_synced_at":"2026-01-14T08:26:42.187Z","repository":{"id":50049326,"uuid":"114740346","full_name":"strigo/wryte","owner":"strigo","description":"A Python logger for people who want some logging sanity","archived":false,"fork":false,"pushed_at":"2022-08-20T18:48:09.000Z","size":150,"stargazers_count":17,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-25T05:28:35.707Z","etag":null,"topics":["log","logger","logging","logging-library","python","python-logger","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":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/strigo.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":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2017-12-19T08:41:06.000Z","updated_at":"2023-09-26T16:22:57.000Z","dependencies_parsed_at":"2022-09-02T20:22:28.467Z","dependency_job_id":null,"html_url":"https://github.com/strigo/wryte","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/strigo/wryte","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strigo%2Fwryte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strigo%2Fwryte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strigo%2Fwryte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strigo%2Fwryte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strigo","download_url":"https://codeload.github.com/strigo/wryte/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strigo%2Fwryte/sbom","scorecard":{"id":855221,"data":{"date":"2025-08-11","repo":{"name":"github.com/strigo/wryte","commit":"0ce43b3d7faa179f1f067b69378c0f1b41bc99c0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/7 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":"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":"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":"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":"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE: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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 25 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-23T23:44:22.012Z","repository_id":50049326,"created_at":"2025-08-23T23:44:22.013Z","updated_at":"2025-08-23T23:44:22.013Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413962,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:16:59.381Z","status":"ssl_error","status_checked_at":"2026-01-14T08:13:45.490Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["log","logger","logging","logging-library","python","python-logger","python-logging"],"created_at":"2026-01-14T08:26:41.509Z","updated_at":"2026-01-14T08:26:42.178Z","avatar_url":"https://github.com/strigo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Wryte\n=====\n\n[![CircleCI](https://circleci.com/gh/strigo/wryte/tree/master.svg?style=svg)](https://circleci.com/gh/strigo/wryte/tree/master)\n[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/xq58fy0fwf6p11xt/branch/master?svg=true)](https://ci.appveyor.com/project/strigo/wryte)\n[![PyPI Version](http://img.shields.io/pypi/v/wryte.svg)](http://img.shields.io/pypi/v/wryte.svg)\n[![Supported Python Versions](https://img.shields.io/pypi/pyversions/wryte.svg)](https://img.shields.io/pypi/pyversions/wryte.svg)\n[![Requirements Status](https://requires.io/github/strigo/wryte/requirements.svg?branch=master)](https://requires.io/github/strigo/wryte/requirements/?branch=master)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/3f32a50da8bd4fbab4017b3780d4adec)](https://www.codacy.com/gh/strigo/wryte/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=strigo/wryte\u0026amp;utm_campaign=Badge_Grade)\n[![Code Coverage](https://codecov.io/github/strigo/wryte/coverage.svg?branch=master)](https://codecov.io/github/strigo/wryte?branch=master)\n[![Maintainability](https://api.codeclimate.com/v1/badges/423b4eee9ba182fecd84/maintainability)](https://codeclimate.com/github/strigo/wryte/maintainability)\n[![Is Wheel](https://img.shields.io/pypi/wheel/wryte.svg?style=flat)](https://pypi.python.org/pypi/wryte)\n\nWryte aims to provide a simple API for logging in Python adhering to logging principles fitting today's systems.\n\nWryte assumes that a standard CLI application logs to the console, while a server side app will probably want to log some human readable messages to syslog/console while logging JSON containing the same information with some additional contextual information over the wire (to a log aggregation backend e.g. ELK/Graylog2)\n\nNote that the following documentation relates to the code currently in the master branch. If you want to view docs for previous versions, please choose the relevant release in the \"releases\" tab.\n\n## Notable Features and design principles\n\n* Very easy to get started\n* Sane defaults!\n* Easily configurable via env vars (for the purpose of easy config via schedulers e.g. systemd, nomad, k8s)\n* Standardize formatting for both Console (human readable) and aggregation (JSON)\n* Handler agnosticism\n* Differentiate user events and system logs\n* Auto-enrich with useful data (hostname, pid, etc..)\n* Enrich with AWS EC2 metadata (instance-id, instance-type, region and ipv4) if available\n* Easily provide contexual data\n* Context binding to prevent repetition\n* Dynamic severity levels\n* Retroactive logging (WIP)\n* Assist in user tracing (via auto-provided context ids)\n* Zero logging exceptions. There should be zero logging exceptions causing the app to crash but rather Wryte should log errors whenever logging errors occur.\n\n\n\n## Installation\n\nWryte supports Linux, Windows and OSX on Python 3.7+\n\n```shell\npip install wryte\npip install wryte[color] # for colored console output.\n```\n\nFor dev:\n\n```shell\npip install https://github.com/strigo/wryte/archive/master.tar.gz\n```\n\n## Usage\n\n### Getting Started\n\nThis will log human readable messages to stdout and JSON to a file:\n\n```bash\n$ pip install wryte\n...\n\n$ export WRYTE_HANDLERS_FILE_PATH=log.txt\n```\n\n```python\nfrom wryte import Wryte\n\nwryter = Wryte(name='app')\nwryter.info('My Message', key='value')\n\n# 2020-11-20T21:37:00.268974 - app - INFO - My Message\n#  key=value\n\n...\n\n```\n\n```shell\n$ cat log.txt\n{\"name\": \"app\", \"hostname\": \"nir0s-x1\", \"pid\": 29748, \"type\": \"log\", \"message\": \"My Message\", \"level\": \"INFO\", \"timestamp\": \"2020-11-20T21:37:00.268974\"}\n```\n\n### CLI\n\nWryte provides a basic CLI to show-off output.\n\n```\n$ pip install wryte[cli]\n\n$ wryte -h\nUsage: wryte [OPTIONS] LEVEL MESSAGE [OBJECTS]...\n\nOptions:\n  --pretty / --ugly  Output JSON instead of key=value pairs for console logger\n  -j, --json         Use the JSON logger formatter instead of the console one\n  -n, --name TEXT    Change the default logger's name\n  --no-color         Disable coloring in console formatter\n  --simple           Log only message to the console\n  -h, --help         Show this message and exit.\n\n# Examples:\n\n$ wryte event my-event\n2018-02-18T08:52:23.526820 - Wryte - EVENT - my-event\n  cid=49b9260c-77b8-4ebb-bb15-d6ccce7c7ba4\n\n$ wryte info my-message key1=value1 key2=value2\n2018-02-18T08:52:50.691206 - Wryte - INFO - my-message\n  key1=value1\n  key2=value2\n\n$ wryte error my-message key1=value1 '{\"key2\": {\"key3\": \"value2\"}}' --ugly\n2018-02-18T08:53:45.126228 - Wryte - ERROR - my-message\n{\n    \"key2\": \"value2\",\n    \"key1\": \"value1\"\n}\n\n$ wryte warn my-message key1=value1 key2=value2 -j\n{\n    \"key1\": \"value1\",\n    \"name\": \"Wryte\",\n    \"pid\": 18613,\n    \"type\": \"log\",\n    \"level\": \"WARNING\",\n    \"timestamp\": \"2018-02-18T08:53:06.222926\",\n    \"message\": \"my-message\",\n    \"hostname\": \"strigo-x1\",\n    \"key2\": \"value2\"\n}\n\n```\n\n### Instantiating the logger\n\n```python\nfrom wryte import Wryte\n\nwryter = Wryte(\n  name=None,  # The name of the logger.\n  hostname=None,  # The name of the host.\n  level='info',  # Minimum severity level to log.\n  pretty=None,  # (Console only) Whether to pretty-print JSON or not.\n  bare=False,  # Omit any default handlers. You will have to add them yourself after.\n  jsonify=False,  # (Console only) Output json to the console instead of a human readable message.\n  color=True,  # (Console only) Colorize Console output.\n  simple=False,  # (Console only) Only print the message and key=value pairs.\n  enable_ec2=False  # Enrich with ec2 instance specific context.\n)\n```\n\n### Available logging levels\n\nWryte supports the following logging levels:\n\n* `event` (see below)\n* `debug`\n* `info`\n* `warning`\n* `warn`\n* `error`\n* `critical`\n\n### Distinguishing Events from Logs\n\nEvents are logs generated by user interactions, while logs are contextual to them. e.g.:\n\n```python\nwryter.event('Logging user in', user_id=user.id)\nwryter.info('Retrieving user info...' user_id=user.id)\nwryter.debug('Requesting session...', ...)\n# more debug stuff..\nwryter.info('User Logged in!', user_id=user.id)\n```\n\nWryte explicitly provides an `event` method so that you're able to distinguish \"things that happened in my application\" from \"things that happened in my system\".\n\nAn event is technically distinguished from a log by having a `{ 'type': 'event' }` field and different coloring in the console as well as returning a `cid` for flow tracing (more on that later).\n\n### Wryting a simple log to the console\n\nBy default, Wryte will output `TIMESTAMP - LEVEL - LOGGER_NAME - MESSAGE` (and the provided key=value pairs) to the console. Many CLI applications log only the message (e.g. `pip`). You can configure Wryte to do so by either passing the `simple` flag or by setting the `WRYTE_SIMPLE_CONSOLE` env var to \"true\" (any other value will explicitly set `false` even if the flag is passed) when instantiating the logger:\n\n```python\nwryter = Wryte(simple=True)\nwryter.info('My Message')\n\u003e\u003e\u003e 'My Message'\n```\n\n### Multiple Handlers\n\nWryte's goal is to allow logging to both the console (journald, stdout, etc..) and an aggregation backend. You're not limited in any way and can ship using as many handlers as you want from a single logger.\n\n#### Adding handlers\n\nThe proposed use-case is when you want to provide easy server-side debugging using human readable messages while also sending events and logs to an aggregation backend such as Elasticsearch, Graylog2 and the likes.\n\nNote that Wryte aims to provide an easy way to configure itself via environment variables. See [Using Environment Variables to configure logging handlers](#using-environment-variables-to-configure-logging-handlers).\n\nFor example:\n\n```python\nfrom logzio.handler import LogzioHandler\n\n# Instantiate a debug level console logger named `wryte`.\nwryter = Wryte(name='wryte', level='debug')\n# `formatter` default is `json`. `level` default is `info`.\nwryter.add_handler(handler=LogzioHandler('LOGZIO_TOKEN'), name='logzio', formatter='json', level='info')\n\nwryter.info('My Message', {'key1': 'value2', 'key2': 'value2'}, key3=value3)\n# 2017-12-22T17:02:59.550920 - app - INFO - my message\n#  key1=value1,\n#  key2=value2,\n#  who=where\n\n...\n\n# This is sent to logz.io\n# {\n#     \"timestamp\": \"2017-12-22T14:19:09.828625\"\n#     \"hostname\": \"strigo-x1\",\n#     \"level\": \"DEBUG\",\n#     \"message\": \"TEST_MESSAGE\",\n#     \"pid\": 8554,\n#     \"name\": \"wryte\",\n#     \"key1\": \"value1\",\n#     \"key2\": \"value2\",\n#     \"key3\": \"value3\",\n# }\n```\n\nThe above will log the message and its associated key value pairs to the console in a human readable format and log machine readable JSON with some additional contextual information to logz.io.\n\n#### Listing and removing handlers\n\nYou can list and remove handlers currently attached to a logger:\n\n```python\nimport logging\n\nwryter = Wryte()\nhandler_name = wryter.add_handler(\n    handler=logging.FileHandler('file.log'),\n    formatter='console')\n\nwryter.info('My Message', {'key1': 'value2', 'key2': 'value2'}, who=where)\n# ...log some more\n\nwryter.list_handlers()\n['777b9655-e6f9-4b90-8be9-730edeb3afcf', '_console']\nwryter.remove_handler(handler_name)\n```\n\nBy default either the `_json` or `_console` handlers are added and they can also be removed.\n\n\n### Adding Context\n\nOn top of logging simple messages, Wryte assumes that you have context you would like to add to your logs.\nInstead of making you work to consolidate your data, Wryte allows you to pass multiple dictionaries and key value pair strings and consolidate them to a single dictionary.\n\nYou can pass any number of single level or nested dictionaries, kwargs and JSON strings, and those will be parsed and added to the log message.\n\nFor example:\n\n```python\nwryter.info('My Message', {'key1': 'value2', 'key2': 'value2'}, who='where')\n# 2017-12-22T17:02:59.550920 - app - INFO - my message\n#  key1=value1,\n#  key2=value2,\n#  who=where\n\nwryter.error('Logging kwargs', key='value')  # kwargs\nwryter.debug('Logging JSON strings', '{\"key\": \"value\"}')  # JSON strings\nwryter.critical('Logging nested dicts', {'key': 'value', 'nested': { 'key1': 'value1', 'key2': 'value2'}})\n\n```\n\n#### Reserved Contextual Fields\n\nThe following are fields reserved to Wryte:\n\n* Any `_field`\n* `hostname`\n* `pid`\n* `message`\n* `timestamp`\n* `level`\n* `type`\n* `name`\n* `ec2_instance_id` (optional)\n* `ec2_instance_type` (optional)\n* `ec2_region` (optional)\n* `ec2_ipv4` (optional)\n\nNote that even if the fields are provided in a log message, they are bound to be overriden by Wryte.\n\n#### Enriching with AWS EC2 instance context\n\nThe aforementioned optional fields are added to the context if the `WRYTE_EC2_ENABLED` env var is set. The information is polled for when the logger is instantiated.\n\nNote that if the data is unattainable for any reason, Wryte will spit out an error message stating so when the logger is instantiated.\n\n#### Container oriented context\n\nIf you're running within a container (namely, Docker), the hostname is the only certain identifier you have for your host. Luckily, starting with a rather early version of Docker, the hostname is also the shortened container's id. Even luckily-er, if you're running on EC2, the metadata endpoint is accessible from within the container, so you can pinpoint your exact location by correlating EC2 instance metadata with container ids.\n\n\n#### Binding contextual information to a logger\n\nTo prevent having to repeat context with every log message, Wryte allows you to bind any amount of key=value pairs to a logger to add context to it.\n\nUntil unbound, the logger will include the bound fields in each message.\n\n```python\nwryter = ...\nwryter.info('This will add the above key value pairs to any log message')\nwryter.bind({'user_id': framework.user, ...}, key=value)\n# ...do stuff\n\nwryter.unbind('user_id')\n```\n\nAnd just like with the logger itself, you can bind nested dicts, kwargs and JSON strings.\n#### Badly formatted context\n\nWhen providing a badly formatted context (e.g. `wryte.info('Message', ['bad_context'])`), a field containing the provided context will be added to the log like so:\n\n```bash\n2018-04-18T08:06:14.739438 - Wryte - INFO - Message\n  _bad_object_3dff246b-f8fd-44af-bd29-29fde77a11fc=['bad_context']\n  bound2=value2\n\n```\n\n\n### Changing a logger's level\n\nTo better control output, you can change a logger's level like so:\n\n```python\nwryter.set_level(LEVEL_NAME)\n```\n\n\n#### Dynamically changing log level on errors\n\nDebug logs are only interesting when we need to debug something. Logging can potentially block the application (if not using asnyc handlers), utilize the network, or increase disk IOPS. Ideally, we would only handle debug logs when some problem arises, and once that is resolved, the logger will assume info level logging.\n\nYou can signal that a certain error requires changing the level to \"debug\" from now on. You could use the `set_level` method everytime you have such an error but instead, Wryte proposes that it should simpler.\n\nFor example, let's say that your application reads a config file per `gunicorn` worker and you would like to activate debug logging if for some reason a worker can't read the file:\n\n```python\nconfig_file_read = False\n\ntry:\n    config = read_config(PATH)\nexcept ReadError as ex:\n    # Can also pass `set_level` to `critical`, not just to `error`.\n    wryter.error('Failed to read config ({})'.format(ex), {'context': context}, _set_level='debug')\n    # do_something to reread the file, but this time with debug logging enabled.\n    config_file_read = True\nfinally:\n    if config_file_read:\n        wryter.set_level('info')\n    else:\n        raise SomeError(...)\n\n```\n\nDumb example maybe, but you get the point :)\n\nThe `_set_level` flag is supported in the `error` and `critical` severity levels.\n\n\n### Retroactive Logging\n\n`raise NotImplementedError('WIP!')` :)\n\nMuch like dynamic level logging, retroactive logging can help reduce strain on the application/server by only logging to disk/network when there's a certain problem.\n\nRetroactive logging is the practice of logging all transaction-specific logs to memory and only flushing to disk/ over the wire if something goes wrong; otherwise, we simply get rid of them. A culprit of this is that if the application stops working, it won't write the logs.\n\nWryte aims to provide a retroactive logging implementation somewhere along the lines of:\n\n```python\nwryter.event('...', _retro=True)\n# from here on, until the end of a transaction, all debug logs will be logged to the MemoryHandler\nwryter.debug('...')\n...\n\n# End of transaction. If err, write, else, flush to Null.\nwryter.flush(err)\n\n```\n\n\n### Instantiating a bare Wryte instance\n\nYou can instantiate a logger without any handlers and add handlers yourself.\n\nNote that if you instantiate a bare logger, logging will succeed but you will not get any output.\nWryte doesn't protect you from doing that simply because you might want to add handlers only in specific situations.\n\n```python\nimport logging\n\nwryter = Wryte(name='wryte', level='debug', bare=True)\nwryter.add_handler(handler=logging.FileHandler('file.log'), formatter='console')\n\nwryter.info('My Message', {'key1': 'value2', 'key2': 'value2'}, who=where)\n\nwith open('file.log') as log_file:\n    print(log_file.read())\n...\n\n```\n\n\n### Using Formatters\n\nCurrently, Wryte allows to choose between just two formatters: `json` and `console`. The `json` formatter obviously doesn't require any formatting as any fields provided in the message will be propagated with the JSON string.\n\nThe console output, on the other hand, might require formatting. Wryte's priority is to simplify and standardize the way we print and ship log messages, not to allow you to just view console logs anyway you want. I might add something in the future to allow to format console messages.\n\nI don't wanna cut off your arm, so to make sure you can still print out messages formatted the way you want, without utilizing `Wryte`, you can simply pass your formatter instance when adding a handler, e.g:\n\n```python\nimport logging\n\nwryter = Wryte(name='wryte', level='debug', bare=True)\nwryter.add_handler(handler=logging.StreamHandler(sys.stdout), formatter=myFormatter)\n\n```\n\n### Coloring\n\nThe Console formatter supplied by Wryte outputs a colorful output by default using colorama, if colorama is installed.\n\nThe severity levels will be colored differently accordingly to the following mapping:\n\n```bash\n$ pip install wryte[color]\n```\n\n```python\nmapping = {\n    'debug': Fore.CYAN,\n    'info': Fore.GREEN,\n    'warning': Fore.YELLOW,\n    'warn': Fore.YELLOW,\n    'error': Fore.RED,\n    'critical': Style.BRIGHT + Fore.RED\n}\n```\n\nYou can disable colored output by instantating your logger like so:\n\n```python\nwryter = Wryte(color=False)\n```\n\n### Flow Tracing and context ID's\n\nIdeally, when a user performs an action in an app, a context related to that event will be logged and attached to any log message relating to that event, so that it is possible to tail the entire flow from the moment the user performed the action and until, say, they got a response from the db. Woo! What a long sentence!\n\nThis feature is not provided by any logging library that I know of, and for a good reason - it depends on many factors potentially related to that specific app. `structlog`, for instance, provides a way to log using a thread-local context, but that's specific to a thread and therefore doesn't solve the problem for distributed systems.\nA good way to solve the problem is to use a global, cross-service identifier (for instance, a user id).\n\nSpecifically for non-distributed systems, you can use the `cid` generated by the `event` method and supply it as an identifier.\n\n```python\n# cid defaults to a uuid if it isn't provided.\ncid = wryter.event('User logging in', {'user_id': 'strigo'}, cid=user.id)\nwryter.bind(cid=cid)\nwryter.debug('Requesting log-in host...', ...)\n...\nwryter.debug('Querying db for available server...', ...)\n...\nwryter.info('Host is: {0}'.format(host_ip))\nwryter.unbind('cid')\n...\n```\n\nThe idea behind this is that a cid can be passed into any log message within the same context. \"within the same context\" is of course very abstract, and is up to the developer to implement as it might be thread-related, framework-related, or else. I intend to expand the framework, but for now, that's what it is.\n\n### Accessing lower-level logger API\n\nWryte is built on top of Python's standard `logging` library and you can access the logger's API directly:\n\n```python\nwryter = Wryte()\n\nwryter.logger.setLevel(...)\ncurrent_level = wryter.logger.getEffectiveLevel(...)\n...\n\n```\n\nThe idea behind this is three-fold:\n\n1. You shouldn't need to use `logging` directly, because why would you?\n2. You should be able to workaround anything not implemented by Wryte, because reality.\n3. For the sake of keeping Wryte as minimal as possible, some things will not be implemented in Wryte and you will be able to use the logger's API directly so that you're not limited.\n\n## Configuring Wryte\n\n### Using Environment Variables to configure logging handlers\n\nNOTE: This is WIP, so things may break / be broken.\n\nOne of Wryte's goals is to provide a simple way to configure loggers. Much like Grafana and Fabio, Wryte aims to be completely env-var configurable.\n\nOn top of having two default `console` and `json` handlers which indicate the formatting and both log to stdout, you can utilize built-in and 3rd party handlers quite easily.\n\nBelow are the global env vars used to configure all loggers instantiated by Wryte.\n\nFor each handler, there are four basic env vars:\n\n* `WRYTE_HANDLERS_TYPE_NAME`      -\u003e The handler's name (defaults to `TYPE` in lowercase)\n* `WRYTE_HANDLERS_TYPE_LEVEL`     -\u003e The handler's logging level (defaults to `info` unless explicitly stated otherwise)\n* `WRYTE_HANDLERS_TYPE_FORMATTER` -\u003e The handler's formatter (`json` or `console`, defaults to `json`)\n\nOn top of those, there are handler specific configuration options:\n\n\n#### Default Handlers\n\nThe default handlers are set a bit differently from the rest (note the omission of `HANDLERS`):\n\n* You cannot set the name of the logger.\n\n```\n# If set, will disable the handler.\nexport WRYTE_CONSOLE_DISABLED\n\n# If set, will replace the human readable format with a JSON format based on the `json` formatter.\nexport WRYTE_CONSOLE_JSONIFY\n\n# As with others, set the level.\nexport WRYTE_CONSOLE_LEVEL will set the level, as with other handlers.\n```\n\n#### FILE Handler\n\nWryte supports both the rotating and watching file handlers (on Windows, FileHandler replaces WatchingFileHandler if not rotating).\n\n```\n# (Required - enables file logging) Absolute path to the file logs should be written to\nexport WRYTE_HANDLERS_FILE_PATH=FILE_TO_LOG_TO\n\n# If set, will rotate files.\nexport WRYTE_HANDLERS_FILE_ROTATE=false\n\n# Size of each file in bytes if rotating\nexport WRYTE_HANDLERS_FILE_MAX_BYTES=13107200\n\n# Amount of logs files to keep if rotating\nexport WRYTE_HANDLERS_FILE_BACKUP_COUNT=7\n```\n\n#### Examples\n\nLogging to file:\n\n```\n$ export WRYTE_HANDLERS_FILE_PATH=log.txt\n\n$ python wryte.py\n2018-02-18T08:56:27.921500 - Wryte - INFO - Logging an error level message:\n2018-02-18T08:56:27.921898 - Wryte - ERROR - w00t\n2018-02-18T08:56:27.922055 - Wryte - INFO - Logging an event:\n2018-02-18T08:56:27.922259 - Wryte - EVENT - w00t\n  cid=5e7bbc8e-5857-4934-9a21-d134a8086319\n2018-02-18T08:56:27.922421 - Wryte - INFO - Binding more dicts to the logger:\n2018-02-18T08:56:27.922623 - Wryte - INFO - bind_test\n  bound1=value1\n  bound2=value2\n2018-02-18T08:56:27.922783 - Wryte - INFO - Unbinding keys:\n  bound1=value1\n  bound2=value2\n2018-02-18T08:56:27.922935 - Wryte - CRITICAL - unbind_test\n  bound2=value2\n2018-02-18T08:56:27.923088 - Wryte - ERROR - w00t\n  bound2=value2\n\n$ cat log.txt\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.921500\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\", \"message\": \"Logging an error level message:\"}\n{\"name\": \"Wryte\", \"level\": \"ERROR\", \"timestamp\": \"2018-02-18T08:56:27.921898\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\", \"message\": \"w00t\"}\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.922055\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\", \"message\": \"Logging an event:\"}\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.922259\", \"hostname\": \"my-host\", \"pid\": 19220, \"message\": \"w00t\", \"type\": \"event\", \"cid\": \"5e7bbc8e-5857-4934-9a21-d134a8086319\"}\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.922421\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\", \"message\": \"Binding more dicts to the logger:\"}\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.922623\", \"bound1\": \"value1\", \"hostname\": \"my-host\", \"pid\": 19220, \"message\": \"bind_test\", \"type\": \"log\", \"bound2\": \"value2\"}\n{\"name\": \"Wryte\", \"level\": \"INFO\", \"timestamp\": \"2018-02-18T08:56:27.922783\", \"bound1\": \"value1\", \"hostname\": \"my-host\", \"pid\": 19220, \"message\": \"Unbinding keys:\", \"type\": \"log\", \"bound2\": \"value2\"}\n{\"name\": \"Wryte\", \"bound2\": \"value2\", \"message\": \"unbind_test\", \"level\": \"CRITICAL\", \"timestamp\": \"2018-02-18T08:56:27.922935\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\"}\n{\"name\": \"Wryte\", \"bound2\": \"value2\", \"message\": \"w00t\", \"level\": \"ERROR\", \"timestamp\": \"2018-02-18T08:56:27.923088\", \"hostname\": \"my-host\", \"pid\": 19220, \"type\": \"log\"}\n\n```\n\n## Performance\n\nI haven't performed a deep benchmark on Wryte yet and am waiting to finish an initial implementation I will feel comfortable with before doing so.\n\nA very shallow benchmark (on my i7 1st Gen x1-carbon) showed:\n\n(For 10000 messages averaged over 30 runs, coloring disabled):\n\n`log.info('My Message')`\n\n* Wryte     -\u003e 407ms\n* Logbook   -\u003e 479ms\n* structlog -\u003e 750ms\n* logging   -\u003e 279ms\n\nNot bad. I'll be optimizing for performance wherever possible.\n\n## Testing\n\n```shell\ngit clone git@github.com:strigo/wryte.git\ncd wryte\npip install -r dev-requirements\ntox\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrigo%2Fwryte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrigo%2Fwryte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrigo%2Fwryte/lists"}