varanus 0.1.0.dev2__tar.gz → 0.1.0.dev3__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 (69) hide show
  1. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/PKG-INFO +1 -1
  2. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/pyproject.toml +1 -1
  3. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/apps.py +1 -1
  4. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/client.py +33 -30
  5. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/context.py +11 -11
  6. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/middleware.py +6 -6
  7. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/transport/base.py +1 -1
  8. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/transport/database.py +4 -1
  9. varanus-0.1.0.dev3/src/varanus/client/transport/http.py +83 -0
  10. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/search/base.py +1 -3
  11. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/views/api.py +20 -12
  12. varanus-0.1.0.dev2/src/varanus/client/transport/http.py +0 -37
  13. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/README.md +0 -0
  14. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/__init__.py +0 -0
  15. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/__init__.py +0 -0
  16. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/loggers.py +0 -0
  17. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/client/transport/__init__.py +0 -0
  18. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/events.py +0 -0
  19. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/__init__.py +0 -0
  20. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/__main__.py +0 -0
  21. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/admin.py +0 -0
  22. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/apps.py +0 -0
  23. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/asgi.py +0 -0
  24. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/context_processors.py +0 -0
  25. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/management/__init__.py +0 -0
  26. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/management/commands/__init__.py +0 -0
  27. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/management/commands/migrateall.py +0 -0
  28. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/middleware.py +0 -0
  29. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/migrations/0001_initial.py +0 -0
  30. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/migrations/0002_context_node_error_node_log_node_metric_node_and_more.py +0 -0
  31. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/migrations/0003_alter_log_level.py +0 -0
  32. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/migrations/__init__.py +0 -0
  33. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/models.py +0 -0
  34. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/router.py +0 -0
  35. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/search/__init__.py +0 -0
  36. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/search/date.py +0 -0
  37. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/search/facet.py +0 -0
  38. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/settings.py +0 -0
  39. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/static/css/varanus.css +0 -0
  40. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/static/js/varanus.js +0 -0
  41. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/base.html +0 -0
  42. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/dashboard.html +0 -0
  43. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/registration/login.html +0 -0
  44. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/search/daterange.html +0 -0
  45. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/search/multifacet.html +0 -0
  46. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/base.html +0 -0
  47. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/environment_nodes.html +0 -0
  48. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/error.html +0 -0
  49. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/log.html +0 -0
  50. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/metric.html +0 -0
  51. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/node.html +0 -0
  52. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/node_environments.html +0 -0
  53. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/query.html +0 -0
  54. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/details/request.html +0 -0
  55. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/errors.html +0 -0
  56. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/logs.html +0 -0
  57. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/metrics.html +0 -0
  58. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/overview.html +0 -0
  59. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/queries.html +0 -0
  60. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templates/site/requests.html +0 -0
  61. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templatetags/__init__.py +0 -0
  62. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/templatetags/varanus.py +0 -0
  63. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/urls.py +0 -0
  64. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/views/__init__.py +0 -0
  65. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/views/base.py +0 -0
  66. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/views/dashboard.py +0 -0
  67. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/views/site.py +0 -0
  68. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/server/wsgi.py +0 -0
  69. {varanus-0.1.0.dev2 → varanus-0.1.0.dev3}/src/varanus/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: varanus
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev3
4
4
  Summary: Django application monitoring.
5
5
  Requires-Dist: httpx>=0.27.0
6
6
  Requires-Dist: msgspec>=0.19.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "varanus"
3
- version = "0.1.0.dev2"
3
+ version = "0.1.0.dev3"
4
4
  description = "Django application monitoring."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -5,4 +5,4 @@ class VaranusClient(AppConfig):
5
5
  name = "varanus.client"
6
6
 
7
7
  def ready(self):
8
- print("VaranusClient.ready")
8
+ pass
@@ -1,6 +1,5 @@
1
1
  import platform
2
2
  from importlib.metadata import distributions
3
- from typing import Union
4
3
  from urllib.parse import urlsplit
5
4
 
6
5
  from varanus.events import Context, NodeInfo
@@ -20,14 +19,14 @@ def install_query_logger(logger):
20
19
 
21
20
 
22
21
  class VaranusClient:
23
- environment: str | None
22
+ environment: str
24
23
  transport: BaseTransport
25
24
 
26
25
  request_attr: str
27
26
  logger_name: str
28
27
  tags: dict
29
28
 
30
- include_headers: Union[list, bool, None]
29
+ include_headers: list | bool | None
31
30
  exclude_headers: list | None
32
31
  sensitive_headers = set(
33
32
  [
@@ -38,9 +37,8 @@ class VaranusClient:
38
37
  )
39
38
 
40
39
  scheme_transports = {
41
- "test": "varanus.client.transport.test.TestTransport",
42
- "http": "varanus.client.transport.http.HttpTransport",
43
- "https": "varanus.client.transport.http.HttpTransport",
40
+ "http": "varanus.client.transport.http.ThreadedHttpTransport",
41
+ "https": "varanus.client.transport.http.ThreadedHttpTransport",
44
42
  "db": "varanus.client.transport.database.ModelTransport",
45
43
  }
46
44
 
@@ -49,29 +47,38 @@ class VaranusClient:
49
47
 
50
48
  def setup(
51
49
  self,
52
- dsn,
53
- request_attr="varanus",
54
- environment=None,
55
- transport_class=None,
56
- logger_name="varanus.request",
57
- tags=None,
58
- include_headers=None,
59
- exclude_headers=None,
50
+ dsn: str,
51
+ node: str | None = None,
52
+ environment: str = "",
53
+ transport_class: str | type[BaseTransport] | None = None,
54
+ request_attr: str = "varanus",
55
+ logger_name: str = "varanus.request",
56
+ tags: dict | None = None,
57
+ include_headers: list | bool | None = None,
58
+ exclude_headers: list | None = None,
60
59
  log_queries: bool | int = False,
61
- log_query_params=False,
62
- query_metrics=False,
63
- send_all=False,
64
- install=None,
60
+ log_query_params: bool = False,
61
+ query_metrics: bool | str = False,
62
+ send_all: bool = False,
63
+ install: list | None = None,
65
64
  ):
66
65
  url = urlsplit(dsn)
67
66
  self.environment = environment
67
+ self.node = node or platform.node()
68
68
  if transport_class is None:
69
- transport_class = self.scheme_transports.get(url.scheme)
70
- if transport_class is None:
69
+ if url.scheme not in self.scheme_transports:
71
70
  raise ValueError(f"No transport class found for `{url.scheme}`")
72
- if isinstance(transport_class, str):
73
- transport_class = import_string(transport_class)
74
- self.transport = transport_class(url, self.environment)
71
+ resolved_class = import_string(self.scheme_transports[url.scheme])
72
+ elif isinstance(transport_class, str):
73
+ resolved_class = import_string(transport_class)
74
+ else:
75
+ resolved_class = transport_class
76
+ if not issubclass(resolved_class, BaseTransport):
77
+ raise ValueError(
78
+ f"Transport class `{transport_class}` must be a subclass of"
79
+ "BaseTransport."
80
+ )
81
+ self.transport = resolved_class(url, self.environment, self.node)
75
82
  self.request_attr = request_attr
76
83
  self.logger_name = logger_name
77
84
  self.tags = tags or {}
@@ -98,10 +105,6 @@ class VaranusClient:
98
105
  self.send_all = send_all
99
106
  self.configured = True
100
107
  if install:
101
- if not isinstance(install, list):
102
- raise TypeError(
103
- "The varanus middleware can only be automatically installed into a list."
104
- )
105
108
  if "django.contrib.auth.middleware.AuthenticationMiddleware" in install:
106
109
  idx = install.index(
107
110
  "django.contrib.auth.middleware.AuthenticationMiddleware"
@@ -121,14 +124,14 @@ class VaranusClient:
121
124
  def ping(self):
122
125
  self.transport.ping(
123
126
  NodeInfo(
124
- name=platform.node(),
127
+ name=self.node,
125
128
  platform=platform.platform(),
126
129
  python_version=platform.python_version(),
127
130
  packages={d.name: d.version for d in distributions()},
128
131
  )
129
132
  )
130
133
 
131
- def log(self, level, message, *args, **kwargs):
134
+ def log(self, level: int, message: str, *args, **kwargs):
132
135
  if ctx := current_context.get():
133
136
  kwargs.setdefault("stacklevel", 2)
134
137
  ctx.log(level, message, *args, **kwargs)
@@ -137,7 +140,7 @@ class VaranusClient:
137
140
  if ctx := current_context.get():
138
141
  ctx.raw_exception(exception, tags=tags)
139
142
 
140
- def metric(self, name, value: float = 0.0, tags: dict | None = None):
143
+ def metric(self, name: str, value: float = 0.0, tags: dict | None = None):
141
144
  if ctx := current_context.get():
142
145
  ctx.metric(name, value, tags=tags)
143
146
 
@@ -78,11 +78,11 @@ class VaranusContext:
78
78
 
79
79
  def log(
80
80
  self,
81
- level,
82
- message,
81
+ level: int,
82
+ message: str,
83
83
  *args,
84
84
  exc_info=None,
85
- stacklevel=1,
85
+ stacklevel: int = 1,
86
86
  tags: dict | None = None,
87
87
  **kwargs,
88
88
  ):
@@ -99,27 +99,27 @@ class VaranusContext:
99
99
  )
100
100
  )
101
101
 
102
- def debug(self, message, *args, **kwargs):
102
+ def debug(self, message: str, *args, **kwargs):
103
103
  kwargs.setdefault("stacklevel", 2)
104
104
  self.log(logging.DEBUG, message, *args, **kwargs)
105
105
 
106
- def info(self, message, *args, **kwargs):
106
+ def info(self, message: str, *args, **kwargs):
107
107
  kwargs.setdefault("stacklevel", 2)
108
108
  self.log(logging.INFO, message, *args, **kwargs)
109
109
 
110
- def warning(self, message, *args, **kwargs):
110
+ def warning(self, message: str, *args, **kwargs):
111
111
  kwargs.setdefault("stacklevel", 2)
112
112
  self.log(logging.WARNING, message, *args, **kwargs)
113
113
 
114
- def error(self, message, *args, **kwargs):
114
+ def error(self, message: str, *args, **kwargs):
115
115
  kwargs.setdefault("stacklevel", 2)
116
116
  self.log(logging.ERROR, message, *args, **kwargs)
117
117
 
118
- def critical(self, message, *args, **kwargs):
118
+ def critical(self, message: str, *args, **kwargs):
119
119
  kwargs.setdefault("stacklevel", 2)
120
120
  self.log(logging.CRITICAL, message, *args, **kwargs)
121
121
 
122
- def exception(self, message, *args, **kwargs):
122
+ def exception(self, message: str, *args, **kwargs):
123
123
  kwargs.setdefault("stacklevel", 2)
124
124
  kwargs.setdefault("exc_info", sys.exc_info())
125
125
  self.log(logging.ERROR, message, *args, **kwargs)
@@ -128,12 +128,12 @@ class VaranusContext:
128
128
  if err := Error.from_exception(exception, tags=tags):
129
129
  self.errors.append(err)
130
130
 
131
- def context(self, name="", tags: dict | None = None):
131
+ def context(self, name: str = "", tags: dict | None = None):
132
132
  ctx = VaranusContext(self.client, name, tags)
133
133
  self.subcontexts.append(ctx)
134
134
  return ctx
135
135
 
136
- def metric(self, name, value: float = 0.0, tags: dict | None = None):
136
+ def metric(self, name: str, value: float = 0.0, tags: dict | None = None):
137
137
  if name not in self.metrics:
138
138
  self.metrics[name] = Metric(name=name, tags=tags or {})
139
139
  self.metrics[name].update(value)
@@ -1,4 +1,4 @@
1
- import re
1
+ import ipaddress
2
2
 
3
3
  from django.core.exceptions import MiddlewareNotUsed
4
4
  from django.http import HttpRequest, HttpResponse
@@ -6,8 +6,6 @@ from django.http import HttpRequest, HttpResponse
6
6
  from ..events import Request
7
7
  from .client import client
8
8
 
9
- IP_REGEX = re.compile(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
10
-
11
9
 
12
10
  def get_ip(request: HttpRequest):
13
11
  ip_address = request.META.get("HTTP_X_FORWARDED_FOR", "").strip()
@@ -15,9 +13,11 @@ def get_ip(request: HttpRequest):
15
13
  ip_address = ip_address.split(",")[0].strip()
16
14
  if not ip_address:
17
15
  ip_address = request.META.get("REMOTE_ADDR", "127.0.0.1").strip()
18
- if not IP_REGEX.match(ip_address):
19
- ip_address = None
20
- return ip_address
16
+ try:
17
+ # Validate and normalize the IP address.
18
+ return str(ipaddress.ip_address(ip_address))
19
+ except ValueError:
20
+ return ""
21
21
 
22
22
 
23
23
  def request_headers(request: HttpRequest):
@@ -4,7 +4,7 @@ from varanus import events
4
4
 
5
5
 
6
6
  class BaseTransport:
7
- def __init__(self, url: SplitResult, environment: str):
7
+ def __init__(self, url: SplitResult, environment: str, node: str):
8
8
  pass
9
9
 
10
10
  def ping(self, info: events.NodeInfo):
@@ -10,8 +10,9 @@ from .base import BaseTransport
10
10
 
11
11
 
12
12
  class ModelTransport(BaseTransport):
13
- def __init__(self, url: SplitResult, environment: str):
13
+ def __init__(self, url: SplitResult, environment: str, node: str):
14
14
  self.environment = environment
15
+ self.node = node
15
16
 
16
17
  def ping(self, info: events.NodeInfo):
17
18
  site = models.Site.objects.get()
@@ -20,9 +21,11 @@ class ModelTransport(BaseTransport):
20
21
  @transaction.atomic
21
22
  def send(self, event: events.Context):
22
23
  site = models.Site.objects.get()
24
+ node = site.nodes.get(name=self.node, environment=self.environment)
23
25
  models.Context.from_event(
24
26
  event,
25
27
  event_id=uuid.uuid4(),
26
28
  site=site,
27
29
  environment=self.environment,
30
+ node=node,
28
31
  )
@@ -0,0 +1,83 @@
1
+ import logging
2
+ import queue
3
+ import threading
4
+ import time
5
+ from typing import Any
6
+ from urllib.parse import SplitResult, parse_qs
7
+
8
+ import httpx
9
+ import msgspec
10
+
11
+ from varanus import events
12
+
13
+ from .base import BaseTransport
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class HttpTransport(BaseTransport):
19
+ def __init__(self, url: SplitResult, environment: str, node: str):
20
+ path = url.path.rstrip("/")
21
+ self.ping_url = f"{url.scheme}://{url.netloc}{path}/api/ping/"
22
+ self.event_url = f"{url.scheme}://{url.netloc}{path}/api/ingest/"
23
+ self.options = parse_qs(url.query, keep_blank_values=True)
24
+ timeout = 1.0
25
+ if "timeout" in self.options:
26
+ timeout = float(self.options["timeout"][0])
27
+ self.client = httpx.Client(
28
+ headers={
29
+ "X-Varanus-Key": url.username or "",
30
+ "X-Varanus-Environment": environment,
31
+ "X-Varanus-Node": node,
32
+ },
33
+ timeout=timeout,
34
+ )
35
+
36
+ def request(self, url: str, obj: Any):
37
+ try:
38
+ self.client.post(url, content=msgspec.json.encode(obj))
39
+ except Exception:
40
+ logger.exception("error sending to %s", url)
41
+
42
+ def ping(self, info: events.NodeInfo):
43
+ self.request(self.ping_url, info)
44
+
45
+ def send(self, event: events.Context):
46
+ self.request(self.event_url, [event])
47
+
48
+
49
+ def sender(pending: queue.SimpleQueue, client: httpx.Client, url: str, rate: float):
50
+ while True:
51
+ start = time.time()
52
+ events = []
53
+ while True:
54
+ try:
55
+ events.append(pending.get_nowait())
56
+ except queue.Empty:
57
+ break
58
+ if len(events) >= 100:
59
+ break
60
+ if events:
61
+ try:
62
+ client.post(url, content=msgspec.json.encode(events))
63
+ except Exception:
64
+ logger.exception("error sending to %s", url)
65
+ elapsed = time.time() - start
66
+ time.sleep(max(rate - elapsed, 1.0))
67
+
68
+
69
+ class ThreadedHttpTransport(HttpTransport):
70
+ def __init__(self, url: SplitResult, environment: str, node: str):
71
+ super().__init__(url, environment, node)
72
+ self.pending = queue.SimpleQueue()
73
+ rate = 3.0
74
+ if "rate" in self.options:
75
+ rate = float(self.options["rate"][0])
76
+ threading.Thread(
77
+ target=sender,
78
+ args=(self.pending, self.client, self.event_url, rate),
79
+ daemon=True,
80
+ ).start()
81
+
82
+ def send(self, event: events.Context):
83
+ self.pending.put(event)
@@ -46,7 +46,6 @@ class SearchField:
46
46
  self.order = order
47
47
 
48
48
  def __set_name__(self, owner, name: str):
49
- # print("__set_name__", owner, name)
50
49
  assert issubclass(owner, Search)
51
50
  self.name = name
52
51
  self.prefix = f"{name}_"
@@ -60,7 +59,6 @@ class SearchField:
60
59
  owner._fields.append(self)
61
60
 
62
61
  def __get__(self, instance: "Search", owner=None) -> StringValues | Self:
63
- # print("__get__", instance, owner)
64
62
  if owner is None:
65
63
  return self
66
64
  return {
@@ -70,7 +68,7 @@ class SearchField:
70
68
  }
71
69
 
72
70
  def __set__(self, instance, value):
73
- print("__set__", instance, value)
71
+ raise AttributeError(f"`{self.name}` is read-only")
74
72
 
75
73
  def apply(self, queryset: QuerySet, field_data: StringValues) -> QuerySet:
76
74
  raise NotImplementedError()
@@ -22,6 +22,7 @@ class APIView(View):
22
22
  self.key = SiteKey.objects.select_related("site").get(
23
23
  access_key=request.META.get("HTTP_X_VARANUS_KEY", ""), active=True
24
24
  )
25
+ self.node = request.META.get("HTTP_X_VARANUS_NODE", "")
25
26
  self.environment = request.META.get("HTTP_X_VARANUS_ENVIRONMENT", "")
26
27
  self.site = self.key.site
27
28
  with self.site.activated():
@@ -32,7 +33,6 @@ class APIView(View):
32
33
  except SiteKey.DoesNotExist:
33
34
  return JsonResponse({"error": "Invalid site key."}, status=401)
34
35
  except Exception as e:
35
- raise e
36
36
  return JsonResponse({"error": str(e)}, status=400)
37
37
 
38
38
 
@@ -45,17 +45,25 @@ class NodePingView(APIView):
45
45
 
46
46
  class IngestView(APIView):
47
47
  def post(self, request, *args, **kwargs):
48
- event = msgspec.json.decode(request.body, type=events.Context)
48
+ try:
49
+ # Try to decode a list of events first.
50
+ event_list = msgspec.json.decode(request.body, type=list[events.Context])
51
+ except msgspec.ValidationError:
52
+ # Fall back to single event decoding.
53
+ event_list = [msgspec.json.decode(request.body, type=events.Context)]
49
54
  # TODO: cache these
50
55
  node = self.site.nodes.filter(
51
- name=request.META.get("HTTP_X_VARANUS_NODE", ""),
52
- environment=self.environment,
53
- ).first()
54
- context = Context.from_event(
55
- event,
56
- event_id=uuid.uuid4(),
57
- site=self.site,
56
+ name=self.node,
58
57
  environment=self.environment,
59
- node=node,
60
- )
61
- return {"event_id": context.event_id}
58
+ ).get()
59
+ created_ids = []
60
+ for event in event_list:
61
+ context = Context.from_event(
62
+ event,
63
+ event_id=uuid.uuid4(),
64
+ site=self.site,
65
+ environment=self.environment,
66
+ node=node,
67
+ )
68
+ created_ids.append(context.event_id)
69
+ return {"created": created_ids}
@@ -1,37 +0,0 @@
1
- import platform
2
- from typing import Any
3
- from urllib.parse import SplitResult
4
-
5
- import httpx
6
- import msgspec
7
-
8
- from varanus import events
9
-
10
- from .base import BaseTransport
11
-
12
-
13
- class HttpTransport(BaseTransport):
14
- def __init__(self, url: SplitResult, environment: str):
15
- path = url.path.rstrip("/")
16
- self.ping_url = f"{url.scheme}://{url.netloc}{path}/api/ping/"
17
- self.event_url = f"{url.scheme}://{url.netloc}{path}/api/ingest/"
18
- self.client = httpx.Client(
19
- headers={
20
- "X-Varanus-Key": url.username or "",
21
- "X-Varanus-Environment": environment or "",
22
- "X-Varanus-Node": platform.node(),
23
- },
24
- timeout=1.0,
25
- )
26
-
27
- def request(self, url: str, obj: Any):
28
- try:
29
- self.client.post(url, content=msgspec.json.encode(obj))
30
- except Exception as ex:
31
- print(f"error sending to {url}: {ex}")
32
-
33
- def ping(self, info: events.NodeInfo):
34
- self.request(self.ping_url, info)
35
-
36
- def send(self, event: events.Context):
37
- self.request(self.event_url, event)
File without changes