# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['troncos',
 'troncos.frameworks',
 'troncos.frameworks.django',
 'troncos.frameworks.gunicorn',
 'troncos.frameworks.requests',
 'troncos.frameworks.starlette',
 'troncos.frameworks.structlog',
 'troncos.logs',
 'troncos.traces']

package_data = \
{'': ['*']}

install_requires = \
['backoff>=1.0,<2.0',
 'opentelemetry-exporter-otlp-proto-grpc>=1.12.0,<2.0.0',
 'opentelemetry-propagator-b3>=1.12.0,<2.0.0',
 'opentelemetry-propagator-jaeger>=1.12.0,<2.0.0']

setup_kwargs = {
    'name': 'troncos',
    'version': '0.0.17',
    'description': 'Observability tools and boilerplate for use in python apps',
    'long_description': '<h1 align="center" style="border-bottom: 0">\n  🪵<br>\n  Troncos <br/>\n</h1>\n\n<p align="center">\n    <em>\n        Collection of Python logging and tracing tools\n    </em>\n    <br>\n    <a href="https://github.com/kolonialno/troncos/actions?workflow=CI">\n        <img src="https://github.com/kolonialno/troncos/actions/workflows/ci.yml/badge.svg" alt="CI status">\n    </a>\n    <a href="https://pypi.python.org/pypi/troncos">\n        <img src="https://img.shields.io/pypi/v/troncos.svg">\n    </a>\n    <img src="https://img.shields.io/pypi/pyversions/troncos">\n    <a href="https://github.com/kolonialno/troncos/blob/master/LICENSE">\n        <img src="https://img.shields.io/github/license/kolonialno/troncos.svg">\n    </a>\n</p>\n\n<!-- TOC -->\n  * [Installation](#installation)\n  * [Etymology](#etymology)\n  * [Setup](#setup)\n    * [Plain](#plain)\n    * [Starlette (with uvicorn)](#starlette--with-uvicorn-)\n    * [Django (with gunicorn)](#django--with-gunicorn-)\n  * [Logging](#logging)\n    * [Structlog](#structlog)\n  * [Tracing](#tracing)\n    * [trace_function](#trace_function)\n    * [trace_block](#trace_block)\n    * [trace_class](#trace_class)\n    * [trace_module](#trace_module)\n    * [trace_ignore](#trace_ignore)\n    * [Other instrumentors for tracing](#other-instrumentors-for-tracing)\n  * [Trace Propagation](#trace-propagation)\n    * [Send context](#send-context)\n      * [Requests](#requests)\n      * [Manually](#manually)\n    * [Receive context](#receive-context)\n      * [Using troncos middleware](#using-troncos-middleware)\n      * [Manually](#manually)\n<!-- TOC -->\n\n## Installation\n\n```console\n# With pip\n$ pip install troncos\n\n# With poetry\n$ poetry add troncos\n```\n\n## Etymology\n\n"Troncos" is the plural of the spanish word "Tronco", which translates to "trunk" or "log".\n\n## Setup\n\n> **NOTE**: It is a good idea to use a `settings.py`-file (or similar) as an authoritative source of variables (service name, environment, whether tracing is enabled or not, log level etc.)\n\n### Plain\n\nSetting up logging and tracing requires some code that lives as close to the invocation of the application as possible (like in an entrypoint).\n\n```python\nfrom os import environ\n\nfrom troncos.logs import init_logging_basic\nfrom troncos.traces import init_tracing_basic\n\ninit_logging_basic(\n    level=environ.get("LOG_LEVEL", "INFO"),\n    formatter=environ.get("LOG_FORMATTER", "cli")  # Use "logfmt" or "json" in k8s\n)\n\ninit_tracing_basic(\n    endpoint=environ.get("TRACING_PATH", "http://localhost:4317"),\n    attributes={\n        "environment": environ.get("ENVIRONMENT", "localdev"),\n        "service.name": "myservice",\n    }\n)\n\n# Add other instrumentors here, like:\n# RequestsInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n#     "service.name": "requests",\n# }))\n```\n\n### Starlette (with uvicorn)\n\n```python\nfrom os import environ\n\nfrom troncos.frameworks.starlette.uvicorn import init_uvicorn_observability\nfrom troncos.logs import init_logging_basic\nfrom troncos.traces import init_tracing_basic\n\ninit_logging_basic(\n    level=environ.get("LOG_LEVEL", "INFO"),\n    formatter=environ.get("LOG_FORMATTER", "cli")  # Use "logfmt" or "json" in k8s\n)\n\ninit_tracing_basic(\n    endpoint=environ.get("TRACING_PATH", "http://localhost:4317"),\n    attributes={\n        "environment": environ.get("ENVIRONMENT", "localdev"),\n        "service.name": "myservice",\n    }\n)\n\n# Add other instrumentors here, like:\n# RequestsInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n#     "service.name": "requests",\n# }))\n\napp = ...  # Setup your app\n\ninit_uvicorn_observability(\n    app=app,\n    log_access_ignored_paths=["/health", "/metrics"],  # Do not log these requests\n)\n```\n\n### Django (with gunicorn)\n\nTo set up tracing you have to set up some gunicorn hooks. Create a `gunicorn/config.py` file in your project:\n\n```python\nfrom os import environ\n\nfrom troncos.frameworks.gunicorn import post_request_trace, pre_request_trace\nfrom troncos.traces import init_tracing_basic \n\n\ndef post_fork(server, worker):\n    init_tracing_basic(\n      endpoint=environ.get("TRACING_PATH", "http://localhost:4317"),\n      attributes={\n        "pid": worker.pid,\n        "environment": environ.get("ENVIRONMENT", "localdev"),\n        "service.name": "myservice",\n      }\n    )\n\n    # Add other instrumentors here, like:\n    # DjangoInstrumentor().instrument()\n    #\n    # Psycopg2Instrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    #     "service.name": "psycopg2",\n    # }))\n\ndef pre_request(worker, req):\n    pre_request_trace(worker, req)\n\n\ndef post_request(worker, req, environ, resp):\n    post_request_trace(worker, req, environ, resp)\n```\n\nThen when running gunicorn, specify what config file to use:\n\n```console\ngunicorn myapp.wsgi:application --config python:myapp.gunicorn.config ...\n```\n\nYou have to manually configure logging in your `settings.py`. You should adhere to the principle described in [the logging section](#logging).\n\nMake sure that you add the `TraceIdFilter` to all handlers. Your logging configuration should look roughly like this:\n\n```python\nfrom os import environ\n\nLOGGING = {\n    "version": 1,\n    "disable_existing_loggers": True,\n    "filters": {\n        "trace_id": {"()": "troncos.logs.filters.TraceIdFilter"},\n    },\n    "formatters": {\n        "cli": {"()": "troncos.logs.formatters.PrettyFormatter"},\n        "json": {"()": "troncos.logs.formatters.JsonFormatter"},\n        "logfmt": {"()": "troncos.logs.formatters.LogfmtFormatter"},\n    },\n    "handlers": {\n        "console": {\n            "class": "logging.StreamHandler",\n            "formatter": environ.get("LOG_FORMATTER", "logfmt"),\n            "filters": ["trace_id"],\n        }\n    },\n    "loggers": {\n        "interno": {"handlers": ["console"], "level": environ.get("LOG_LEVEL", "INFO")},\n        "django": {"handlers": ["console"], "level": environ.get("LOG_LEVEL", "INFO")},\n        "django.server": {\n            "handlers": ["console"],\n            "level": environ.get("LOG_LEVEL", "INFO"),\n            "propagate": False,\n        },\n        "gunicorn.error": {\n            "handlers": ["console"], \n            "level": environ.get("LOG_LEVEL", "INFO")\n        },\n    },\n}\n```\n\n## Logging\n\nMore often then not, you want all loggers to propagate their records to the `root` logger and make the `root` logger handle everything. Depending on your project, this might require some additional configuration. Looking at the [python logging flow](https://docs.python.org/3/howto/logging.html#logging-flow) can help you understand how child loggers can propagate records to the `root` logger. Note that propagating to `root` is the default behaviour.\n\nThere is a nice helper function that will print all loggers in troncos called `print_loggers`:\n\n```python\nfrom troncos.logs import print_loggers\nprint_loggers(verbose=False)  # To visualize loggers\n```\n\nAfter calling the `init_logging_basic` function in a simple project you should see something like this printed `print_loggers`:\n\n```console\nLoggers:\n[ root                 ] logging.RootLogger LEVEL: 20 PROPAGATE: True\n  └ HANDLER logging.StreamHandler  LEVEL: 20\n    └ FILTER troncos.logs.filters.TraceIdFilter\n    └ FORMATTER troncos.logs.formatters.PrettyFormatter\n```\n\nSo in general, after the initial setup you can use any logger and that will propagate the log record to root:\n\n```python\nimport logging\n\nlogging.getLogger("my.random.logger").info("Root will handle this record")\n```\n\n### Structlog\n\nTo include traces in your structlog logs, add this processor to your configuration.\n\n> **NOTE**: This only adds trace information to your logs if you have set up tracing in your project.\n\n```python\nimport structlog\n\nfrom troncos.frameworks.structlog.processors import trace_injection_processor\n\nstructlog.configure(\n    processors=[\n       trace_injection_processor,\n    ],\n)\n```\n\n## Tracing\n\nAfter initializing tracing in your project you can use different methods to trace your code.\n\n### trace_function\n\nThis decorator adds tracing to a function. You can supply a tracer provider, if none is supplied, the global tracer provider will be used:\n\n```python\nfrom troncos.traces.decorate import trace_function\nfrom troncos.traces import init_tracing_provider\n\ncustom_provider = init_tracing_provider(attributes={\n  "service.name": "my_custom_provider",\n})\n\n@trace_function\ndef myfunc1():\n    return "This will be traced"\n\n@trace_function(tracer_provider=custom_provider)\ndef myfunc2():\n    return "This will be traced using a custom provider"\n```\n\n### trace_block\n\nTrace using a with statement. You can supply a tracer provider, if none is supplied, the global tracer provider will be used.\n\n```python\nfrom troncos.traces.decorate import trace_block\n\nwith trace_block(name="my block", attributes={"some": "attribute"}):\n    print("... do something ...")\n```\n\n### trace_class\n\nThis decorator adds a tracing decorator to every method of the decorated class. If you don\'t want some methods to be traced, you can add the [trace_ignore](#trace_ignore) decorator to them. You can supply a tracer provider, if none is supplied, the global tracer provider will be used:\n\n```python\nfrom troncos.traces.decorate import trace_class, trace_ignore\nfrom troncos.traces import init_tracing_provider\n\ncustom_provider = init_tracing_provider(attributes={\n    "service.name": "my_custom_provider",\n})\n\n@trace_class\nclass MyClass1:\n\n    def m1(self):\n        return "This will be traced"\n\n    @trace_ignore\n    def m2(self):\n        return "This will not traced"\n\n\n@trace_class(tracer_provider=custom_provider)\nclass MyClass2:\n\n    def m3(self):\n        return "This will be traced using a custom provider"\n```\n\n### trace_module\n\nThis function adds a tracing decorator to every function of the calling module. If you don\'t want some functions to be traced, you can add the [trace_ignore](#trace_ignore) decorator to them. You can supply a tracer provider, if none is supplied, the global tracer provider will be used:\n\n```python\nfrom troncos.traces.decorate import trace_ignore, trace_module\n\ndef my_function():\n    return "This func will be traced"\n\n@trace_ignore\ndef my_function():\n    return "This func will not be traced"\n\ntrace_module()\n```\n\n### trace_ignore\n\nA decorator that will make [trace_class](#trace_class) and [trace_module](#trace_module) ignore the decorated function/method.\n\n### Other instrumentors for tracing\n\nYou can add extra instrumentors to you app for even more tracing. You have to install the relevant packages yourself.\n\n```python\nfrom troncos.traces import init_tracing_provider\n\nDjangoInstrumentor().instrument()\n\nPsycopg2Instrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "psycopg2",\n}))\n\nRedisInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "redis",\n}))\n\nCeleryInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "celery",\n}))\n\nElasticsearchInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "elasticsearch",\n}))\n\nGrpcInstrumentorClient().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "grpc",\n}))\n\nRequestsInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "requests",\n}))\n\nHTTPXClientInstrumentor().instrument(tracer_provider=init_tracing_provider(attributes={\n    "service.name": "requests",  # Async requests\n}))\n```\n\n## Trace Propagation\n\nIf you want to propagate your trace to the next service, you need to send/receive the `traceparent` header with your requests/message. Here are examples on how to do that.\n\n### Send context\n\n#### Requests\n\nIn general, if you have the `RequestsInstrumentor` setup you do not have to think about this. If you are not using that for some reason, you can propagate with this method:\n\n```python\nimport requests\nfrom troncos.frameworks.requests import traced_session\n\n# Using a new session\nwith traced_session() as s:\n    response = s.get("http://postman-echo.com/get")\n\n\n# Using an old session\nmy_session = requests.session()\nwith traced_session(my_session) as s:\n    response = s.get("http://postman-echo.com/get")\n```\n\n#### Manually\n\n```python\nfrom troncos.traces.propagation import get_propagation_value\n\n# Get traceparent\ntraceparent = get_propagation_value()\n\n# Send it somewhere\n```\n\nor\n\n```python\nfrom troncos.traces.propagation import add_context_to_dict\n\nsome_dict = {}\n\n# Add traceparent to dict\nadd_context_to_dict(some_dict)\n\n# Send it somewhere\n```\n\n### Receive context\n\n#### Using troncos middleware\n\nTroncos defines middleware for some frameworks that does this automatically for you. If your framework is missing in troncos, please create an issue or PR.\n\n#### Manually\n\n```python\nfrom troncos.traces.propagation import get_context_from_dict\nfrom opentelemetry.trace import get_tracer\n\nsome_dict = ...\ncontext = get_context_from_dict(some_dict)\n\nwith get_tracer(__name__).start_as_current_span(\n        "span.name",\n        attributes={"some": "attrs"},\n        context=context,\n):\n    print("... do something ...")\n```',
    'author': 'Guðmundur Björn Birkisson',
    'author_email': 'gudmundur.birkisson@oda.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/kolonialno/troncos',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.10,<4.0',
}


setup(**setup_kwargs)
