c2cwsgiutils 5.2.1__tar.gz → 5.2.1.dev197__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/PKG-INFO +168 -99
  2. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/README.md +140 -74
  3. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/__init__.py +12 -12
  4. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/acceptance/connection.py +5 -2
  5. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/acceptance/image.py +95 -3
  6. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/acceptance/package-lock.json +1933 -0
  7. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/acceptance/package.json +7 -0
  8. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/acceptance/print.py +3 -3
  9. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/acceptance/screenshot.js +62 -0
  10. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/acceptance/utils.py +14 -22
  11. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/auth.py +4 -4
  12. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/broadcast/__init__.py +15 -7
  13. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/broadcast/interface.py +3 -2
  14. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/broadcast/local.py +3 -2
  15. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/broadcast/redis.py +6 -5
  16. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/client_info.py +5 -5
  17. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/config_utils.py +2 -1
  18. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/db.py +20 -11
  19. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/db_maintenance_view.py +2 -1
  20. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/debug/_listeners.py +7 -6
  21. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/debug/_views.py +11 -10
  22. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/debug/utils.py +5 -5
  23. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/health_check.py +72 -73
  24. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/index.py +90 -105
  25. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/loader.py +3 -3
  26. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/logging_view.py +3 -2
  27. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/models_graph.py +4 -4
  28. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/prometheus.py +184 -0
  29. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/pyramid.py +4 -2
  30. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/pyramid_logging.py +2 -1
  31. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/redis_stats.py +13 -11
  32. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/redis_utils.py +11 -5
  33. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/request_tracking/__init__.py +36 -30
  34. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/scripts/genversion.py +4 -4
  35. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/scripts/stats_db.py +92 -60
  36. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sentry.py +2 -1
  37. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/setup_process.py +12 -16
  38. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sql_profiler/_impl.py +3 -2
  39. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/_models.py +2 -2
  40. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/handlers.py +6 -6
  41. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/static/favicon-16x16.png +0 -0
  42. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/static/favicon-32x32.png +0 -0
  43. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/stats_pyramid/__init__.py +7 -11
  44. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/stats_pyramid/_db_spy.py +14 -11
  45. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/stats_pyramid/_pyramid_spy.py +27 -21
  46. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/templates/index.html.mako +50 -0
  47. c2cwsgiutils-5.2.1.dev197/c2cwsgiutils/version.py +90 -0
  48. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/pyproject.toml +74 -77
  49. c2cwsgiutils-5.2.1/c2cwsgiutils/acceptance/composition.py +0 -129
  50. c2cwsgiutils-5.2.1/c2cwsgiutils/metrics.py +0 -110
  51. c2cwsgiutils-5.2.1/c2cwsgiutils/prometheus.py +0 -66
  52. c2cwsgiutils-5.2.1/c2cwsgiutils/scripts/check_es.py +0 -130
  53. c2cwsgiutils-5.2.1/c2cwsgiutils/stats.py +0 -344
  54. c2cwsgiutils-5.2.1/c2cwsgiutils/stats_pyramid/_views.py +0 -16
  55. c2cwsgiutils-5.2.1/c2cwsgiutils/version.py +0 -57
  56. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/LICENSE +0 -0
  57. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/acceptance/__init__.py +0 -0
  58. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/broadcast/utils.py +0 -0
  59. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/coverage_setup.py +0 -0
  60. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/debug/__init__.py +0 -0
  61. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/errors.py +0 -0
  62. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/pretty_json.py +0 -0
  63. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/profiler.py +0 -0
  64. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/py.typed +0 -0
  65. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/request_tracking/_sql.py +0 -0
  66. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/scripts/__init__.py +0 -0
  67. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/scripts/test_print.py +0 -0
  68. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/services.py +0 -0
  69. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sql_profiler/__init__.py +0 -0
  70. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/README.md +0 -0
  71. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/__init__.py +0 -0
  72. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/_filters.py +0 -0
  73. {c2cwsgiutils-5.2.1 → c2cwsgiutils-5.2.1.dev197}/c2cwsgiutils/sqlalchemylogger/examples/example.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cwsgiutils
3
- Version: 5.2.1
3
+ Version: 5.2.1.dev197
4
4
  Summary: Common utilities for Camptocamp WSGI applications
5
5
  Home-page: https://github.com/camptocamp/c2cwsgiutils
6
6
  License: BSD-2-Clause
7
7
  Keywords: geo,gis,sqlalchemy,orm,wsgi
8
8
  Author: Camptocamp
9
9
  Author-email: info@camptocamp.com
10
- Requires-Python: >=3.8,<4.0
10
+ Requires-Python: >=3.9,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Environment :: Plugins
13
13
  Classifier: Framework :: Pyramid
@@ -17,45 +17,48 @@ Classifier: License :: OSI Approved :: BSD License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.8
21
20
  Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
- Classifier: Programming Language :: Python :: 3
25
- Classifier: Programming Language :: Python :: 3.8
26
23
  Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
27
24
  Classifier: Typing :: Typed
25
+ Provides-Extra: alembic
28
26
  Provides-Extra: all
29
27
  Provides-Extra: broadcast
28
+ Provides-Extra: debug
30
29
  Provides-Extra: dev
31
30
  Provides-Extra: oauth2
31
+ Provides-Extra: sentry
32
32
  Provides-Extra: standard
33
33
  Provides-Extra: test-images
34
- Requires-Dist: SQLAlchemy (>=1.4.0,<3.0.0) ; extra == "standard" or extra == "all"
35
- Requires-Dist: SQLAlchemy-Utils ; extra == "standard" or extra == "all"
36
- Requires-Dist: alembic ; extra == "standard" or extra == "all"
37
- Requires-Dist: boltons ; extra == "standard" or extra == "all"
38
- Requires-Dist: cee_syslog_handler ; extra == "standard" or extra == "all"
34
+ Provides-Extra: tests
35
+ Provides-Extra: webserver
36
+ Requires-Dist: SQLAlchemy (>=1.4.0,<3.0.0)
37
+ Requires-Dist: SQLAlchemy-Utils
38
+ Requires-Dist: alembic ; extra == "standard" or extra == "alembic" or extra == "all"
39
+ Requires-Dist: boltons ; extra == "tests" or extra == "all"
40
+ Requires-Dist: cee_syslog_handler
39
41
  Requires-Dist: certifi
40
- Requires-Dist: cornice ; extra == "standard" or extra == "all"
41
- Requires-Dist: gunicorn ; extra == "standard" or extra == "all"
42
- Requires-Dist: lxml ; extra == "standard" or extra == "all"
43
- Requires-Dist: netifaces ; extra == "standard" or extra == "all"
44
- Requires-Dist: objgraph ; extra == "standard" or extra == "all"
45
- Requires-Dist: psycopg2 ; extra == "standard" or extra == "all"
46
- Requires-Dist: pyjwt ; extra == "oauth2" or extra == "all"
47
- Requires-Dist: pyramid ; extra == "standard" or extra == "all"
48
- Requires-Dist: pyramid-tm ; extra == "standard" or extra == "all"
42
+ Requires-Dist: cornice
43
+ Requires-Dist: gunicorn ; extra == "standard" or extra == "webserver" or extra == "all"
44
+ Requires-Dist: lxml ; extra == "tests" or extra == "all"
45
+ Requires-Dist: objgraph ; extra == "debug" or extra == "all"
46
+ Requires-Dist: prometheus-client
47
+ Requires-Dist: psycopg2
48
+ Requires-Dist: pyjwt ; extra == "standard" or extra == "oauth2" or extra == "all"
49
+ Requires-Dist: pyramid
50
+ Requires-Dist: pyramid-tm
51
+ Requires-Dist: pyramid_mako
49
52
  Requires-Dist: pyyaml
50
- Requires-Dist: redis ; extra == "standard" or extra == "broadcast" or extra == "all" or extra == "all"
53
+ Requires-Dist: redis ; extra == "standard" or extra == "broadcast" or extra == "all"
51
54
  Requires-Dist: requests
52
- Requires-Dist: requests-oauthlib ; extra == "oauth2" or extra == "all"
55
+ Requires-Dist: requests-oauthlib ; extra == "standard" or extra == "oauth2" or extra == "all"
53
56
  Requires-Dist: scikit-image ; extra == "test-images"
54
- Requires-Dist: sentry-sdk ; extra == "standard" or extra == "all"
55
- Requires-Dist: ujson ; extra == "standard" or extra == "all"
57
+ Requires-Dist: sentry-sdk ; extra == "standard" or extra == "sentry" or extra == "all"
58
+ Requires-Dist: ujson
56
59
  Requires-Dist: waitress ; extra == "dev" or extra == "all"
57
- Requires-Dist: zope.interface ; extra == "standard" or extra == "all"
58
- Requires-Dist: zope.sqlalchemy ; extra == "standard" or extra == "all"
60
+ Requires-Dist: zope.interface
61
+ Requires-Dist: zope.sqlalchemy
59
62
  Project-URL: Repository, https://github.com/camptocamp/c2cwsgiutils
60
63
  Description-Content-Type: text/markdown
61
64
 
@@ -64,8 +67,7 @@ Description-Content-Type: text/markdown
64
67
  This is a Python 3 library (>=3.5) providing common tools for Camptocamp WSGI
65
68
  applications:
66
69
 
67
- - Provide a small framework for gathering performance statistics about
68
- a web application (statsd protocol)
70
+ - Provide prometheus metrics
69
71
  - Allow to use a master/slave PostgresQL configuration
70
72
  - Logging handler for CEE/UDP logs
71
73
  - An optional view to change runtime the log levels
@@ -104,7 +106,7 @@ You should install `c2cwsgiutils` with the tool you use to manage your pip depen
104
106
 
105
107
  In the `Dockerfile` you should add the following lines:
106
108
 
107
- ```
109
+ ```dockerfile
108
110
  # Generate the version file.
109
111
  RUN c2cwsgiutils-genversion $(git rev-parse HEAD)
110
112
 
@@ -112,20 +114,19 @@ CMD ["gunicorn", "--paste=/app/production.ini"]
112
114
 
113
115
  # Default values for the environment variables
114
116
  ENV \
115
- DEVELOPMENT=0 \
116
- SQLALCHEMY_POOL_RECYCLE=30 \
117
- SQLALCHEMY_POOL_SIZE=5 \
118
- SQLALCHEMY_MAX_OVERFLOW=25 \
119
- SQLALCHEMY_SLAVE_POOL_RECYCLE=30 \
120
- SQLALCHEMY_SLAVE_POOL_SIZE=5 \
121
- SQLALCHEMY_SLAVE_MAX_OVERFLOW=25\
122
- LOG_TYPE=console \
123
- OTHER_LOG_LEVEL=WARNING \
124
- GUNICORN_LOG_LEVEL=WARNING \
125
- GUNICORN_ACCESS_LOG_LEVEL=INFO \
126
- SQL_LOG_LEVEL=WARNING \
127
- C2CWSGIUTILS_LOG_LEVEL=WARNING \
128
- LOG_LEVEL=INFO
117
+ DEVELOPMENT=0 \
118
+ SQLALCHEMY_POOL_RECYCLE=30 \
119
+ SQLALCHEMY_POOL_SIZE=5 \
120
+ SQLALCHEMY_MAX_OVERFLOW=25 \
121
+ SQLALCHEMY_SLAVE_POOL_RECYCLE=30 \
122
+ SQLALCHEMY_SLAVE_POOL_SIZE=5 \
123
+ SQLALCHEMY_SLAVE_MAX_OVERFLOW=25\
124
+ LOG_TYPE=console \
125
+ OTHER_LOG_LEVEL=WARNING \
126
+ GUNICORN_LOG_LEVEL=WARNING \
127
+ SQL_LOG_LEVEL=WARNING \
128
+ C2CWSGIUTILS_LOG_LEVEL=WARNING \
129
+ LOG_LEVEL=INFO
129
130
  ```
130
131
 
131
132
  Add in your `main` function.
@@ -161,8 +162,6 @@ The related environment variables:
161
162
  - `SQL_LOG_LEVEL`: The SQL query log level, `WARNING`: no logs, `INFO`: logs the queries,
162
163
  `DEBUG` also logs the results, default is `WARNING`.
163
164
  - `GUNICORN_ERROR_LOG_LEVEL`: The Gunicorn error log level, default is `WARNING`.
164
- - `GUNICORN_ACCESS_LOG_LEVEL`: The Gunicorn access log level, the logs have the level `INFO`,
165
- default is `WARNING`.
166
165
  - `C2CWSGIUTILS_CONFIG`: The fallback ini file to use by gunicorn, default is `production.ini`.
167
166
  - `C2CWSGIUTILS_LOG_LEVEL`: The c2c WSGI utils log level, default is `WARNING`.
168
167
  - `OTHER_LOG_LEVEL`: The log level for all the other logger, default is `WARNING`.
@@ -331,47 +330,6 @@ The requests module is also patched to monitor requests done without timeout. In
331
330
  configure a default timeout with the `C2C_REQUESTS_DEFAULT_TIMEOUT` environment variable
332
331
  (`c2c.requests_default_timeout`). If no timeout and no default is specified, a warning is issued.
333
332
 
334
- ## Metrics
335
-
336
- To enable and configure the metrics framework, you can use:
337
-
338
- - STATS_VIEW (c2c.stats_view): if defined, will enable the stats view `{C2C_BASE_PATH}/stats.json`
339
- - STATSD_ADDRESS (c2c.statsd_address): if defined, send stats to the given statsd server
340
- - STATSD_PREFIX (c2c.statsd_prefix): prefix to add to every metric names
341
- - STATSD_USE_TAGS: If true, automatic metrics will use tags
342
- - STATSD*TAG*{tag_name}: To set a global tag for the service
343
-
344
- If enabled, some metrics are automatically generated:
345
-
346
- - {STATSD_PREFIX}.route.{verb}.{route_name}.{status}: The time to process a query (includes rendering)
347
- - {STATSD_PREFIX}.render.{verb}.{route_name}.{status}: The time to render a query
348
- - {STATSD_PREFIX}.sql.{query}: The time to execute the given SQL query (simplified and normalized)
349
- - {STATSD_PREFIX}.requests.{scheme}.{hostname}.{port}.{verb}.{status}: The time to execute HTTP requests to
350
- outside services (only the time between the start of sending of the request and when the header is
351
- back with a chunk of the body)
352
- - {STATSD_PREFIX}.redis.{command}: The time to execute the given Redis command
353
-
354
- You can manually measure the time spent on something like that:
355
-
356
- ```python
357
- from c2cwsgiutils import stats
358
- with stats.timer_context(['toto', 'tutu']):
359
- do_something()
360
- ```
361
-
362
- It will only add a timer event in case of success. If you want to measure both success and failures, do that:
363
-
364
- ```python
365
- from c2cwsgiutils import stats
366
- with stats.outcome_timer_context(['toto', 'tutu']):
367
- do_something()
368
- ```
369
-
370
- Other functions exists to generate metrics. Look at the `c2cwsgiutils.stats` module.
371
-
372
- Look at the `c2cwsgiutils-stats-db` utility if you want to generate statistics (gauges) about the
373
- row counts.
374
-
375
333
  ## SQL profiler
376
334
 
377
335
  The SQL profiler must be configured with the `C2C_SQL_PROFILER_ENABLED` environment variable. That enables a view
@@ -519,27 +477,100 @@ If the `/app/versions.json` exists, a view is added (`{C2C_BASE_PATH}/versions.j
519
477
  version of a app. This file is generated by calling the `c2cwsgiutils-genversion [$GIT_TAG] $GIT_HASH`
520
478
  command line. Usually done in the [Dockerfile](acceptance_tests/app/Dockerfile) of the WSGI application.
521
479
 
522
- ## Metrics
480
+ ## Prometheus
523
481
 
524
- The path `/metrics` provide some metrics for Prometheus.
525
- By default we have the `smap` `pss`, but we can easily add the `rss`, `size` or your custom settings:
482
+ [Prometheus client](https://github.com/prometheus/client_python) is integrated in c2cwsgiutils.
526
483
 
527
- Example:
484
+ It will work in multi process mode with the limitation listed in the
485
+ [`prometheus_client` documentation](https://github.com/prometheus/client_python#multiprocess-mode-eg-gunicorn).
528
486
 
487
+ To enable it you should provide the `C2CWSGIUTILS_PROMETHEUS_PORT` environment variable.
488
+ For security reason, this port should not be exposed.
489
+
490
+ We can customize it with the following environment variables:
491
+
492
+ - `C2C_PROMETHEUS_PREFIX`: to customize the prefix, default is `c2cwsggiutils-`.
493
+ - `C2C_PROMETHEUS_PACKAGES` the packages that will be present in the version information, default is `c2cwsgiutils,pyramid,gunicorn,sqlalchemy`.
494
+ - `C2C_PROMETHEUS_APPLICATION_PACKAGE` the packages that will be present in the version information as application.
495
+
496
+ And you should add in your `gunicorn.conf.py`:
497
+
498
+ ```python
499
+ from prometheus_client import multiprocess
500
+
501
+
502
+ def on_starting(server):
503
+ from c2cwsgiutils import prometheus
504
+
505
+ del server
506
+
507
+ prometheus.start()
508
+
509
+
510
+ def post_fork(server, worker):
511
+ from c2cwsgiutils import prometheus
512
+
513
+ del server, worker
514
+
515
+ prometheus.cleanup()
516
+
517
+
518
+ def child_exit(server, worker):
519
+ del server
520
+
521
+ multiprocess.mark_process_dead(worker.pid)
529
522
  ```
530
- from import c2cwsgiutils.metrics import add_provider, Provider, MemoryMapProvider
531
523
 
532
- class CustomProvider(Provider):
533
- def __init__(self):
534
- super().__init__("my_metrics", "My Metric")
524
+ In your `Dockerfile` you should add:
525
+
526
+ ```dockerfile
527
+ RUN mkdir -p /prometheus-metrics \
528
+ && chmod a+rwx /prometheus-metrics
529
+ ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
530
+ ```
535
531
 
536
- def get_data(self):
537
- return [({'metadata_key': 'matadata_value'}, metrics_value)]
532
+ ### Add custom metric collector
538
533
 
539
- add_provider(MemoryMapProvider('rss'))
540
- add_provider(CustomProvider())
534
+ See [official documentation](https://github.com/prometheus/client_python#custom-collectors).
535
+
536
+ Related to the Unix process.
537
+
538
+ ```python
539
+ from c2cwsgiutils import broadcast, prometheus
540
+
541
+ prometheus.MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS.append("prometheus_collector_custom")
542
+ broadcast.subscribe("c2cwsgiutils_prometheus_collect_gc", _broadcast_collector_custom)
543
+ my_custom_collector_instance = MyCustomCollector()
544
+
545
+
546
+ def _broadcast_collector_custom() -> List[prometheus.SerializedGauge]:
547
+ """Get the collected GC gauges."""
548
+
549
+ return prometheus.serialize_collected_data(my_custom_collector_instance)
550
+ ```
551
+
552
+ Related to the host, use that in the `gunicorn.conf.py`:
553
+
554
+ ```python
555
+ def on_starting(server):
556
+ from c2cwsgiutils import prometheus
557
+
558
+ del server
559
+
560
+ registry = CollectorRegistry()
561
+ registry.register(MyCollector())
562
+ prometheus.start(registry)
541
563
  ```
542
564
 
565
+ ### Database metrics
566
+
567
+ Look at the `c2cwsgiutils-stats-db` utility if you want to generate statistics (gauges) about the
568
+ row counts.
569
+
570
+ ### Usage of metrics
571
+
572
+ With c2cwsgiutils each instance (Pod) has its own metrics, so we need to aggregate them to have the metrics for the service you probably need to use `sum by (<fields>) (<metric>)` to get the metric (especially for counters).
573
+
543
574
  ## Custom scripts
544
575
 
545
576
  To have the application initialized in a script you should use the
@@ -592,7 +623,7 @@ have dumps of a few things:
592
623
  - memory usage: `{C2C_BASE_PATH}/debug/memory?secret={C2C_SECRET}&limit=30&analyze_type=builtins.dict&python_internals_map=false`
593
624
  - object ref: `{C2C_BASE_PATH}/debug/show_refs.dot?secret={C2C_SECRET}&analyze_type=gunicorn.app.wsgiapp.WSGIApplication&analyze_id=12345&max_depth=3&too_many=10&filter=1024&no_extra_info&backrefs`
594
625
  `analyze_type` and `analyze_id` should not ve used toogether, you can use it like:
595
- ```
626
+ ```bash
596
627
  curl "<URL>" > /tmp/show_refs.dot
597
628
  dot -Lg -Tpng /tmp/show_refs.dot > /tmp/show_refs.png
598
629
  ```
@@ -653,7 +684,7 @@ client. In production mode, you can still get them by sending the secret defined
653
684
 
654
685
  If you want to use pyramid_debugtoolbar, you need to disable exception handling and configure it like that:
655
686
 
656
- ```
687
+ ```ini
657
688
  pyramid.includes =
658
689
  pyramid_debugtoolbar
659
690
  debugtoolbar.enabled = true
@@ -727,3 +758,41 @@ To make a release:
727
758
  - Add the new branch name in the `.github/workflows/rebuild.yaml` and
728
759
  `.github/workflows/audit.yaml` files.
729
760
 
761
+ ## Testing
762
+
763
+ ### Screenshots
764
+
765
+ To test the screenshots, you need to install `node` with `npm`, to do that add the following lines in your `Dockerfile`:
766
+
767
+ ```dockerfile
768
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
769
+ --mount=type=cache,target=/var/cache,sharing=locked \
770
+ . /etc/os-release \
771
+ && echo "deb https://deb.nodesource.com/node_18.x ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/nodesource.list \
772
+ && curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
773
+ && apt-get update \
774
+ && apt-get install --assume-yes --no-install-recommends 'nodejs=18.*'
775
+ ```
776
+
777
+ To do the image test call `check_screenshot` e.g.:
778
+
779
+ ```python
780
+ def test_screenshot(app_connection):
781
+ image.check_screenshot(
782
+ app_connection.base_url + "my-path",
783
+ width=800,
784
+ height=600,
785
+ result_folder="results",
786
+ expected_filename=os.path.join(os.path.dirname(__file__), "my-check.expected.png"),
787
+ )
788
+ ```
789
+
790
+ ## Contributing
791
+
792
+ Install the pre-commit hooks:
793
+
794
+ ```bash
795
+ pip install pre-commit
796
+ pre-commit install --allow-missing-config
797
+ ```
798
+
@@ -3,8 +3,7 @@
3
3
  This is a Python 3 library (>=3.5) providing common tools for Camptocamp WSGI
4
4
  applications:
5
5
 
6
- - Provide a small framework for gathering performance statistics about
7
- a web application (statsd protocol)
6
+ - Provide prometheus metrics
8
7
  - Allow to use a master/slave PostgresQL configuration
9
8
  - Logging handler for CEE/UDP logs
10
9
  - An optional view to change runtime the log levels
@@ -43,7 +42,7 @@ You should install `c2cwsgiutils` with the tool you use to manage your pip depen
43
42
 
44
43
  In the `Dockerfile` you should add the following lines:
45
44
 
46
- ```
45
+ ```dockerfile
47
46
  # Generate the version file.
48
47
  RUN c2cwsgiutils-genversion $(git rev-parse HEAD)
49
48
 
@@ -51,20 +50,19 @@ CMD ["gunicorn", "--paste=/app/production.ini"]
51
50
 
52
51
  # Default values for the environment variables
53
52
  ENV \
54
- DEVELOPMENT=0 \
55
- SQLALCHEMY_POOL_RECYCLE=30 \
56
- SQLALCHEMY_POOL_SIZE=5 \
57
- SQLALCHEMY_MAX_OVERFLOW=25 \
58
- SQLALCHEMY_SLAVE_POOL_RECYCLE=30 \
59
- SQLALCHEMY_SLAVE_POOL_SIZE=5 \
60
- SQLALCHEMY_SLAVE_MAX_OVERFLOW=25\
61
- LOG_TYPE=console \
62
- OTHER_LOG_LEVEL=WARNING \
63
- GUNICORN_LOG_LEVEL=WARNING \
64
- GUNICORN_ACCESS_LOG_LEVEL=INFO \
65
- SQL_LOG_LEVEL=WARNING \
66
- C2CWSGIUTILS_LOG_LEVEL=WARNING \
67
- LOG_LEVEL=INFO
53
+ DEVELOPMENT=0 \
54
+ SQLALCHEMY_POOL_RECYCLE=30 \
55
+ SQLALCHEMY_POOL_SIZE=5 \
56
+ SQLALCHEMY_MAX_OVERFLOW=25 \
57
+ SQLALCHEMY_SLAVE_POOL_RECYCLE=30 \
58
+ SQLALCHEMY_SLAVE_POOL_SIZE=5 \
59
+ SQLALCHEMY_SLAVE_MAX_OVERFLOW=25\
60
+ LOG_TYPE=console \
61
+ OTHER_LOG_LEVEL=WARNING \
62
+ GUNICORN_LOG_LEVEL=WARNING \
63
+ SQL_LOG_LEVEL=WARNING \
64
+ C2CWSGIUTILS_LOG_LEVEL=WARNING \
65
+ LOG_LEVEL=INFO
68
66
  ```
69
67
 
70
68
  Add in your `main` function.
@@ -100,8 +98,6 @@ The related environment variables:
100
98
  - `SQL_LOG_LEVEL`: The SQL query log level, `WARNING`: no logs, `INFO`: logs the queries,
101
99
  `DEBUG` also logs the results, default is `WARNING`.
102
100
  - `GUNICORN_ERROR_LOG_LEVEL`: The Gunicorn error log level, default is `WARNING`.
103
- - `GUNICORN_ACCESS_LOG_LEVEL`: The Gunicorn access log level, the logs have the level `INFO`,
104
- default is `WARNING`.
105
101
  - `C2CWSGIUTILS_CONFIG`: The fallback ini file to use by gunicorn, default is `production.ini`.
106
102
  - `C2CWSGIUTILS_LOG_LEVEL`: The c2c WSGI utils log level, default is `WARNING`.
107
103
  - `OTHER_LOG_LEVEL`: The log level for all the other logger, default is `WARNING`.
@@ -270,47 +266,6 @@ The requests module is also patched to monitor requests done without timeout. In
270
266
  configure a default timeout with the `C2C_REQUESTS_DEFAULT_TIMEOUT` environment variable
271
267
  (`c2c.requests_default_timeout`). If no timeout and no default is specified, a warning is issued.
272
268
 
273
- ## Metrics
274
-
275
- To enable and configure the metrics framework, you can use:
276
-
277
- - STATS_VIEW (c2c.stats_view): if defined, will enable the stats view `{C2C_BASE_PATH}/stats.json`
278
- - STATSD_ADDRESS (c2c.statsd_address): if defined, send stats to the given statsd server
279
- - STATSD_PREFIX (c2c.statsd_prefix): prefix to add to every metric names
280
- - STATSD_USE_TAGS: If true, automatic metrics will use tags
281
- - STATSD*TAG*{tag_name}: To set a global tag for the service
282
-
283
- If enabled, some metrics are automatically generated:
284
-
285
- - {STATSD_PREFIX}.route.{verb}.{route_name}.{status}: The time to process a query (includes rendering)
286
- - {STATSD_PREFIX}.render.{verb}.{route_name}.{status}: The time to render a query
287
- - {STATSD_PREFIX}.sql.{query}: The time to execute the given SQL query (simplified and normalized)
288
- - {STATSD_PREFIX}.requests.{scheme}.{hostname}.{port}.{verb}.{status}: The time to execute HTTP requests to
289
- outside services (only the time between the start of sending of the request and when the header is
290
- back with a chunk of the body)
291
- - {STATSD_PREFIX}.redis.{command}: The time to execute the given Redis command
292
-
293
- You can manually measure the time spent on something like that:
294
-
295
- ```python
296
- from c2cwsgiutils import stats
297
- with stats.timer_context(['toto', 'tutu']):
298
- do_something()
299
- ```
300
-
301
- It will only add a timer event in case of success. If you want to measure both success and failures, do that:
302
-
303
- ```python
304
- from c2cwsgiutils import stats
305
- with stats.outcome_timer_context(['toto', 'tutu']):
306
- do_something()
307
- ```
308
-
309
- Other functions exists to generate metrics. Look at the `c2cwsgiutils.stats` module.
310
-
311
- Look at the `c2cwsgiutils-stats-db` utility if you want to generate statistics (gauges) about the
312
- row counts.
313
-
314
269
  ## SQL profiler
315
270
 
316
271
  The SQL profiler must be configured with the `C2C_SQL_PROFILER_ENABLED` environment variable. That enables a view
@@ -458,27 +413,100 @@ If the `/app/versions.json` exists, a view is added (`{C2C_BASE_PATH}/versions.j
458
413
  version of a app. This file is generated by calling the `c2cwsgiutils-genversion [$GIT_TAG] $GIT_HASH`
459
414
  command line. Usually done in the [Dockerfile](acceptance_tests/app/Dockerfile) of the WSGI application.
460
415
 
461
- ## Metrics
416
+ ## Prometheus
417
+
418
+ [Prometheus client](https://github.com/prometheus/client_python) is integrated in c2cwsgiutils.
419
+
420
+ It will work in multi process mode with the limitation listed in the
421
+ [`prometheus_client` documentation](https://github.com/prometheus/client_python#multiprocess-mode-eg-gunicorn).
422
+
423
+ To enable it you should provide the `C2CWSGIUTILS_PROMETHEUS_PORT` environment variable.
424
+ For security reason, this port should not be exposed.
425
+
426
+ We can customize it with the following environment variables:
427
+
428
+ - `C2C_PROMETHEUS_PREFIX`: to customize the prefix, default is `c2cwsggiutils-`.
429
+ - `C2C_PROMETHEUS_PACKAGES` the packages that will be present in the version information, default is `c2cwsgiutils,pyramid,gunicorn,sqlalchemy`.
430
+ - `C2C_PROMETHEUS_APPLICATION_PACKAGE` the packages that will be present in the version information as application.
431
+
432
+ And you should add in your `gunicorn.conf.py`:
433
+
434
+ ```python
435
+ from prometheus_client import multiprocess
436
+
437
+
438
+ def on_starting(server):
439
+ from c2cwsgiutils import prometheus
440
+
441
+ del server
442
+
443
+ prometheus.start()
444
+
445
+
446
+ def post_fork(server, worker):
447
+ from c2cwsgiutils import prometheus
448
+
449
+ del server, worker
450
+
451
+ prometheus.cleanup()
452
+
453
+
454
+ def child_exit(server, worker):
455
+ del server
462
456
 
463
- The path `/metrics` provide some metrics for Prometheus.
464
- By default we have the `smap` `pss`, but we can easily add the `rss`, `size` or your custom settings:
457
+ multiprocess.mark_process_dead(worker.pid)
458
+ ```
459
+
460
+ In your `Dockerfile` you should add:
461
+
462
+ ```dockerfile
463
+ RUN mkdir -p /prometheus-metrics \
464
+ && chmod a+rwx /prometheus-metrics
465
+ ENV PROMETHEUS_MULTIPROC_DIR=/prometheus-metrics
466
+ ```
467
+
468
+ ### Add custom metric collector
469
+
470
+ See [official documentation](https://github.com/prometheus/client_python#custom-collectors).
471
+
472
+ Related to the Unix process.
473
+
474
+ ```python
475
+ from c2cwsgiutils import broadcast, prometheus
465
476
 
466
- Example:
477
+ prometheus.MULTI_PROCESS_COLLECTOR_BROADCAST_CHANNELS.append("prometheus_collector_custom")
478
+ broadcast.subscribe("c2cwsgiutils_prometheus_collect_gc", _broadcast_collector_custom)
479
+ my_custom_collector_instance = MyCustomCollector()
467
480
 
481
+
482
+ def _broadcast_collector_custom() -> List[prometheus.SerializedGauge]:
483
+ """Get the collected GC gauges."""
484
+
485
+ return prometheus.serialize_collected_data(my_custom_collector_instance)
468
486
  ```
469
- from import c2cwsgiutils.metrics import add_provider, Provider, MemoryMapProvider
470
487
 
471
- class CustomProvider(Provider):
472
- def __init__(self):
473
- super().__init__("my_metrics", "My Metric")
488
+ Related to the host, use that in the `gunicorn.conf.py`:
474
489
 
475
- def get_data(self):
476
- return [({'metadata_key': 'matadata_value'}, metrics_value)]
490
+ ```python
491
+ def on_starting(server):
492
+ from c2cwsgiutils import prometheus
477
493
 
478
- add_provider(MemoryMapProvider('rss'))
479
- add_provider(CustomProvider())
494
+ del server
495
+
496
+ registry = CollectorRegistry()
497
+ registry.register(MyCollector())
498
+ prometheus.start(registry)
480
499
  ```
481
500
 
501
+ ### Database metrics
502
+
503
+ Look at the `c2cwsgiutils-stats-db` utility if you want to generate statistics (gauges) about the
504
+ row counts.
505
+
506
+ ### Usage of metrics
507
+
508
+ With c2cwsgiutils each instance (Pod) has its own metrics, so we need to aggregate them to have the metrics for the service you probably need to use `sum by (<fields>) (<metric>)` to get the metric (especially for counters).
509
+
482
510
  ## Custom scripts
483
511
 
484
512
  To have the application initialized in a script you should use the
@@ -531,7 +559,7 @@ have dumps of a few things:
531
559
  - memory usage: `{C2C_BASE_PATH}/debug/memory?secret={C2C_SECRET}&limit=30&analyze_type=builtins.dict&python_internals_map=false`
532
560
  - object ref: `{C2C_BASE_PATH}/debug/show_refs.dot?secret={C2C_SECRET}&analyze_type=gunicorn.app.wsgiapp.WSGIApplication&analyze_id=12345&max_depth=3&too_many=10&filter=1024&no_extra_info&backrefs`
533
561
  `analyze_type` and `analyze_id` should not ve used toogether, you can use it like:
534
- ```
562
+ ```bash
535
563
  curl "<URL>" > /tmp/show_refs.dot
536
564
  dot -Lg -Tpng /tmp/show_refs.dot > /tmp/show_refs.png
537
565
  ```
@@ -592,7 +620,7 @@ client. In production mode, you can still get them by sending the secret defined
592
620
 
593
621
  If you want to use pyramid_debugtoolbar, you need to disable exception handling and configure it like that:
594
622
 
595
- ```
623
+ ```ini
596
624
  pyramid.includes =
597
625
  pyramid_debugtoolbar
598
626
  debugtoolbar.enabled = true
@@ -665,3 +693,41 @@ To make a release:
665
693
  - Tag the GIT commit.
666
694
  - Add the new branch name in the `.github/workflows/rebuild.yaml` and
667
695
  `.github/workflows/audit.yaml` files.
696
+
697
+ ## Testing
698
+
699
+ ### Screenshots
700
+
701
+ To test the screenshots, you need to install `node` with `npm`, to do that add the following lines in your `Dockerfile`:
702
+
703
+ ```dockerfile
704
+ RUN --mount=type=cache,target=/var/lib/apt/lists \
705
+ --mount=type=cache,target=/var/cache,sharing=locked \
706
+ . /etc/os-release \
707
+ && echo "deb https://deb.nodesource.com/node_18.x ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/nodesource.list \
708
+ && curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
709
+ && apt-get update \
710
+ && apt-get install --assume-yes --no-install-recommends 'nodejs=18.*'
711
+ ```
712
+
713
+ To do the image test call `check_screenshot` e.g.:
714
+
715
+ ```python
716
+ def test_screenshot(app_connection):
717
+ image.check_screenshot(
718
+ app_connection.base_url + "my-path",
719
+ width=800,
720
+ height=600,
721
+ result_folder="results",
722
+ expected_filename=os.path.join(os.path.dirname(__file__), "my-check.expected.png"),
723
+ )
724
+ ```
725
+
726
+ ## Contributing
727
+
728
+ Install the pre-commit hooks:
729
+
730
+ ```bash
731
+ pip install pre-commit
732
+ pre-commit install --allow-missing-config
733
+ ```