Skip to content

Commit 31af20d

Browse files
authored
Merge pull request #25 from madkote/dev_0.14
0.14.x - logging improvements
2 parents 3758af5 + 523b3c0 commit 31af20d

25 files changed

+444
-297
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changes
2+
## 0.14.0 (2025-07-10)
3+
- `[feature]` `orjson` logging format for more performance
4+
- `[feature]` `logging_memory_*` buffered logging for more performance
25
## 0.13.2 (2025-02-12)
36
- `[fix]` `examples` field in `Control` schema
47
## 0.13.1 (2024-06-03)

ENV

Whitespace-only changes.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 RES
3+
Copyright (c) 2025 RES
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Makefile

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ clean: clean-build clean-docker clean-pyc clean-pycache
4949

5050
install: clean
5151
@echo $@
52-
pip install --no-cache-dir -U pip setuptools twine wheel
52+
pip install --no-cache-dir -U build pip setuptools twine wheel
5353
pip install --no-cache-dir -U --force-reinstall -r requirements.txt
5454
rm -rf build *.egg-info
5555
pip uninstall ${PYPACKAGE} -y || true
@@ -62,20 +62,15 @@ demo-app: clean
6262
@echo $@
6363
uvicorn scripts.demo_app:app
6464

65-
flake: clean
65+
lint: clean
6666
@echo $@
67-
flake8 --statistics --ignore E252 ${PYPACKAGE} tests scripts setup.py
67+
pdm run lint
6868

69-
bandit: clean
69+
test-pdm:
7070
@echo $@
71-
bandit -r ${PYPACKAGE}/ scripts/ demo.py setup.py
72-
bandit -s B101 -r tests/
71+
pdm run test
7372

74-
test-unit-pytest:
75-
@echo $@
76-
python -m pytest -v -x tests/ --cov=${PYPACKAGE}
77-
78-
test-unit: clean flake bandit docker-up-test test-unit-pytest docker-down-test
73+
test-unit: clean docker-up-test test-pdm docker-down-test
7974
@echo $@
8075

8176
test-toxtox:
@@ -88,14 +83,18 @@ test-tox: clean docker-up-test test-toxtox docker-down-test
8883
test: test-unit
8984
@echo $@
9085

91-
test-all: clean flake bandit docker-up-test test-unit-pytest test-toxtox docker-down-test
86+
test-all: clean docker-up-test test-pdm test-toxtox docker-down-test
9287
@echo $@
9388

94-
pypi-build: clean test-all
95-
@echo $@
96-
python setup.py sdist bdist_wheel
89+
pypi-build-twine:
9790
twine check dist/*
9891

92+
pypi-build-wheel:
93+
pdm run build
94+
95+
pypi-build: clean test-all pypi-build-wheel pypi-build-twine
96+
@echo $@
97+
9998
pypi-upload-test:
10099
@echo $@
101100
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*

demo.py

Lines changed: 189 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ async def coro(name, timeout):
7272
s = await fastapi_plugins.scheduler_plugin()
7373
# import random
7474
for i in range(10):
75-
await s.spawn(coro(str(i), i/10))
76-
# await s.spawn(coro(str(i), i/10 + random.choice([0.1, 0.2, 0.3, 0.4, 0.5]))) # nosec B311
75+
await s.spawn(coro(str(i), i / 10))
76+
# await s.spawn(coro(str(i), i/10 + random.choice([0.1, 0.2, 0.3, 0.4, 0.5]))) # nosec B311 # noqa
7777
# print('----------')
7878
print('- sleep', 5)
7979
await asyncio.sleep(5.0)
@@ -148,18 +148,18 @@ async def coro(con, name, timeout):
148148
num_sleep = 0.25
149149

150150
print('- play')
151-
l = await fastapi_plugins.log_plugin()
151+
logger = await fastapi_plugins.log_plugin()
152152
c = await fastapi_plugins.redis_plugin()
153153
s = await fastapi_plugins.scheduler_plugin()
154154
for i in range(num_jobs):
155-
await s.spawn(coro(c, str(i), i/10))
156-
l.info('- sleep %s' % num_sleep)
155+
await s.spawn(coro(c, str(i), i / 10))
156+
logger.info('- sleep %s' % num_sleep)
157157
# print('- sleep', num_sleep)
158158
await asyncio.sleep(num_sleep)
159-
l.info('- check')
159+
logger.info('- check')
160160
# print('- check')
161161
for i in range(num_jobs):
162-
l.info('%s == %s' % (i, await c.get(str(i))))
162+
logger.info('%s == %s' % (i, await c.get(str(i))))
163163
# print(i, '==', await c.get(str(i)))
164164
finally:
165165
print('- terminate')
@@ -186,9 +186,9 @@ class CustomLoggingSettings(fastapi_plugins.LoggingSettings):
186186

187187
class CustomLoggingPlugin(fastapi_plugins.LoggingPlugin):
188188
def _create_logger(
189-
self,
190-
name:str,
191-
config:pydantic_settings.BaseSettings=None
189+
self,
190+
name: str,
191+
config: pydantic_settings.BaseSettings = None
192192
) -> logging.Logger:
193193
import sys
194194
handler = logging.StreamHandler(stream=sys.stderr)
@@ -227,18 +227,18 @@ class AppSettings(
227227
num_sleep = 0.25
228228

229229
print('- play')
230-
l = await mylog_plugin()
230+
logger = await mylog_plugin()
231231
c = await fastapi_plugins.redis_plugin()
232232
s = await fastapi_plugins.scheduler_plugin()
233233
for i in range(num_jobs):
234-
await s.spawn(coro(c, str(i), i/10))
235-
l.info('- sleep %s' % num_sleep)
234+
await s.spawn(coro(c, str(i), i / 10))
235+
logger.info('- sleep %s' % num_sleep)
236236
# print('- sleep', num_sleep)
237237
await asyncio.sleep(num_sleep)
238-
l.info('- check')
238+
logger.info('- check')
239239
# print('- check')
240240
for i in range(num_jobs):
241-
l.info('%s == %s' % (i, await c.get(str(i))))
241+
logger.info('%s == %s' % (i, await c.get(str(i))))
242242
# print(i, '==', await c.get(str(i)))
243243
finally:
244244
print('- terminate')
@@ -248,6 +248,104 @@ class AppSettings(
248248
print('---demo done')
249249

250250

251+
async def test_demo_orjson_log():
252+
async def coro(con, name, timeout):
253+
try:
254+
await con.set(name, '...')
255+
print('> sleep', name, timeout)
256+
await asyncio.sleep(timeout)
257+
await con.set(name, 'done')
258+
print('---> sleep done', name, timeout)
259+
except asyncio.CancelledError as e:
260+
print('coro cancelled', name)
261+
raise e
262+
263+
print('--- do demo')
264+
app = fastapi_plugins.register_middleware(fastapi.FastAPI())
265+
config = AppSettings(logging_style=fastapi_plugins.LoggingStyle.logorjson)
266+
267+
await fastapi_plugins.log_plugin.init_app(app, config, name=__name__)
268+
await fastapi_plugins.log_plugin.init()
269+
await fastapi_plugins.redis_plugin.init_app(app=app, config=config)
270+
await fastapi_plugins.redis_plugin.init()
271+
await fastapi_plugins.scheduler_plugin.init_app(app=app, config=config)
272+
await fastapi_plugins.scheduler_plugin.init()
273+
274+
try:
275+
num_jobs = 10
276+
num_sleep = 0.25
277+
278+
print('- play')
279+
logger = await fastapi_plugins.log_plugin()
280+
c = await fastapi_plugins.redis_plugin()
281+
s = await fastapi_plugins.scheduler_plugin()
282+
for i in range(num_jobs):
283+
await s.spawn(coro(c, str(i), i / 10))
284+
logger.info('- sleep %s' % num_sleep, extra=dict(bla='bla'))
285+
# print('- sleep', num_sleep)
286+
await asyncio.sleep(num_sleep)
287+
logger.info('- check')
288+
# print('- check')
289+
for i in range(num_jobs):
290+
logger.info('%s == %s' % (i, await c.get(str(i))))
291+
# print(i, '==', await c.get(str(i)))
292+
finally:
293+
print('- terminate')
294+
await fastapi_plugins.scheduler_plugin.terminate()
295+
await fastapi_plugins.redis_plugin.terminate()
296+
await fastapi_plugins.log_plugin.terminate()
297+
print('---demo done')
298+
299+
300+
async def test_demo_json_log():
301+
async def coro(con, name, timeout):
302+
try:
303+
await con.set(name, '...')
304+
print('> sleep', name, timeout)
305+
await asyncio.sleep(timeout)
306+
await con.set(name, 'done')
307+
print('---> sleep done', name, timeout)
308+
except asyncio.CancelledError as e:
309+
print('coro cancelled', name)
310+
raise e
311+
312+
print('--- do demo')
313+
app = fastapi_plugins.register_middleware(fastapi.FastAPI())
314+
config = AppSettings(logging_style=fastapi_plugins.LoggingStyle.logjson)
315+
316+
await fastapi_plugins.log_plugin.init_app(app, config, name=__name__)
317+
await fastapi_plugins.log_plugin.init()
318+
await fastapi_plugins.redis_plugin.init_app(app=app, config=config)
319+
await fastapi_plugins.redis_plugin.init()
320+
await fastapi_plugins.scheduler_plugin.init_app(app=app, config=config)
321+
await fastapi_plugins.scheduler_plugin.init()
322+
323+
try:
324+
num_jobs = 10
325+
num_sleep = 0.25
326+
327+
print('- play')
328+
logger = await fastapi_plugins.log_plugin()
329+
c = await fastapi_plugins.redis_plugin()
330+
s = await fastapi_plugins.scheduler_plugin()
331+
for i in range(num_jobs):
332+
await s.spawn(coro(c, str(i), i / 10))
333+
logger.info('- sleep %s' % num_sleep, extra=dict(bla='bla'))
334+
# print('- sleep', num_sleep)
335+
await asyncio.sleep(num_sleep)
336+
logger.info('- check')
337+
# print('- check')
338+
for i in range(num_jobs):
339+
logger.info('%s == %s' % (i, await c.get(str(i))))
340+
# print(i, '==', await c.get(str(i)))
341+
finally:
342+
print('- terminate')
343+
await fastapi_plugins.scheduler_plugin.terminate()
344+
await fastapi_plugins.redis_plugin.terminate()
345+
await fastapi_plugins.log_plugin.terminate()
346+
print('---demo done')
347+
348+
251349
async def test_memcached():
252350
print('---test memcached')
253351
from fastapi_plugins.memcached import MemcachedSettings
@@ -261,7 +359,7 @@ class MoreSettings(AppSettings, MemcachedSettings):
261359
config = MoreSettings()
262360
await memcached_plugin.init_app(app=app, config=config)
263361
await memcached_plugin.init()
264-
362+
265363
c = await memcached_plugin()
266364
print(await c.get(b'x'))
267365
print(await c.set(b'x', str(time.time()).encode()))
@@ -270,6 +368,51 @@ class MoreSettings(AppSettings, MemcachedSettings):
270368
print('---test memcached done')
271369

272370

371+
async def test_demo_buffered_log():
372+
async def coro(con, name, timeout):
373+
try:
374+
await con.set(name, '...')
375+
print('> sleep', name, timeout)
376+
await asyncio.sleep(timeout)
377+
await con.set(name, 'done')
378+
print('---> sleep done', name, timeout)
379+
except asyncio.CancelledError as e:
380+
print('coro cancelled', name)
381+
raise e
382+
383+
print('--- do demo')
384+
app = fastapi_plugins.register_middleware(fastapi.FastAPI())
385+
config = AppSettings(
386+
logging_style=fastapi_plugins.LoggingStyle.logjson,
387+
logging_memory_capacity=25
388+
)
389+
390+
await fastapi_plugins.log_plugin.init_app(app, config, name=__name__)
391+
await fastapi_plugins.log_plugin.init()
392+
await fastapi_plugins.redis_plugin.init_app(app=app, config=config)
393+
await fastapi_plugins.redis_plugin.init()
394+
await fastapi_plugins.scheduler_plugin.init_app(app=app, config=config)
395+
await fastapi_plugins.scheduler_plugin.init()
396+
397+
try:
398+
num_jobs = 10
399+
num_tasks = 30
400+
401+
logger = await fastapi_plugins.log_plugin()
402+
for c_job in range(num_jobs):
403+
print(f'----- JOB {c_job}')
404+
for c_task in range(num_tasks):
405+
logger.info(f'job={c_job} task={c_task} hello world')
406+
await asyncio.sleep(0.1)
407+
await asyncio.sleep(0.5)
408+
finally:
409+
print('- terminate')
410+
await fastapi_plugins.scheduler_plugin.terminate()
411+
await fastapi_plugins.redis_plugin.terminate()
412+
await fastapi_plugins.log_plugin.terminate()
413+
print('---demo done')
414+
415+
273416
# =============================================================================
274417
# ---
275418
# =============================================================================
@@ -301,21 +444,49 @@ def main_demo():
301444
loop = asyncio.get_event_loop()
302445
loop.run_until_complete(test_demo())
303446

447+
304448
def main_demo_custom_log():
305449
print(os.linesep * 3)
306450
print('=' * 50)
451+
print('= DEMO CUSTOM LOG')
307452
loop = asyncio.get_event_loop()
308453
loop.run_until_complete(test_demo_custom_log())
309454

310455

456+
def main_demo_json_log():
457+
print(os.linesep * 3)
458+
print('=' * 50)
459+
print('= DEMO JSON LOG')
460+
loop = asyncio.get_event_loop()
461+
loop.run_until_complete(test_demo_json_log())
462+
463+
464+
def main_demo_orjson_log():
465+
print(os.linesep * 3)
466+
print('=' * 50)
467+
print('= DEMO ORJSON LOG')
468+
loop = asyncio.get_event_loop()
469+
loop.run_until_complete(test_demo_orjson_log())
470+
471+
472+
def main_demo_buffered_log():
473+
print(os.linesep * 3)
474+
print('=' * 50)
475+
print('= DEMO BUFFERED LOG')
476+
loop = asyncio.get_event_loop()
477+
loop.run_until_complete(test_demo_buffered_log())
478+
479+
311480
if __name__ == '__main__':
312481
main_redis()
313482
main_scheduler()
314483
main_demo()
315484
main_demo_custom_log()
316-
#
485+
main_demo_json_log()
486+
main_demo_orjson_log()
487+
main_demo_buffered_log()
488+
317489
try:
318490
main_memcached()
319491
except Exception as e:
320492
print(type(e), e)
321-

docs/logger.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@ For details see below.
1010

1111
## Valid variables and values
1212
* `LOGGING_LEVEL` - verbosity level
13-
* any valid level provided by standard `logging` library (e.g. `10`, `20`, `30`, ...)
13+
* any valid level provided by standard `logging` library (e.g. `10`, `20`, `30`, ...)
1414
* `LOGGING_STYLE` - style/format of log records
1515
* `txt` - default `logging` format
16-
* `json` - JSON format
16+
* `json` - JSON format with standard `json`
17+
* `orjson` - JSON format with `orjson`
1718
* `logfmt` - `Logfmt` format (key, value)
1819
* `LOGGING_HANDLER` - Handler type for log entries.
1920
* `stdout` - Output log entries to `sys.stdout`.
2021
* `list` - Collect log entries in a queue, **for testing purposes only**.
2122
* `LOGGING_FMT` - logging format for default formatter, e.g. `"%(asctime)s %(levelname) %(message)s"`.
2223
**Note**: this parameter is only valid in conjuction with `LOGGING_STYLE=txt`.
24+
* `LOGGING_MEMORY_CAPACITY` - if greater then `0` enable buffered log record output
25+
* default is `0` - disabled.
26+
* a possible _good_ value for production can be `1024*100`
27+
* `LOGGING_MEMORY_FLUSH_LEVEL` - logging level to immediately flush logging buffer to the handler
28+
* any valid level provided by standard `logging` library (e.g. `10`, `20`, `30`, ...)
2329

2430
## Example
2531
### Application

0 commit comments

Comments
 (0)