# python ows-logger

## Table of Contents

- [Setup and usage](#setup-and-usage)
  - [Flask apps](#for-your-flask-python-application)
  - [FastAPI/ASGI apps](#for-your-fastapi-or-other-asgi-application)
  - [Non-Flask apps](#for-your-non-python-flask-application)
- Upgrading
  - [3.x](#upgrading-to-owslogger-3x)
  - [2.x](#upgrading-to-owslogger-2x)
- [Contributing](#contributing)

## Setup and usage

This logger is designed to send JSON in a format that matches our
OWS1 standard. This package is published to our internal Pypi server.
Installation is minimal:

```bash
pip install -i https://pypi.theorchard.io/pypi/ owslogger
```

Alternatively, put the following in your requirements file:

```text
-i https://pypi.theorchard.io/pypi/

owslogger
```

### For your Flask python application

#### Setup

This should be in your `api.py` file:

```python
from flask import Flask
from owslogger import flask_logger
import logging


app = Flask(__name__)
flask_logger.setup(
    app, config.ENVIRONMENT, config.LOGGER_NAME,
    config.LOGGER_LEVEL, config.SERVICE_NAME, config.SERVICE_VERSION)

# Your config vars might look something like this:
# ENVIRONMENT = os.environ.get('Environment', 'dev')
# LOGGER_LEVEL = logging.INFO
# LOGGER_NAME = 'logger-name'
# SERVICE_NAME = 'ows-great-service'
# SERVICE_VERSION = '1.0.0'
```

#### Usage

and in any of your other files in your Flask python application:

```python
import g

...

# You can also choose a different log level to use too (debug, warning, etc)
g.log.info('whatever you want to log')
```

### For your FastAPI or other ASGI application

Make sure to install `owslogger` with the asgi extra, e.g., `pip install owslogger[asgi]`.

#### Setup

This should be in your `api.py` file:

```python
import logging.config
from typing import Any

from fastapi import FastAPI
from starlette.middleware import Middleware
from starlette.requests import Request
import uvicorn

from owslogger.asgi import RequestLoggingMiddleware

# Set config settings
SERVICE_NAME = 'ows-demo'
SERVICE_VERSION = '0.1.0'
ENVIRONMENT = 'qa'

# Configure logging (for example using dictConfig)
logging.config.dictConfig(
    {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'json': {
                '()': 'owslogger.logger.DDJsonFormatter',
                'service_name': SERVICE_NAME,
                'service_version': SERVICE_VERSION,
                'env': ENVIRONMENT,
            }
        },
        'handlers': {
            'stream': {
                'level': 'INFO',
                'class': 'logging.StreamHandler',
                'formatter': 'json',
            },
        },
        'loggers': {
            '': {
                'handlers': ['stream'],
                'level': 'INFO',
            },
        }
    }
)

# Create FastAPI application
app = FastAPI(
    middleware=[
        Middleware(RequestLoggingMiddleware),
    ],
)

@app.get('/hello/')
async def index(request: Request) -> Any:
    """Hello endpoint."""
    return {'hello': 'world'}


if __name__ == '__main__':
    uvicorn.run(
        app, 
        host='0.0.0.0', 
        port=8000, 
        access_log=False,
        log_config=None,  # disable uvicorn logs in favour of custom config
    )
```

#### Using custom logger

To use a custom logger instance, pass it as an argument to `RequestLoggingMiddleware`:

```python
logger = logging.getLogger(__name__)

app = FastAPI(
    middleware=[
        Middleware(RequestLoggingMiddleware, logger=logger),
    ],
)
```

#### Exclude URL paths

To exclude unnecessary paths from being logged, use the `exclude_paths` argument:

```python
logger = logging.getLogger(__name__)

app = FastAPI(
    middleware=[
        Middleware(
            RequestLoggingMiddleware, 
            logger=logger, 
            exclude_paths=[
                "/openapi.json",
                "/docs",
                "/hello/"
            ],
        ),
    ],
)
```

### For your non-python Flask/ASGI application

#### Setup

```python
from owslogger import logger

# Global logger
app_logger = logger.setup(
    'environment', 'logger_name', logging.INFO, 'service_name', '1.0.0')

# Make it specific to a request by creating an adapter and adding the
# correlation id.
current_app_logger = logger.OwsLoggingAdapter(app_logger, {
    'correlation_id': 'correlation_id'
})

# If you just want to set the correlation id one time:
current_app_logger = logger.setup(
    'environment', 'logger_name', logging.INFO,
    'service_name', '1.0.0', correlation_id='correlation_id')
```

#### Usage

Continuing on from the setup, if you have access to that same `current_app_logger` then:

```python
current_app_logger.info('whatever you want to log here')
```

## Upgrading to Owslogger 3.x

The 3.x release adds support for Python 3.8 through 3.11, dropping official support for <=3.6. No code changes should be
necessary to upgrade from v2 to v3, but you will need to upgrade/rebalance your dependencies and reconfigure `uwsgi`
when applicable. Using `ddtrace~=1.0` is recommended. For lambdas, `datadog-lambda~=4.0` is recommended. For
applications using `uwsgi`, see [this page](https://ddtrace.readthedocs.io/en/stable/advanced_usage.html#uwsgi) for
recommendations for reconfiguring uwsgi to be compatible. Also note that `Flask>=2.3` appears to be incompatible.

## Upgrading to Owslogger 2.x

The 2.x release of owslogger contains a breaking change that removes the logger DSN as a required argument in favor of logging to stdout by default.
`dsn` is still available as an optional keyword argument if your application requires remote host logging.

To upgrade owslogger from 1.x to 2.x, refer to this checklist:

- Remove dsn as a positional argument to the setup function.
- Remove code references to any logger DSN environment variables.
- Remove the logger DSN environment variable from your application's terraform configuration.
- Upgrade to the latest terraform-fargate module to ship stdout/stderr logs to Datadog via fluentbit.

The latest release of owslogger also contains an override for the ddtrace python library's creation of a root logger with a default stream handler,
which caused application logs to appear "doubled" in Datadog due to logger propagation. If you do not want the root logger handlers list cleared
at runtime, you can set the `clear_handlers` argument to `False` in the setup function. If your application is manually patching ddtrace as a workaround,
you may prefer to wrap the uwsgi start command to enable automatic context propagation for your logs.

## Contributing

Be sure to publish the latest version of this package to Pypi when contributing. This
[Jenkins job](https://pipeline.theorchard.io/job/publish-pypi-package/) will bump the version, tag and publish the
package to Pypi.
