SAP / cf-python-logging-support

Logging library for python applications deployed on SAP Cloud Platform - CloudFoundry environment
Apache License 2.0
17 stars 11 forks source link
open-source

Python logging library to emit JSON logs in a SAP CloudFoundry environment.

.. image:: https://api.reuse.software/badge/github.com/SAP/cf-python-logging-support :target: https://api.reuse.software/info/github.com/SAP/cf-python-logging-support

This is a collection of support libraries for Python applications running on Cloud Foundry that serve two main purposes: provide (a) means to emit structured application log messages and (b) instrument web applications of your application stack to collect request metrics.

For details on the concepts and log formats, please look at the sibling project for java logging support <https://github.com/SAP/cf-java-logging-support>__.

Features

  1. Lightweight, no dependencies. Support of Python 2.7 & 3.5.

  2. Compatible with the Python logging module. Minimal configuration needed.

  3. Emits JSON logs (format details <https://github.com/SAP/cf-java-logging-support/tree/master/cf-java-logging-support-core/beats>__).

  4. Supports correlation-id.

  5. Supports request instrumentation. Built in support for:

    • Flask 0.1x <http://flask.pocoo.org/>__
    • Sanic 0.5.x <https://github.com/channelcat/sanic>__
    • Falcon <https://falconframework.org/>__
    • Django <https://www.djangoproject.com/>__
    • Extensible to support others
  6. Includes CF-specific information (space id, app id, etc.) to logs.

  7. Supports adding extra properties to JSON log object.

Installation

Install the package with pip:

::

pip install sap_cf_logging

Usage

Setting up your application


Logging library needs to be initialized. Depending on you application type, different initialization
is used. You should usually do this in your application entrypoint.

For CLI applications you just need to call ``cf_logging.init()`` *once* to configure the library.
The library will try to configure future loggers to emit logs in JSON format.

If you are using one of the supported frameworks, check the `Configuration <#configuration>`__
section to see how to configure it.

**Setting up the CloudFoundry environment**

In order for your logs to appear in the Kibana dashboard, you have to create an **application-logs**
service instance and bind it to your application.

Configuration

After installation use the following guide to configure the Python cf logging library.

Flask ^^^^^

First import the cf_logging library and setup Flask logging on the application.

.. code:: python

from sap.cf_logging import flask_logging

app = flask.Flask(__name__)
flask_logging.init(app, logging.INFO)

Next use Python’s logging library

.. code:: python

@app.route('/')
def root_route():
    logger = logging.getLogger('my.logger')
    logger.info('Hi')

    return 'ok'

Note the logs generated by the application

Sanic ^^^^^

.. code:: python

import sanic
import logging

from sanic.response import HTTPResponse
from sap.cf_logging import sanic_logging
from sap.cf_logging.core.constants import REQUEST_KEY

app = sanic.Sanic('test.cf_logging')
sanic_logging.init(app)

@app.route('/')
async def two(request):
    extra = {REQUEST_KEY: request}
    logging.getLogger('my.logger').debug('Hi', extra = extra)
    return HTTPResponse(body='ok')

Note: With Sanic you need to pass the request with an extra parameter in the logging API. This is needed in order to get the correlation_id generated at the beginning of the request or fetched from the HTTP headers.

Falcon ^^^^^^

.. code:: python

import falcon from sap.cf_logging import falcon_logging from sap.cf_logging.core.constants import REQUEST_KEY

class Resource: def on_get(self, req, resp): extra = {REQUEST_KEY: req} logging.getLogger('my.logger').log('Resource requested', extra=extra) resp.media = {'name': 'Cloud Foundry'}

app = falcon.API(middleware=[ falcon_logging.LoggingMiddleware() ]) app.add_route('/resource', Resource()) falcon_logging.init(app)

Django ^^^^^^

.. code:: bash

django-admin startproject example

.. code:: python

# example/settings.py

    MIDDLEWARES = [
        # ...,
        'sap.cf_logging.django_logging.LoggingMiddleware'
    ]

# example/wsgi.py

    # ...
    from sap.cf_logging import django_logging

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sap_logtesting.settings")
    django_logging.init()

    # ...

Create a new app

.. code:: bash

python manage.py startapp example_app

.. code:: python

# example_app/views.py

    import logging

    from django.http import HttpResponse
    from sap.cf_logging.core.constants import REQUEST_KEY

    def index(request):
        extra = {REQUEST_KEY: request}
        logger = logging.getLogger('my.logger')
        logger.info("Resource requested", extra=extra)
        return HttpResponse("ok")

# example_app/urls.py

    from django.conf.urls import url

    from . import views

    urlpatterns = [
        url('^$', views.index)
    ]

# example/urls.py

    from django.contrib import admin
    from django.conf.urls import url, include

    urlpatterns = [
        url('admin/', admin.site.urls),
        url('example/', include('example_app.urls'))
    ]

General ^^^^^^^

.. code:: python

import logging
from sap import cf_logging

cf_logging.init()

logger = logging.getLogger("cli.logger")
logger.info('hi')

Notes: All loggers set up and created before the initialization of the Cloud Foundry logging library will be left untouched. When using Flask and Sanic with the logging library before and after request middleware is attached, and it will capture response times for each request.

Custom Fields """""""""""""

To use custom fields. Pass a dictionary property custom_fields to the initialize method:

.. code:: python

import logging
from sap import cf_logging
cf_logging.init(custom_fields={"foo": "default", "bar": None})

Here we mark the two fields: foo and bar as custom_fields. Logging with:

.. code:: python

logging.getLogger('my.logger').debug('Hi')

The property foo will be output as a custom field with a value "default". The property bar will not be logged, as it does not have a value.

To log bar, provide a value when logging:

.. code:: python

logging.getLogger('my.logger').debug('Hi', extra={"bar": "new_value"})

It is also possible to log foo with a different value:

.. code:: python

logging.getLogger('my.logger').debug('Hi', extra={"foo": "hello"})

Setting and getting correlation ID """"""""""""""""""""""""""""""""""

When using cf_logging in a web application you don't need to set the correlation ID, because the logging library will fetch it from the HTTP headers and set it. For non web applications you could set the correlation ID manually, so that the log entries can be filtered later on based on the correlation_id log property. In this case the correlation ID is kept in a thread local variable and each thread should set its own correlation ID.

Setting and getting the correlation_id can be done via:

.. code:: python

cf_logging.FRAMEWORK.context.get_correlation_id()
cf_logging.FRAMEWORK.context.set_correlation_id(value)

If you need to get the correlation ID in a web application, take into account the framework you are using. In async frameworks like Sanic and Falcon the context is stored into the request object and you need to provide the request to the call:

.. code:: python

cf_logging.FRAMEWORK.context.get_correlation_id(request)

Logging sensitive data ^^^^^^^^^^^^^^^^^^^^^^

The logging library does not log sensitive fields by default. Those fields are replaced with 'redacted' instead of their original content. The following fields are considered sensitive data: remote_ip, remote_host, remote_port, x_forwarded_for, remote_user, referer. Logging of all or some of these fields can be activated by setting the following environment variables:

+-----------------------------------+-----------+------------------------------------------------------------------------+ | Environment variable | Value | Enables sensitive field | +===================================+===========+========================================================================+ | LOG_SENSITIVE_CONNECTION_DATA | true | remote_ip, remote_host, remote_port, x_forwarded_for | +-----------------------------------+-----------+------------------------------------------------------------------------+ | LOG_REMOTE_USER | true | remote_user | +-----------------------------------+-----------+------------------------------------------------------------------------+ | LOG_REFERER | true | referer | +-----------------------------------+-----------+------------------------------------------------------------------------+

This behavior matches the corresponding mechanism in the CF Java Logging Support library <https://github.com/SAP/cf-java-logging-support/wiki/Overview#logging-sensitive-user-data>__.

Examples



For more examples please see the tests within the ``./tests/`` directory.

Requirements
------------

No external requirements are needed to run the package.

Limitations
-----------

NA

Known Issues
------------

NA

How to obtain support
---------------------

Please open an issue on the github page.

Contributing
------------

Please create a pull request and briefly describe the nature of the change. Please submit a test
case along with your pull request.

To-Do (upcoming changes)
------------------------

NA

Changelog
---------

See `CHANGELOG file <https://github.com/SAP/cf-python-logging-support/blob/master/CHANGELOG.md>`__.

License
-------

Copyright (c) 2017-2021 SAP SE or an SAP affiliate company and cf-python-logging-support contributors.  Please see our `LICENSE file <https://github.com/SAP/cf-python-logging-support/blob/master/LICENSE>`__ for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available `via the REUSE tool <https://api.reuse.software/info/github.com/SAP/cf-python-logging-support>`__.