Skip to content

Commit

Permalink
Merge pull request #2297 from camptocamp/debug-sqlalchemylogger
Browse files Browse the repository at this point in the history
Fix parsing of the ini file to setup sqlalchemylogger
  • Loading branch information
ger-benjamin authored Jun 17, 2024
2 parents b88f762 + ca728e7 commit 791785c
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 33 deletions.
7 changes: 7 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Release 6.1

- The `handlers` in the `.ini` files don't support `args` anymore. You must use `kwargs`
arguments. Example `args = (sys.stdout,)` becomes `kwargs = {'stream': 'ext://sys.stdout'}`.
- SqlAlchemy logger must now be instantiated by your app's `main` method and not by your
`.ini` file. Read the example in the sqlalchemylogger folder.

## Release 6.0

- The stats will not anymore be published on StatsD, use Prometheus client instead.
Expand Down
3 changes: 1 addition & 2 deletions acceptance_tests/app/c2cwsgiutils_app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from c2cwsgiutils_app import models
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPInternalServerError

import c2cwsgiutils.pyramid
from c2cwsgiutils import broadcast, db
from c2cwsgiutils.health_check import HealthCheck, JsonCheckException

from c2cwsgiutils_app import models


def _failure(_request):
raise HTTPInternalServerError("failing check")
Expand Down
3 changes: 1 addition & 2 deletions acceptance_tests/app/c2cwsgiutils_app/get_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import psycopg2
import transaction
from c2cwsgiutils_app import models

import c2cwsgiutils.db
import c2cwsgiutils.setup_process

from c2cwsgiutils_app import models


def _fill_db():
for db, value in (("db", "master"), ("db_slave", "slave")):
Expand Down
3 changes: 1 addition & 2 deletions acceptance_tests/app/c2cwsgiutils_app/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import prometheus_client
import requests
from c2cwsgiutils_app import models
from pyramid.httpexceptions import (
HTTPBadRequest,
HTTPForbidden,
Expand All @@ -12,8 +13,6 @@

from c2cwsgiutils import sentry, services

from c2cwsgiutils_app import models

_PROMETHEUS_TEST_COUNTER = prometheus_client.Counter("test_counter", "Test counter")
_PROMETHEUS_TEST_GAUGE = prometheus_client.Gauge("test_gauge", "Test gauge", ["value", "toto"])
_PROMETHEUS_TEST_SUMMARY = prometheus_client.Summary("test_summary", "Test summary")
Expand Down
4 changes: 2 additions & 2 deletions acceptance_tests/app/models_graph.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
from c2cwsgiutils.models_graph import generate_model_graph

from c2cwsgiutils_app import models

from c2cwsgiutils.models_graph import generate_model_graph


def main():
generate_model_graph(models)
Expand Down
4 changes: 2 additions & 2 deletions acceptance_tests/app/production.ini
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ qualname = c2cwsgiutils_app

[handler_console]
class = logging.StreamHandler
args = (sys.stdout,)
kwargs = {'stream': 'ext://sys.stdout'}
level = NOTSET
formatter = generic

[handler_json]
class = c2cwsgiutils.pyramid_logging.JsonLogHandler
args = (sys.stdout,)
kwargs = {'stream': 'ext://sys.stdout'}
level = NOTSET
formatter = generic

Expand Down
19 changes: 11 additions & 8 deletions c2cwsgiutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ast
import configparser
import logging
import os
import re
import sys
from configparser import SectionProxy
from typing import Any
Expand Down Expand Up @@ -32,23 +32,26 @@ def get_config_defaults() -> dict[str, str]:
def _create_handlers(config: configparser.ConfigParser) -> dict[str, Any]:
handlers = [k.strip() for k in config["handlers"]["keys"].split(",")]
d_handlers: dict[str, Any] = {}
stream_re = re.compile(r"\((.*?),\)")
for hh in handlers:
block = config[f"handler_{hh}"]
stream_match = stream_re.match(block["args"])
if stream_match is None:
raise Exception(f"Could not parse args of handler {hh}") # pylint: disable=broad-exception-raised
args = stream_match.groups()[0]
if "args" in block:
raise ValueError(f"Can not parse args of handlers {hh}, use kwargs instead.")
c = block["class"]
if "." not in c:
# classes like StreamHandler does not need the prefix in the ini so we add it here
c = f"logging.{c}"
conf = {
"class": c,
"stream": f"ext://{args}", # like ext://sys.stdout
}
if "level" in block:
conf["level"] = block["level"]
if "formatter" in block:
conf["formatter"] = block["formatter"]
if "filters" in block:
conf["filters"] = block["filters"]
if "kwargs" in block:
kwargs = ast.literal_eval(block["kwargs"])
conf.update(kwargs)
d_handlers[hh] = conf
return d_handlers

Expand All @@ -57,7 +60,7 @@ def _filter_logger(block: SectionProxy) -> dict[str, Any]:
out: dict[str, Any] = {"level": block["level"]}
handlers = block.get("handlers", "")
if handlers != "":
out["handlers"] = [block["handlers"]]
out["handlers"] = [k.strip() for k in block["handlers"].split(",")]
return out


Expand Down
43 changes: 30 additions & 13 deletions c2cwsgiutils/sqlalchemylogger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,39 @@ This module is used to ship logging records to an SQL database.

Currently only `sqlite` and `postgres_psycopg2` are fully supported.

To add the logger in a pyramid ini file use something like:
To add the handler, setup it directly in your app's main function. You
can add it to an existing logger (setup in you `.ini` file),
or create a new logger by calling the `logging.getlogger` method.

```python
import logging
from c2cwsgiutils.sqlalchemylogger.handlers import SQLAlchemyHandler

def _setup_sqlalchemy_logger():
"""
Setup sqlalchemy logger.
"""
logger = logging.getLogger("A_LOGGER")
handler = SQLAlchemyHandler(
sqlalchemy_url={
# "url": "sqlite:///logger_db.sqlite3",
"url": "postgresql://postgres:password@localhost:5432/test",
"tablename": "test",
"tableargs": {"schema": "xyz"},
},
does_not_contain_expression="curl",
)
logger.addHandler(handler)

def main(_, **settings):
_setup_sqlalchemy_logger ()
...
```
[handlers]
keys = sqlalchemy_logger
[handler_sqlalchemy_logger]
class = c2cwsgiutils.sqlalchemylogger.handlers.SQLAlchemyHandler
#args = ({'url':'sqlite:///logger_db.sqlite3','tablename':'test'},'curl')
args = ({'url':'postgresql://postgres:password@localhost:5432/test','tablename':'test','tableargs': {'schema':'xyz'}},'curl')
level = NOTSET
formatter = generic
propagate = 0
```

if the credentials given in `args = ` section are sufficient, the handler will
Do not set up this sqlalchemy logger in you `.ini` file directly.
It won't work (multi process issue).

if the given credentials are sufficient, the handler will
create the DB, schema and table it needs directly.

In the above example the second parameter provided `'curl'` is a negative
Expand Down
4 changes: 2 additions & 2 deletions production.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ qualname = c2cwsgiutils

[handler_console]
class = logging.StreamHandler
args = (sys.stdout,)
kwargs = {'stream': 'ext://sys.stdout'}
level = NOTSET
formatter = generic

[handler_json]
class = c2cwsgiutils.pyramid_logging.JsonLogHandler
args = (sys.stdout,)
kwargs = {'stream': 'ext://sys.stdout'}
level = NOTSET
formatter = generic

Expand Down

0 comments on commit 791785c

Please sign in to comment.