c2cwsgiutils 5.2.1__py3-none-any.whl → 5.2.1.dev197__py3-none-any.whl

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 (56) hide show
  1. c2cwsgiutils/__init__.py +12 -12
  2. c2cwsgiutils/acceptance/connection.py +5 -2
  3. c2cwsgiutils/acceptance/image.py +95 -3
  4. c2cwsgiutils/acceptance/package-lock.json +1933 -0
  5. c2cwsgiutils/acceptance/package.json +7 -0
  6. c2cwsgiutils/acceptance/print.py +3 -3
  7. c2cwsgiutils/acceptance/screenshot.js +62 -0
  8. c2cwsgiutils/acceptance/utils.py +14 -22
  9. c2cwsgiutils/auth.py +4 -4
  10. c2cwsgiutils/broadcast/__init__.py +15 -7
  11. c2cwsgiutils/broadcast/interface.py +3 -2
  12. c2cwsgiutils/broadcast/local.py +3 -2
  13. c2cwsgiutils/broadcast/redis.py +6 -5
  14. c2cwsgiutils/client_info.py +5 -5
  15. c2cwsgiutils/config_utils.py +2 -1
  16. c2cwsgiutils/db.py +20 -11
  17. c2cwsgiutils/db_maintenance_view.py +2 -1
  18. c2cwsgiutils/debug/_listeners.py +7 -6
  19. c2cwsgiutils/debug/_views.py +11 -10
  20. c2cwsgiutils/debug/utils.py +5 -5
  21. c2cwsgiutils/health_check.py +72 -73
  22. c2cwsgiutils/index.py +90 -105
  23. c2cwsgiutils/loader.py +3 -3
  24. c2cwsgiutils/logging_view.py +3 -2
  25. c2cwsgiutils/models_graph.py +4 -4
  26. c2cwsgiutils/prometheus.py +175 -57
  27. c2cwsgiutils/pyramid.py +4 -2
  28. c2cwsgiutils/pyramid_logging.py +2 -1
  29. c2cwsgiutils/redis_stats.py +13 -11
  30. c2cwsgiutils/redis_utils.py +11 -5
  31. c2cwsgiutils/request_tracking/__init__.py +36 -30
  32. c2cwsgiutils/scripts/genversion.py +4 -4
  33. c2cwsgiutils/scripts/stats_db.py +92 -60
  34. c2cwsgiutils/sentry.py +2 -1
  35. c2cwsgiutils/setup_process.py +12 -16
  36. c2cwsgiutils/sql_profiler/_impl.py +3 -2
  37. c2cwsgiutils/sqlalchemylogger/_models.py +2 -2
  38. c2cwsgiutils/sqlalchemylogger/handlers.py +6 -6
  39. c2cwsgiutils/static/favicon-16x16.png +0 -0
  40. c2cwsgiutils/static/favicon-32x32.png +0 -0
  41. c2cwsgiutils/stats_pyramid/__init__.py +7 -11
  42. c2cwsgiutils/stats_pyramid/_db_spy.py +14 -11
  43. c2cwsgiutils/stats_pyramid/_pyramid_spy.py +27 -21
  44. c2cwsgiutils/templates/index.html.mako +50 -0
  45. c2cwsgiutils/version.py +49 -16
  46. {c2cwsgiutils-5.2.1.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/METADATA +168 -99
  47. c2cwsgiutils-5.2.1.dev197.dist-info/RECORD +67 -0
  48. {c2cwsgiutils-5.2.1.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/WHEEL +1 -1
  49. c2cwsgiutils/acceptance/composition.py +0 -129
  50. c2cwsgiutils/metrics.py +0 -110
  51. c2cwsgiutils/scripts/check_es.py +0 -130
  52. c2cwsgiutils/stats.py +0 -344
  53. c2cwsgiutils/stats_pyramid/_views.py +0 -16
  54. c2cwsgiutils-5.2.1.dist-info/RECORD +0 -66
  55. {c2cwsgiutils-5.2.1.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/LICENSE +0 -0
  56. {c2cwsgiutils-5.2.1.dist-info → c2cwsgiutils-5.2.1.dev197.dist-info}/entry_points.txt +0 -0
c2cwsgiutils/metrics.py DELETED
@@ -1,110 +0,0 @@
1
- """Used to publish metrics to Prometheus."""
2
-
3
- import re
4
- import socket
5
- import warnings
6
- from os import listdir
7
- from typing import Any, Dict, List, Optional, Tuple, Union
8
-
9
- import pyramid.request
10
- import pyramid.response
11
-
12
- from c2cwsgiutils.debug.utils import dump_memory_maps
13
-
14
-
15
- class Provider:
16
- """The provider interface."""
17
-
18
- def __init__(self, name: str, help_: str, type_: str = "gauge", extend: bool = True):
19
- self.name = name
20
- self.help = help_
21
- self.type = type_
22
- self.extend = extend
23
-
24
- def get_data(self) -> List[Tuple[Dict[str, str], Union[int, float]]]:
25
- """Get empty response, should be defined in the specific provider."""
26
- return []
27
-
28
-
29
- _PROVIDERS = []
30
-
31
-
32
- POD_NAME = socket.gethostname()
33
- SERVICE_NAME = re.match("^(.+)-[0-9a-f]+-[0-9a-z]+$", POD_NAME)
34
-
35
-
36
- def add_provider(provider: Provider) -> None:
37
- """Add the provider."""
38
- _PROVIDERS.append(provider)
39
-
40
-
41
- def _metrics() -> pyramid.response.Response:
42
- result: List[str] = []
43
-
44
- for provider in _PROVIDERS:
45
- result += [
46
- f"# HELP {provider.name} {provider.help}",
47
- f"# TYPE {provider.name} {provider.type}",
48
- ]
49
- for attributes, value in provider.get_data():
50
- attrib = {}
51
- if provider.extend:
52
- attrib["pod_name"] = POD_NAME
53
- if SERVICE_NAME is not None:
54
- attrib["service_name"] = SERVICE_NAME.group(1)
55
- attrib.update(attributes)
56
- dbl_quote = '"'
57
- printable_attribs = ",".join([f'{k}="{v.replace(dbl_quote, "_")}"' for k, v in attrib.items()])
58
- result.append(f"{provider.name}{{{printable_attribs}}} {value}")
59
-
60
- return "\n".join(result)
61
-
62
-
63
- def _view(request: pyramid.request.Request) -> pyramid.response.Response:
64
- request.response.text = _metrics()
65
- return request.response
66
-
67
-
68
- NUMBER_RE = re.compile(r"^[0-9]+$")
69
-
70
-
71
- class MemoryMapProvider(Provider):
72
- """The Linux memory map provider."""
73
-
74
- def __init__(self, memory_type: str = "pss", pids: Optional[List[str]] = None):
75
- """
76
- Initialize.
77
-
78
- Arguments:
79
-
80
- memory_type: can be rss, pss or size
81
- pids: the list of pids or none
82
- """
83
- super().__init__(
84
- f"pod_process_smap_{memory_type}_kb",
85
- f"Container smap used {memory_type.capitalize()}",
86
- )
87
- self.memory_type = memory_type
88
- self.pids = pids
89
-
90
- def get_data(self) -> List[Tuple[Dict[str, Any], Union[int, float]]]:
91
- """Get empty response, should be defined in the specific provider."""
92
- results: List[Tuple[Dict[str, Any], Union[int, float]]] = []
93
- for pid in [p for p in listdir("/proc/") if NUMBER_RE.match(p)] if self.pids is None else self.pids:
94
- results += [
95
- ({"pid": pid, "name": e["name"]}, e[self.memory_type + "_kb"]) for e in dump_memory_maps(pid)
96
- ]
97
- return results
98
-
99
-
100
- def init(config: pyramid.config.Configurator) -> None:
101
- """Initialize the metrics view, , for backward compatibility."""
102
- warnings.warn("init function is deprecated; use includeme instead")
103
- includeme(config)
104
-
105
-
106
- def includeme(config: pyramid.config.Configurator) -> None:
107
- """Initialize the metrics view."""
108
- config.add_route("c2c_metrics", r"/metrics", request_method="GET")
109
- config.add_view(_view, route_name="c2c_metrics", http_cache=0)
110
- add_provider(MemoryMapProvider())
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env python3
2
- import argparse
3
- import datetime
4
- import logging
5
- import os
6
- import sys
7
- import time
8
- import uuid
9
- import warnings
10
- from typing import Any, List, Optional
11
-
12
- import requests.exceptions
13
- from dateutil import parser as dp
14
-
15
- import c2cwsgiutils.setup_process
16
- from c2cwsgiutils import stats
17
-
18
-
19
- def _ensure_slash(txt: Optional[str]) -> Optional[str]:
20
- if txt is None:
21
- return None
22
- if txt.endswith("/"):
23
- return txt
24
- return txt + "/"
25
-
26
-
27
- LOGGER_NAME = "check_elasticsearch"
28
- LOG_TIMEOUT = int(os.environ["LOG_TIMEOUT"])
29
- LOG = logging.getLogger(LOGGER_NAME)
30
- ES_URL = _ensure_slash(os.environ.get("ES_URL"))
31
- ES_INDEXES = os.environ.get("ES_INDEXES")
32
- ES_AUTH = os.environ.get("ES_AUTH")
33
- ES_FILTERS = os.environ.get("ES_FILTERS", "")
34
-
35
- SEARCH_HEADERS = {"Content-Type": "application/json;charset=UTF-8", "Accept": "application/json"}
36
- if ES_AUTH is not None:
37
- SEARCH_HEADERS["Authorization"] = ES_AUTH
38
- SEARCH_URL = f"{ES_URL}{ES_INDEXES}/_search"
39
-
40
-
41
- def _max_timestamp() -> datetime.datetime:
42
- must: List[Any] = []
43
- query = {
44
- "aggs": {"max_timestamp": {"max": {"field": "@timestamp"}}},
45
- "query": {"bool": {"must": must}},
46
- }
47
- if ES_FILTERS != "":
48
- for filter_ in ES_FILTERS.split(","):
49
- name, value = filter_.split("=")
50
- must.append({"term": {name: value}})
51
- else:
52
- del query["query"]
53
-
54
- r = requests.post(SEARCH_URL, json=query, headers=SEARCH_HEADERS)
55
- r.raise_for_status()
56
- json = r.json()
57
- return dp.parse(json["aggregations"]["max_timestamp"]["value_as_string"])
58
-
59
-
60
- def _check_roundtrip() -> None:
61
- check_uuid = str(uuid.uuid4())
62
-
63
- # emit the log we are going to look for
64
- logger_name = LOGGER_NAME + "." + check_uuid
65
- logger = logging.getLogger(logger_name)
66
- logger.setLevel(logging.INFO)
67
- logger.info("Test roundtrip")
68
-
69
- query = {"query": {"match_phrase": {"log.logger": logger_name}}}
70
- start = time.monotonic()
71
- while time.monotonic() < start + LOG_TIMEOUT:
72
- exception = None
73
- for _ in range(int(os.environ.get("C2CWSGIUTILS_CHECK_ES_TRYNUMBER", 10))):
74
- try:
75
- r = requests.post(SEARCH_URL, json=query, headers=SEARCH_HEADERS)
76
- exception = None
77
- except requests.exceptions.RequestException as e:
78
- logger.exception("Error on querying Elasticsearch")
79
- exception = e
80
- if r.ok:
81
- continue
82
- time.sleep(float(os.environ.get("C2CWSGIUTILS_CHECK_ES_SLEEP", 1)))
83
- if exception is not None:
84
- raise exception
85
- r.raise_for_status()
86
- json = r.json()
87
- found = json["hits"]["total"]
88
- if isinstance(found, dict):
89
- found = found["value"]
90
- if found > 0:
91
- LOG.info("Found the test log line.")
92
- stats.set_gauge(["roundtrip"], time.monotonic() - start)
93
- return
94
- else:
95
- LOG.info("Didn't find the test log line. Wait 1s...")
96
- time.sleep(1)
97
- LOG.warning("Timeout waiting for the test log line")
98
- stats.set_gauge(["roundtrip"], LOG_TIMEOUT * 2)
99
-
100
-
101
- def deprecated() -> None:
102
- """Run the command and print a deprecated notice."""
103
- warnings.warn("c2cwsgiutils_check_es.py is deprecated; use c2cwsgiutils-check-es instead")
104
- return main()
105
-
106
-
107
- def main() -> None:
108
- """Run the command."""
109
- try:
110
- argparser = argparse.ArgumentParser(description="Check logs on Elasticsearch")
111
- c2cwsgiutils.setup_process.fill_arguments(argparser)
112
- args = argparser.parse_args()
113
- c2cwsgiutils.setup_process.bootstrap_application_from_options(args)
114
-
115
- with stats.outcome_timer_context(["get_max_timestamp"]):
116
- max_ts = _max_timestamp()
117
- now = datetime.datetime.now(max_ts.tzinfo)
118
- age = round((now - max_ts).total_seconds())
119
- LOG.info("Last log age: %ss", age)
120
- stats.set_gauge(["max_age"], age)
121
-
122
- if "LOG_TIMEOUT" in os.environ:
123
- _check_roundtrip()
124
- except: # pylint: disable=bare-except
125
- LOG.exception("Exception during run")
126
- sys.exit(1)
127
-
128
-
129
- if __name__ == "__main__":
130
- main()
c2cwsgiutils/stats.py DELETED
@@ -1,344 +0,0 @@
1
- """Generate statsd metrics."""
2
-
3
- import contextlib
4
- import logging
5
- import os
6
- import re
7
- import socket
8
- import threading
9
- import time
10
- from abc import ABCMeta, abstractmethod
11
- from typing import Any, Callable, Dict, Generator, List, Mapping, MutableMapping, Optional, Sequence, Tuple
12
-
13
- import pyramid.request
14
-
15
- from c2cwsgiutils import config_utils
16
-
17
- LOG = logging.getLogger(__name__)
18
- USE_TAGS_ENV = "STATSD_USE_TAGS"
19
- TAG_PREFIX_ENV = "STATSD_TAG_"
20
- USE_TAGS = config_utils.config_bool(os.environ.get(USE_TAGS_ENV, "0"))
21
- TagType = Optional[Mapping[str, Any]]
22
-
23
-
24
- class _BaseBackend(metaclass=ABCMeta):
25
- @abstractmethod
26
- def timer(self, key: Sequence[Any], duration: float, tags: TagType = None) -> None:
27
- pass
28
-
29
- @abstractmethod
30
- def gauge(self, key: Sequence[Any], value: float, tags: TagType = None) -> None:
31
- pass
32
-
33
- @abstractmethod
34
- def counter(self, key: Sequence[Any], increment: int, tags: TagType = None) -> None:
35
- pass
36
-
37
-
38
- BACKENDS: MutableMapping[str, _BaseBackend] = {}
39
-
40
-
41
- class Timer:
42
- """Allow to measure the duration of some activity."""
43
-
44
- def __init__(self, key: Optional[Sequence[Any]], tags: TagType) -> None:
45
- self._key = key
46
- self._tags = tags
47
- self._start = time.monotonic()
48
-
49
- def stop(self, key_final: Optional[Sequence[Any]] = None, tags_final: TagType = None) -> float:
50
- duration = time.monotonic() - self._start
51
- if key_final is not None:
52
- self._key = key_final
53
- if tags_final is not None:
54
- self._tags = tags_final
55
- assert self._key is not None
56
- for backend in BACKENDS.values():
57
- backend.timer(self._key, duration, self._tags)
58
- return duration
59
-
60
-
61
- @contextlib.contextmanager
62
- def timer_context(key: Sequence[Any], tags: TagType = None) -> Generator[None, None, None]:
63
- """
64
- Add a duration measurement to the stats using the duration the context took to run.
65
-
66
- Arguments:
67
-
68
- key: The path of the key, given as a list.
69
- tags: Some tags to attach to the metric.
70
- """
71
- measure = timer(key, tags)
72
- yield
73
- measure.stop()
74
-
75
-
76
- @contextlib.contextmanager
77
- def outcome_timer_context(key: List[Any], tags: TagType = None) -> Generator[None, None, None]:
78
- """
79
- Add a duration measurement to the stats using the duration the context took to run.
80
-
81
- The given key is prepended with 'success' or 'failure' according to the context's outcome.
82
-
83
- Arguments:
84
-
85
- key: The path of the key, given as a list.
86
- tags: Some tags to attach to the metric.
87
- """
88
- measure = timer()
89
- try:
90
- yield
91
- if USE_TAGS:
92
- opt_tags = dict(tags) if tags is not None else {}
93
- opt_tags["success"] = 1
94
- measure.stop(key, opt_tags)
95
- else:
96
- measure.stop(key + ["success"], tags)
97
- except Exception:
98
- if USE_TAGS:
99
- opt_tags = dict(tags) if tags is not None else {}
100
- opt_tags["success"] = 0
101
- measure.stop(key, opt_tags)
102
- else:
103
- measure.stop(key + ["failure"], tags)
104
- raise
105
-
106
-
107
- def timer(key: Optional[Sequence[Any]] = None, tags: TagType = None) -> Timer:
108
- """
109
- Create a timer for the given key.
110
-
111
- The key can be omitted, but then need to be specified when stop is called.
112
-
113
- Keyword Arguments:
114
-
115
- key: The path of the key, given as a list.
116
- tags: Some tags to attach to the metric.
117
-
118
- Returns: An instance of _Timer
119
- """
120
- assert key is None or isinstance(key, list)
121
- return Timer(key, tags)
122
-
123
-
124
- def set_gauge(key: Sequence[Any], value: float, tags: TagType = None) -> None:
125
- """
126
- Set a gauge value.
127
-
128
- Arguments:
129
-
130
- key: The path of the key, given as a list.
131
- value: The new value of the gauge
132
- tags: Some tags to attach to the metric.
133
- """
134
- for backend in BACKENDS.values():
135
- backend.gauge(key, value, tags)
136
-
137
-
138
- def increment_counter(key: Sequence[Any], increment: int = 1, tags: TagType = None) -> None:
139
- """
140
- Increment a counter value.
141
-
142
- Arguments:
143
-
144
- key: The path of the key, given as a list.
145
- increment: The increment
146
- tags: Some tags to attach to the metric.
147
- """
148
- for backend in BACKENDS.values():
149
- backend.counter(key, increment, tags)
150
-
151
-
152
- class MemoryBackend(_BaseBackend):
153
- """Store stats in the memors."""
154
-
155
- def __init__(self) -> None:
156
- self._timers: MutableMapping[str, Tuple[int, float, float, float]] = {}
157
- self._gauges: MutableMapping[str, float] = {}
158
- self._counters: MutableMapping[str, int] = {}
159
- self._stats_lock = threading.Lock()
160
- LOG.info("Starting a MemoryBackend for stats")
161
-
162
- @staticmethod
163
- def _key_entry(key: str) -> str:
164
- return str(key).replace("/", "_")
165
-
166
- @staticmethod
167
- def _key(key: Sequence[Any], tags: TagType) -> str:
168
- result = "/".join(MemoryBackend._key_entry(v) for v in key)
169
- result += _format_tags(
170
- tags,
171
- prefix="/",
172
- tag_sep="/",
173
- kv_sep="=",
174
- key_formatter=MemoryBackend._key_entry,
175
- value_formatter=MemoryBackend._key_entry,
176
- )
177
- return result
178
-
179
- def timer(self, key: Sequence[Any], duration: float, tags: TagType = None) -> None:
180
- """Add a duration measurement to the stats."""
181
- the_key = self._key(key, tags)
182
- with self._stats_lock:
183
- cur = self._timers.get(the_key, None)
184
- if cur is None:
185
- self._timers[the_key] = (1, duration, duration, duration)
186
- else:
187
- self._timers[the_key] = (
188
- cur[0] + 1,
189
- cur[1] + duration,
190
- min(cur[2], duration),
191
- max(cur[3], duration),
192
- )
193
-
194
- def gauge(self, key: Sequence[Any], value: float, tags: TagType = None) -> None:
195
- self._gauges[self._key(key, tags)] = value
196
-
197
- def counter(self, key: Sequence[Any], increment: int, tags: TagType = None) -> None:
198
- the_key = self._key(key, tags)
199
- with self._stats_lock:
200
- self._counters[the_key] = self._counters.get(the_key, 0) + increment
201
-
202
- def get_stats(self, request: pyramid.request.Request) -> Mapping[str, Any]:
203
- reset = request.params.get("reset", "0") == "1"
204
- with self._stats_lock:
205
- timers = {}
206
- for key, value in self._timers.items():
207
- timers[key] = {
208
- "nb": value[0],
209
- "avg_ms": int(round((value[1] / value[0]) * 1000.0)),
210
- "min_ms": int(round(value[2] * 1000.0)),
211
- "max_ms": int(round(value[3] * 1000.0)),
212
- }
213
- gauges = dict(self._gauges)
214
- counters = dict(self._counters)
215
-
216
- if reset:
217
- self._timers.clear()
218
- self._gauges.clear()
219
- self._counters.clear()
220
- return {"timers": timers, "gauges": gauges, "counters": counters}
221
-
222
-
223
- # https://github.com/prometheus/statsd_exporter/blob/master/mapper.go#L29
224
- INVALID_KEY_CHARS = re.compile(r"[^a-zA-Z0-9_]")
225
- INVALID_TAG_VALUE_CHARS = re.compile(r"[,#|]")
226
-
227
-
228
- class StatsDBackend(_BaseBackend): # pragma: nocover
229
- """Abstraction of the statd backend to sent some metrics."""
230
-
231
- def __init__(self, address: str, prefix: str, tags: Optional[Dict[str, str]] = None) -> None:
232
- self._prefix = prefix
233
- self._tags = tags
234
- if self._prefix != "" and not self._prefix.endswith("."):
235
- self._prefix += "."
236
-
237
- host, port = address.rsplit(":")
238
- host = host.strip("[]")
239
- addrinfo = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_UDP)
240
- family, socktype, protocol, _canonname, sock_addr = addrinfo[0]
241
- LOG.info("Starting a StatsDBackend for %s stats: %s -> %s", prefix, address, repr(sock_addr))
242
-
243
- self._socket = socket.socket(family, socktype, protocol)
244
- self._socket.setblocking(False)
245
- self._socket.connect(sock_addr)
246
-
247
- @staticmethod
248
- def _key_entry(key_entry: Any) -> str:
249
- return INVALID_KEY_CHARS.sub("_", str(key_entry))
250
-
251
- @staticmethod
252
- def _tag_value(tag_value: Any) -> str:
253
- return INVALID_TAG_VALUE_CHARS.sub("_", str(tag_value))
254
-
255
- def _key(self, key: Sequence[Any]) -> str:
256
- return (self._prefix + ".".join(map(StatsDBackend._key_entry, key)))[:450]
257
-
258
- def _merge_tags(self, tags: TagType) -> TagType:
259
- if tags is None:
260
- return self._tags
261
- elif self._tags is None:
262
- return tags
263
- else:
264
- tmp = dict(self._tags)
265
- tmp.update(tags)
266
- return tmp
267
-
268
- def _send(self, message: str, tags: TagType) -> None:
269
- tags = self._merge_tags(tags)
270
- message += _format_tags(
271
- tags,
272
- prefix="|#",
273
- tag_sep=",",
274
- kv_sep=":",
275
- key_formatter=StatsDBackend._key_entry,
276
- value_formatter=StatsDBackend._tag_value,
277
- )
278
- try:
279
- self._socket.send(message.encode("utf-8"))
280
- except Exception: # nosec # pylint: disable=broad-except
281
- pass # Ignore errors (must survive if stats cannot be sent)
282
-
283
- def timer(self, key: Sequence[Any], duration: float, tags: TagType = None) -> None:
284
- the_key = self._key(key)
285
- ms_duration = int(round(duration * 1000.0))
286
- ms_duration = max(ms_duration, 1) # collectd would ignore events with zero durations
287
- message = f"{the_key}:{ms_duration}|ms"
288
- self._send(message, tags)
289
-
290
- def gauge(self, key: Sequence[Any], value: float, tags: TagType = None) -> None:
291
- the_key = self._key(key)
292
- message = f"{the_key}:{value}|g"
293
- self._send(message, tags)
294
-
295
- def counter(self, key: Sequence[Any], increment: int, tags: TagType = None) -> None:
296
- the_key = self._key(key)
297
- message = f"{the_key}:{increment}|c"
298
- self._send(message, tags)
299
-
300
-
301
- def init_backends(settings: Optional[Mapping[str, str]] = None) -> None:
302
- """
303
- Initialize the backends according to the configuration.
304
-
305
- Arguments:
306
-
307
- settings: The Pyramid config
308
- """
309
- if config_utils.env_or_settings(settings, "STATS_VIEW", "c2c.stats_view", False): # pragma: nocover
310
- BACKENDS["memory"] = MemoryBackend()
311
-
312
- statsd_address = config_utils.env_or_settings(settings, "STATSD_ADDRESS", "c2c.statsd_address", None)
313
- if statsd_address is not None: # pragma: nocover
314
- statsd_prefix = config_utils.env_or_settings(settings, "STATSD_PREFIX", "c2c.statsd_prefix", "")
315
- statsd_tags = get_env_tags()
316
- try:
317
- BACKENDS["statsd"] = StatsDBackend(statsd_address, statsd_prefix, statsd_tags)
318
- except Exception: # pylint: disable=broad-except
319
- LOG.error("Failed configuring the statsd backend. Will continue without it.", exc_info=True)
320
-
321
-
322
- def _format_tags(
323
- tags: Optional[Mapping[str, Any]],
324
- prefix: str,
325
- tag_sep: str,
326
- kv_sep: str,
327
- key_formatter: Callable[[str], str],
328
- value_formatter: Callable[[str], str],
329
- ) -> str:
330
- if tags:
331
- return prefix + tag_sep.join(
332
- key_formatter(k) + kv_sep + value_formatter(v) for k, v in sorted(tags.items())
333
- )
334
- else:
335
- return ""
336
-
337
-
338
- def get_env_tags() -> Dict[str, str]:
339
- """Get the tag from the environment variable."""
340
- tags = {}
341
- for name, value in os.environ.items():
342
- if name.startswith(TAG_PREFIX_ENV):
343
- tags[name[len(TAG_PREFIX_ENV) :].lower()] = value
344
- return tags
@@ -1,16 +0,0 @@
1
- from typing import cast
2
-
3
- import pyramid.config
4
-
5
- from c2cwsgiutils import config_utils, stats
6
-
7
-
8
- def init(config: pyramid.config.Configurator) -> None:
9
- """Initialize the statistic view."""
10
- config.add_route(
11
- "c2c_read_stats_json", config_utils.get_base_path(config) + r"/stats.json", request_method="GET"
12
- )
13
- memory_backend = cast(stats.MemoryBackend, stats.BACKENDS["memory"])
14
- config.add_view(
15
- memory_backend.get_stats, route_name="c2c_read_stats_json", renderer="fast_json", http_cache=0
16
- )
@@ -1,66 +0,0 @@
1
- c2cwsgiutils/__init__.py,sha256=sPZ6-3L_gJy-NIbYdr7EcoMK2TFZMUnn-qD2BglQP8w,3997
2
- c2cwsgiutils/acceptance/__init__.py,sha256=vjtpPfu0kbXUOYMx15Z8713IfPFZA9XnkUKkIFtVj_M,1500
3
- c2cwsgiutils/acceptance/composition.py,sha256=73WT-DB3Zk_Va8bNfAQuYAOdXuJvNr-DbFJW4N_rLO8,4620
4
- c2cwsgiutils/acceptance/connection.py,sha256=PxqSPYi0Ogrj5FU4IPGK9bQPfBMqxz7L3vQQ4hRCL2U,9109
5
- c2cwsgiutils/acceptance/image.py,sha256=Q7YmO9Z-2BcugLgyMiKgKBvg36pVFfz6MXlijFUjoOY,4475
6
- c2cwsgiutils/acceptance/print.py,sha256=DDAugcYsDAiFPzMnAQ1k4Us_x37ANEbuk5hb4d6xyKc,2329
7
- c2cwsgiutils/acceptance/utils.py,sha256=ixCaHzLyA744dZnfwHpp2AeFlc14WAJb4ojBbbr_R-c,2072
8
- c2cwsgiutils/auth.py,sha256=EyOH2OVG1wrXNVsQWPa5SpSkdzX96MZDir8PFE-xO9w,9314
9
- c2cwsgiutils/broadcast/__init__.py,sha256=cAODXqq2pwZp0n92QSi0ybJIQttV2CJwc1Cd1hhpEvQ,4160
10
- c2cwsgiutils/broadcast/interface.py,sha256=M2NMNCXky05xA_jVlnkEcRG-3SE0TEA2uXTz0oR4zKs,615
11
- c2cwsgiutils/broadcast/local.py,sha256=iJxv9IjzKjKYz6ZLXRSbrqGRmNUmGoYsSbcRSdz4dbs,1062
12
- c2cwsgiutils/broadcast/redis.py,sha256=02EE4-FHG8cE4v_sIswiQblKN6LseorkO13a-b2qq-k,5058
13
- c2cwsgiutils/broadcast/utils.py,sha256=0fQZXPu3p_5LEJpGenJwiiMxECQjJhjZBjIkBk8h-ng,272
14
- c2cwsgiutils/client_info.py,sha256=k1jTv3_NVLeMgJURjcgUbH4tmHPjW39UuOWXPJF7DN8,3050
15
- c2cwsgiutils/config_utils.py,sha256=aN_New-YukyZ08--z2CsjTSS6YfDsI5pc_ciNewjxS0,1496
16
- c2cwsgiutils/coverage_setup.py,sha256=fES0sdhFy6oaeOCuP1qjjm7PQL9l_O8rUKZhRvRBRwQ,839
17
- c2cwsgiutils/db.py,sha256=xtA_x1i6Oi4AELn5cBWVdSNaHTTsO4OfLYqeeh9lXF4,16288
18
- c2cwsgiutils/db_maintenance_view.py,sha256=Br9EnG4wfFCh5-vWGY86be9iCQmS8Z3dg-n0F0Qp_wc,3049
19
- c2cwsgiutils/debug/__init__.py,sha256=80zdAZnE9cwgQW1odE2aOauIxYsG5CQpWvHPcslRue8,1239
20
- c2cwsgiutils/debug/_listeners.py,sha256=atiDbQaWYSSbLRcAuI53Rq_8jvwSN8U6e40Qzc6gePU,4364
21
- c2cwsgiutils/debug/_views.py,sha256=OxFHGglT6QVGAoZqsgREtK3sZ4aSAoxd4Je0CmP5eAc,7507
22
- c2cwsgiutils/debug/utils.py,sha256=1vtfXND_hzs6PH8ohnRGcPYaW2_qKRJ6VxDvEdG0hdU,2328
23
- c2cwsgiutils/errors.py,sha256=LUhZF3BW1i-v20x3EE-rZbT-TpdugpxiW7p5iCAu73Q,6723
24
- c2cwsgiutils/health_check.py,sha256=qqVqIahvxbb2FlACRrRT1x4LrTZDfrcQtlFH-9SiqaU,21068
25
- c2cwsgiutils/index.py,sha256=hXrkHNMuTQnNlG-pTe19teNWHX_gjBF4W1WmBAHiUBw,17356
26
- c2cwsgiutils/loader.py,sha256=hFIB30saYkyj8brmL07AKE_PFAb2Bny_kWlSOg-xp1E,628
27
- c2cwsgiutils/logging_view.py,sha256=zVvZiNvJDdMwclm3KiqsfK8Ra2JSKPSSQt5VYGGpQ2k,3337
28
- c2cwsgiutils/metrics.py,sha256=6Fx6ZjVuy40X5pLLlgflgd1hQYTCxm2OMweyXvLEUUI,3425
29
- c2cwsgiutils/models_graph.py,sha256=z-sfSssKR89W-X3JQvAEdfiAyl-vxkSLo-hs7PcfOUI,2691
30
- c2cwsgiutils/pretty_json.py,sha256=f1-oecFX9hub1nD32mmZRjOTIxhV8bVSt3Meqw68sNU,1698
31
- c2cwsgiutils/profiler.py,sha256=3tIwoDSzOKQ06ug_U6j5VDR1BQ9auUOqdJRRLRhDoHw,739
32
- c2cwsgiutils/prometheus.py,sha256=03DcJqwEj_UaDjVb5rzSbMAqJ6930ZycvNWm-lKB3c4,2062
33
- c2cwsgiutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- c2cwsgiutils/pyramid.py,sha256=VmrAMFpC636WTToNNaI9hWQOYJAk5hn__K43mBvUFtw,1346
35
- c2cwsgiutils/pyramid_logging.py,sha256=BfxYbpF7mxjMY_08TGp3I9101e6s8tUSJ5yKN7tAt9o,3703
36
- c2cwsgiutils/redis_stats.py,sha256=2Y0xj7_yK_A6PRhu8jSK8TF0fsboMiB88BWuEO-qv90,1478
37
- c2cwsgiutils/redis_utils.py,sha256=_gAO3cbhmt2aycFdkWakFt5FnMt1UziFr1j_u7zcOps,4527
38
- c2cwsgiutils/request_tracking/__init__.py,sha256=cArGa8BLPCEvgDrxEgJIKJoaQEhNxr5WPtBT5Gcmt0o,3823
39
- c2cwsgiutils/request_tracking/_sql.py,sha256=ME13tdXV2Vvhvscon9DbDUqehNWcn4uL75uqzcN5-Mg,577
40
- c2cwsgiutils/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- c2cwsgiutils/scripts/check_es.py,sha256=dXF1FpyyIUMRlDxA-OY4RjOdnNqxYq2F6IhoNM7nRPg,4147
42
- c2cwsgiutils/scripts/genversion.py,sha256=A5jE3E-yNMd3IW4jRFJmvLHphrvko4rA-is43sFKoxw,1998
43
- c2cwsgiutils/scripts/stats_db.py,sha256=pskeBh41baRCmIG8ArkgHGWy2JdsDNY3eh0lasYjt-U,9629
44
- c2cwsgiutils/scripts/test_print.py,sha256=UeOZa7jTazgEq5BRJD6lq-u9K6G4movf-sOVKTEs1cQ,2096
45
- c2cwsgiutils/sentry.py,sha256=SpqCADxHvYVR89mFRMQDA7IbUVHh6iwKS06keWXtZSQ,5002
46
- c2cwsgiutils/services.py,sha256=qz51oCZOC0Lj2_ig4UuHIm0ZZO3FfpFTxrXBWZ_oaNo,1567
47
- c2cwsgiutils/setup_process.py,sha256=FDaHWxzBJwuyq2T7YdILyCyQw2T_NjhPK62ldEBcRa4,3494
48
- c2cwsgiutils/sql_profiler/__init__.py,sha256=lZYq83LYlm_P4uNMv0WU_B9Obl90YaNzkqWtteUHadg,876
49
- c2cwsgiutils/sql_profiler/_impl.py,sha256=wKv9zE6Eh7vna_Ft4bX-b16Y-sE7fiy79jlhn1SSSRM,3680
50
- c2cwsgiutils/sqlalchemylogger/README.md,sha256=WEyJSrBjedtX1FFrYiq4oMaWMt1fNxRkJYmJWnAoz3g,1552
51
- c2cwsgiutils/sqlalchemylogger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- c2cwsgiutils/sqlalchemylogger/_filters.py,sha256=OJQ9_WA-fd9fMZ7TUNFzHHTPI6msw2NVBl5RoeYFnGw,752
53
- c2cwsgiutils/sqlalchemylogger/_models.py,sha256=gCCP_AfGl8unVtV1mQW8l2vk8ltDMWl5UJj3-tJ94Pk,1477
54
- c2cwsgiutils/sqlalchemylogger/examples/example.py,sha256=n48dJdUi1FH1hfBMAbfHLGPSb1bOVD8pXMxXB57PnpQ,460
55
- c2cwsgiutils/sqlalchemylogger/handlers.py,sha256=mteP0ahLj7UIKHFDHuCy3k-Qs48Z7oTWPotBbTn90Rk,4809
56
- c2cwsgiutils/stats.py,sha256=A32rL2EZ0XlZGJzfZ2uF7cgzdBb4QtFiPnq77WmFhj4,11480
57
- c2cwsgiutils/stats_pyramid/__init__.py,sha256=Vxncq4iRCIlrPjiugQVARqg3VjAMK5H3iMXpWO3NISo,971
58
- c2cwsgiutils/stats_pyramid/_db_spy.py,sha256=9dpOmx3uoJo8H6NGshDo9IqY97_JQ1Ywu_1dZc4fj08,2840
59
- c2cwsgiutils/stats_pyramid/_pyramid_spy.py,sha256=K6Vd-nNAsstF0-2L51Qdo9xDt0RFB6-N5oBiLx5ZJms,2943
60
- c2cwsgiutils/stats_pyramid/_views.py,sha256=c3PfyrXzG6I184-bWUrHpR1bt3xyIMgs5mfcZ6NjEwU,527
61
- c2cwsgiutils/version.py,sha256=NPWGTOIOMbF2H6S0lmyOHw3r7UXh-1IkrY4ySJx7_Vk,1817
62
- c2cwsgiutils-5.2.1.dist-info/LICENSE,sha256=rM6IWxociA3daRkXnNLYOxGndT5fbs3BfVZCA2Xgt-g,1304
63
- c2cwsgiutils-5.2.1.dist-info/METADATA,sha256=kXRE8ahYa_1WnLbfILxDjVX_2_a-GVPRiOnujtHFM8U,31538
64
- c2cwsgiutils-5.2.1.dist-info/WHEEL,sha256=UTbu2d3PIo7FtACOQrq825bNtQhldwLx2SG2oh0Fl8Q,88
65
- c2cwsgiutils-5.2.1.dist-info/entry_points.txt,sha256=ujgqMTL1awN9qDg8WXmrF7m0fgR-hslUM6zKH86pvy0,703
66
- c2cwsgiutils-5.2.1.dist-info/RECORD,,