varanus 0.1.0__tar.gz → 0.1.0.dev1__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.
- varanus-0.1.0.dev1/PKG-INFO +20 -0
- varanus-0.1.0.dev1/README.md +3 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/pyproject.toml +12 -12
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/__init__.py +0 -1
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/apps.py +1 -1
- varanus-0.1.0.dev1/src/varanus/client/client.py +151 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/context.py +11 -22
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/loggers.py +17 -18
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/middleware.py +20 -24
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/transport/base.py +1 -1
- varanus-0.1.0.dev1/src/varanus/client/transport/database.py +28 -0
- varanus-0.1.0.dev1/src/varanus/client/transport/http.py +37 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/events.py +9 -49
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/admin.py +15 -85
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/apps.py +0 -3
- varanus-0.1.0.dev1/src/varanus/server/context_processors.py +7 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/migrations/0001_initial.py +60 -246
- varanus-0.1.0.dev1/src/varanus/server/migrations/0002_context_node_error_node_log_node_metric_node_and_more.py +79 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/models.py +29 -210
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/router.py +0 -4
- {varanus-0.1.0/src/varanus → varanus-0.1.0.dev1/src/varanus/server}/search/__init__.py +2 -3
- varanus-0.1.0.dev1/src/varanus/server/search/base.py +106 -0
- varanus-0.1.0.dev1/src/varanus/server/search/date.py +33 -0
- varanus-0.1.0.dev1/src/varanus/server/search/facet.py +59 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/settings.py +6 -83
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/templates/base.html +2 -2
- varanus-0.1.0.dev1/src/varanus/server/templates/dashboard.html +4 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/search/daterange.html +10 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/search/multifacet.html +11 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/base.html +61 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/error.html +1 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/log.html +1 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/metric.html +1 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/query.html +1 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/request.html +44 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/errors.html +30 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/logs.html +32 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/templates/site/metrics.html +5 -17
- varanus-0.1.0.dev1/src/varanus/server/templates/site/overview.html +48 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/queries.html +36 -0
- varanus-0.1.0.dev1/src/varanus/server/templates/site/requests.html +32 -0
- varanus-0.1.0.dev1/src/varanus/server/urls.py +45 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/views/api.py +16 -14
- varanus-0.1.0.dev1/src/varanus/server/views/dashboard.py +11 -0
- varanus-0.1.0.dev1/src/varanus/server/views/site.py +112 -0
- varanus-0.1.0.dev1/src/varanus/utils.py +10 -0
- varanus-0.1.0/PKG-INFO +0 -46
- varanus-0.1.0/README.md +0 -25
- varanus-0.1.0/src/varanus/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/__init__.py +0 -6
- varanus-0.1.0/src/varanus/client/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/client/client.py +0 -231
- varanus-0.1.0/src/varanus/client/transport/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/client/transport/database.py +0 -57
- varanus-0.1.0/src/varanus/client/transport/http.py +0 -83
- varanus-0.1.0/src/varanus/search/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/search/base.py +0 -159
- varanus-0.1.0/src/varanus/search/fields.py +0 -189
- varanus-0.1.0/src/varanus/search/templates/search/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/search/templates/search/daterange.html +0 -11
- varanus-0.1.0/src/varanus/search/templates/search/filter.html +0 -1
- varanus-0.1.0/src/varanus/search/templates/search/hidden.html +0 -12
- varanus-0.1.0/src/varanus/search/templates/search/multifacet.html +0 -6
- varanus-0.1.0/src/varanus/search/templates/search/search.html +0 -23
- varanus-0.1.0/src/varanus/search/utils.py +0 -38
- varanus-0.1.0/src/varanus/server/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/context_processors.py +0 -9
- varanus-0.1.0/src/varanus/server/integrations/__init__.py +0 -3
- varanus-0.1.0/src/varanus/server/integrations/base.py +0 -38
- varanus-0.1.0/src/varanus/server/integrations/squish.py +0 -74
- varanus-0.1.0/src/varanus/server/management/commands/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/management/commands/maintenance.py +0 -21
- varanus-0.1.0/src/varanus/server/migrations/0002_drop_scheduledtask.py +0 -13
- varanus-0.1.0/src/varanus/server/migrations/0003_site_retention.py +0 -21
- varanus-0.1.0/src/varanus/server/migrations/0004_node_version.py +0 -17
- varanus-0.1.0/src/varanus/server/static/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/static/css/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/static/js/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/tasks.py +0 -73
- varanus-0.1.0/src/varanus/server/templates/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/templates/dashboard.html +0 -43
- varanus-0.1.0/src/varanus/server/templates/site/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/templates/site/base.html +0 -65
- varanus-0.1.0/src/varanus/server/templates/site/details/environment_nodes.html +0 -6
- varanus-0.1.0/src/varanus/server/templates/site/details/error.html +0 -47
- varanus-0.1.0/src/varanus/server/templates/site/details/log.html +0 -34
- varanus-0.1.0/src/varanus/server/templates/site/details/metric.html +0 -38
- varanus-0.1.0/src/varanus/server/templates/site/details/node_env.html +0 -24
- varanus-0.1.0/src/varanus/server/templates/site/details/node_environments.html +0 -6
- varanus-0.1.0/src/varanus/server/templates/site/details/node_packages.html +0 -44
- varanus-0.1.0/src/varanus/server/templates/site/details/node_settings.html +0 -24
- varanus-0.1.0/src/varanus/server/templates/site/details/query.html +0 -46
- varanus-0.1.0/src/varanus/server/templates/site/details/request.html +0 -150
- varanus-0.1.0/src/varanus/server/templates/site/errors.html +0 -44
- varanus-0.1.0/src/varanus/server/templates/site/logs.html +0 -47
- varanus-0.1.0/src/varanus/server/templates/site/overview.html +0 -62
- varanus-0.1.0/src/varanus/server/templates/site/queries.html +0 -64
- varanus-0.1.0/src/varanus/server/templates/site/requests.html +0 -54
- varanus-0.1.0/src/varanus/server/templatetags/varanus.py +0 -20
- varanus-0.1.0/src/varanus/server/urls.py +0 -94
- varanus-0.1.0/src/varanus/server/utils.py +0 -8
- varanus-0.1.0/src/varanus/server/views/.DS_Store +0 -0
- varanus-0.1.0/src/varanus/server/views/__init__.py +0 -0
- varanus-0.1.0/src/varanus/server/views/dashboard.py +0 -25
- varanus-0.1.0/src/varanus/server/views/site.py +0 -254
- varanus-0.1.0/src/varanus/utils.py +0 -28
- /varanus-0.1.0/src/varanus/version.py → /varanus-0.1.0.dev1/src/varanus/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/client/transport/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/__main__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/asgi.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/management/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/management/commands/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/management/commands/migrateall.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/middleware.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/migrations/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/static/css/varanus.css +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/static/js/varanus.js +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/templates/registration/login.html +0 -0
- {varanus-0.1.0/src/varanus/server/templatetags → varanus-0.1.0.dev1/src/varanus/server/views}/__init__.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/views/base.py +0 -0
- {varanus-0.1.0 → varanus-0.1.0.dev1}/src/varanus/server/wsgi.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: varanus
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
|
+
Summary: Django application monitoring.
|
|
5
|
+
Requires-Dist: httpx>=0.27.0
|
|
6
|
+
Requires-Dist: msgspec>=0.19.0
|
|
7
|
+
Requires-Dist: cconf>=1.0.0 ; extra == 'server'
|
|
8
|
+
Requires-Dist: django~=5.2.0 ; extra == 'server'
|
|
9
|
+
Requires-Dist: django-passkey-auth>=0.2.0 ; extra == 'server'
|
|
10
|
+
Requires-Dist: granian>=2.4.2 ; extra == 'server'
|
|
11
|
+
Requires-Dist: psycopg[binary]>=3.2.1 ; extra == 'server'
|
|
12
|
+
Requires-Dist: websockets>=13.0 ; extra == 'server'
|
|
13
|
+
Requires-Dist: whitenoise>=6.7.0 ; extra == 'server'
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Provides-Extra: server
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
* Single database mode
|
|
19
|
+
* Multiple schema mode
|
|
20
|
+
* Local mode
|
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "varanus"
|
|
3
|
-
version = "0.1.0"
|
|
3
|
+
version = "0.1.0.dev1"
|
|
4
4
|
description = "Django application monitoring."
|
|
5
|
-
authors = [
|
|
6
|
-
{ name = "Dan Watson", email = "watsond@imsweb.com" }
|
|
7
|
-
]
|
|
8
5
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.13"
|
|
10
7
|
dependencies = [
|
|
11
8
|
"httpx>=0.27.0",
|
|
12
9
|
"msgspec>=0.19.0",
|
|
13
10
|
]
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Intended Audience :: Developers",
|
|
16
|
-
"Programming Language :: Python",
|
|
17
|
-
"Programming Language :: Python :: 3",
|
|
18
|
-
]
|
|
19
11
|
|
|
20
12
|
[project.optional-dependencies]
|
|
21
13
|
server = [
|
|
22
14
|
"cconf>=1.0.0",
|
|
23
|
-
"django
|
|
24
|
-
"django-dbtasks[serve]>=0.3.2; python_version>='3.12'",
|
|
15
|
+
"django~=5.2.0",
|
|
25
16
|
"django-passkey-auth>=0.2.0",
|
|
17
|
+
"granian>=2.4.2",
|
|
26
18
|
"psycopg[binary]>=3.2.1",
|
|
19
|
+
"websockets>=13.0",
|
|
27
20
|
"whitenoise>=6.7.0",
|
|
28
21
|
]
|
|
29
22
|
|
|
30
23
|
[dependency-groups]
|
|
31
24
|
dev = []
|
|
32
25
|
|
|
26
|
+
[project.scripts]
|
|
27
|
+
manage = "varanus.server.__main__:manage"
|
|
28
|
+
|
|
33
29
|
[build-system]
|
|
34
30
|
requires = ["uv_build>=0.9.2,<0.10.0"]
|
|
35
31
|
build-backend = "uv_build"
|
|
@@ -37,3 +33,7 @@ build-backend = "uv_build"
|
|
|
37
33
|
[tool.ruff.lint]
|
|
38
34
|
extend-select = ["I"]
|
|
39
35
|
isort.known-first-party = ["varanus"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
addopts = "--tb=short -s"
|
|
39
|
+
DJANGO_SETTINGS_MODULE = "varanus.server.settings"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
from importlib.metadata import distributions
|
|
3
|
+
from typing import Union
|
|
4
|
+
from urllib.parse import urlsplit
|
|
5
|
+
|
|
6
|
+
from varanus.events import Context, NodeInfo
|
|
7
|
+
|
|
8
|
+
from ..utils import import_string
|
|
9
|
+
from .context import VaranusContext, current_context
|
|
10
|
+
from .loggers import QueryLogger
|
|
11
|
+
from .transport.base import BaseTransport
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def install_query_logger(logger):
|
|
15
|
+
def handler(sender, **kwargs):
|
|
16
|
+
if logger not in kwargs["connection"].execute_wrappers:
|
|
17
|
+
kwargs["connection"].execute_wrappers.append(logger)
|
|
18
|
+
|
|
19
|
+
return handler
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VaranusClient:
|
|
23
|
+
environment: str | None
|
|
24
|
+
transport: BaseTransport
|
|
25
|
+
|
|
26
|
+
request_attr: str
|
|
27
|
+
logger_name: str
|
|
28
|
+
tags: dict
|
|
29
|
+
|
|
30
|
+
include_headers: Union[list, bool, None]
|
|
31
|
+
exclude_headers: list | None
|
|
32
|
+
sensitive_headers = set(
|
|
33
|
+
[
|
|
34
|
+
"authorization",
|
|
35
|
+
"cookie",
|
|
36
|
+
"proxy-authorization",
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
scheme_transports = {
|
|
41
|
+
"test": "varanus.client.transport.test.TestTransport",
|
|
42
|
+
"http": "varanus.client.transport.http.HttpTransport",
|
|
43
|
+
"https": "varanus.client.transport.http.HttpTransport",
|
|
44
|
+
"db": "varanus.client.transport.database.ModelTransport",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
send_all: bool
|
|
48
|
+
configured = False
|
|
49
|
+
|
|
50
|
+
def setup(
|
|
51
|
+
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,
|
|
60
|
+
log_queries: bool | int = False,
|
|
61
|
+
log_query_params=False,
|
|
62
|
+
query_metrics=False,
|
|
63
|
+
send_all=False,
|
|
64
|
+
install=None,
|
|
65
|
+
):
|
|
66
|
+
url = urlsplit(dsn)
|
|
67
|
+
self.environment = environment
|
|
68
|
+
if transport_class is None:
|
|
69
|
+
transport_class = self.scheme_transports.get(url.scheme)
|
|
70
|
+
if transport_class is None:
|
|
71
|
+
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)
|
|
75
|
+
self.request_attr = request_attr
|
|
76
|
+
self.logger_name = logger_name
|
|
77
|
+
self.tags = tags or {}
|
|
78
|
+
self.include_headers = include_headers
|
|
79
|
+
self.exclude_headers = exclude_headers
|
|
80
|
+
if log_queries or query_metrics:
|
|
81
|
+
try:
|
|
82
|
+
# The logger is installed as early as possible, and for all connections.
|
|
83
|
+
from django.db.backends.signals import connection_created
|
|
84
|
+
|
|
85
|
+
# Create a single QueryLogger to be used by all connections.
|
|
86
|
+
self.query_logger = QueryLogger(
|
|
87
|
+
log_queries,
|
|
88
|
+
log_query_params,
|
|
89
|
+
query_metrics,
|
|
90
|
+
)
|
|
91
|
+
# Install it in each new connection (if it's not already installed).
|
|
92
|
+
connection_created.connect(
|
|
93
|
+
install_query_logger(self.query_logger),
|
|
94
|
+
weak=False,
|
|
95
|
+
)
|
|
96
|
+
except ImportError:
|
|
97
|
+
pass
|
|
98
|
+
self.send_all = send_all
|
|
99
|
+
self.configured = True
|
|
100
|
+
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
|
+
if "django.contrib.auth.middleware.AuthenticationMiddleware" in install:
|
|
106
|
+
idx = install.index(
|
|
107
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware"
|
|
108
|
+
)
|
|
109
|
+
install.insert(idx + 1, "varanus.client.middleware.VaranusMiddleware")
|
|
110
|
+
elif "django.middleware.common.CommonMiddleware" in install:
|
|
111
|
+
idx = install.index("django.middleware.common.CommonMiddleware")
|
|
112
|
+
install.insert(idx + 1, "varanus.client.middleware.VaranusMiddleware")
|
|
113
|
+
else:
|
|
114
|
+
install.append("varanus.client.middleware.VaranusMiddleware")
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def send(self, *events: Context):
|
|
118
|
+
for e in events:
|
|
119
|
+
self.transport.send(e)
|
|
120
|
+
|
|
121
|
+
def ping(self):
|
|
122
|
+
self.transport.ping(
|
|
123
|
+
NodeInfo(
|
|
124
|
+
name=platform.node(),
|
|
125
|
+
platform=platform.platform(),
|
|
126
|
+
python_version=platform.python_version(),
|
|
127
|
+
packages={d.name: d.version for d in distributions()},
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def log(self, level, message, *args, **kwargs):
|
|
132
|
+
if ctx := current_context.get():
|
|
133
|
+
kwargs.setdefault("stacklevel", 2)
|
|
134
|
+
ctx.log(level, message, *args, **kwargs)
|
|
135
|
+
|
|
136
|
+
def raw_exception(self, exception, tags: dict | None = None):
|
|
137
|
+
if ctx := current_context.get():
|
|
138
|
+
ctx.raw_exception(exception, tags=tags)
|
|
139
|
+
|
|
140
|
+
def metric(self, name, value: float = 0.0, tags: dict | None = None):
|
|
141
|
+
if ctx := current_context.get():
|
|
142
|
+
ctx.metric(name, value, tags=tags)
|
|
143
|
+
|
|
144
|
+
def context(self, name: str, tags: dict | None = None):
|
|
145
|
+
if ctx := current_context.get():
|
|
146
|
+
return ctx.context(name, tags)
|
|
147
|
+
else:
|
|
148
|
+
return VaranusContext(self, name, tags or self.tags)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
client = VaranusClient()
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import contextlib
|
|
2
1
|
import inspect
|
|
3
2
|
import logging
|
|
4
3
|
import sys
|
|
5
|
-
import time
|
|
6
4
|
from contextvars import ContextVar, Token
|
|
7
5
|
from datetime import timedelta
|
|
8
6
|
from typing import TYPE_CHECKING
|
|
@@ -80,11 +78,11 @@ class VaranusContext:
|
|
|
80
78
|
|
|
81
79
|
def log(
|
|
82
80
|
self,
|
|
83
|
-
level
|
|
84
|
-
message
|
|
81
|
+
level,
|
|
82
|
+
message,
|
|
85
83
|
*args,
|
|
86
84
|
exc_info=None,
|
|
87
|
-
stacklevel
|
|
85
|
+
stacklevel=1,
|
|
88
86
|
tags: dict | None = None,
|
|
89
87
|
**kwargs,
|
|
90
88
|
):
|
|
@@ -101,27 +99,27 @@ class VaranusContext:
|
|
|
101
99
|
)
|
|
102
100
|
)
|
|
103
101
|
|
|
104
|
-
def debug(self, message
|
|
102
|
+
def debug(self, message, *args, **kwargs):
|
|
105
103
|
kwargs.setdefault("stacklevel", 2)
|
|
106
104
|
self.log(logging.DEBUG, message, *args, **kwargs)
|
|
107
105
|
|
|
108
|
-
def info(self, message
|
|
106
|
+
def info(self, message, *args, **kwargs):
|
|
109
107
|
kwargs.setdefault("stacklevel", 2)
|
|
110
108
|
self.log(logging.INFO, message, *args, **kwargs)
|
|
111
109
|
|
|
112
|
-
def warning(self, message
|
|
110
|
+
def warning(self, message, *args, **kwargs):
|
|
113
111
|
kwargs.setdefault("stacklevel", 2)
|
|
114
112
|
self.log(logging.WARNING, message, *args, **kwargs)
|
|
115
113
|
|
|
116
|
-
def error(self, message
|
|
114
|
+
def error(self, message, *args, **kwargs):
|
|
117
115
|
kwargs.setdefault("stacklevel", 2)
|
|
118
116
|
self.log(logging.ERROR, message, *args, **kwargs)
|
|
119
117
|
|
|
120
|
-
def critical(self, message
|
|
118
|
+
def critical(self, message, *args, **kwargs):
|
|
121
119
|
kwargs.setdefault("stacklevel", 2)
|
|
122
120
|
self.log(logging.CRITICAL, message, *args, **kwargs)
|
|
123
121
|
|
|
124
|
-
def exception(self, message
|
|
122
|
+
def exception(self, message, *args, **kwargs):
|
|
125
123
|
kwargs.setdefault("stacklevel", 2)
|
|
126
124
|
kwargs.setdefault("exc_info", sys.exc_info())
|
|
127
125
|
self.log(logging.ERROR, message, *args, **kwargs)
|
|
@@ -130,25 +128,16 @@ class VaranusContext:
|
|
|
130
128
|
if err := Error.from_exception(exception, tags=tags):
|
|
131
129
|
self.errors.append(err)
|
|
132
130
|
|
|
133
|
-
def context(self, name
|
|
131
|
+
def context(self, name="", tags: dict | None = None):
|
|
134
132
|
ctx = VaranusContext(self.client, name, tags)
|
|
135
133
|
self.subcontexts.append(ctx)
|
|
136
134
|
return ctx
|
|
137
135
|
|
|
138
|
-
def metric(self, name
|
|
136
|
+
def metric(self, name, value: float = 0.0, tags: dict | None = None):
|
|
139
137
|
if name not in self.metrics:
|
|
140
138
|
self.metrics[name] = Metric(name=name, tags=tags or {})
|
|
141
139
|
self.metrics[name].update(value)
|
|
142
140
|
|
|
143
|
-
@contextlib.contextmanager
|
|
144
|
-
def timer(self, name: str, tags: dict | None = None):
|
|
145
|
-
start = time.monotonic()
|
|
146
|
-
try:
|
|
147
|
-
yield self
|
|
148
|
-
finally:
|
|
149
|
-
elapsed = time.monotonic() - start
|
|
150
|
-
self.metric(name, elapsed, tags=tags)
|
|
151
|
-
|
|
152
141
|
|
|
153
142
|
current_context: ContextVar[VaranusContext | None] = ContextVar(
|
|
154
143
|
"current_context",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
-
from ..events import Log, Query,
|
|
4
|
+
from ..events import Log, Query, now
|
|
5
5
|
from .context import ONE_MS, current_context
|
|
6
6
|
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ class VaranusHandler(logging.Handler):
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class QueryLogger:
|
|
15
|
-
def __init__(self, threshold, log_params,
|
|
15
|
+
def __init__(self, threshold, log_params, metrics):
|
|
16
16
|
# TODO: add callback for tagging?
|
|
17
17
|
if threshold is True:
|
|
18
18
|
self.threshold = 0
|
|
@@ -21,7 +21,6 @@ class QueryLogger:
|
|
|
21
21
|
else:
|
|
22
22
|
self.threshold = int(threshold)
|
|
23
23
|
self.log_params = log_params
|
|
24
|
-
self.log_stack = log_stack
|
|
25
24
|
if isinstance(metrics, str):
|
|
26
25
|
self.metrics_name = metrics
|
|
27
26
|
else:
|
|
@@ -42,19 +41,19 @@ class QueryLogger:
|
|
|
42
41
|
elapsed_ms = (now() - start) // ONE_MS
|
|
43
42
|
if self.metrics_name:
|
|
44
43
|
ctx.metric(self.metrics_name, elapsed_ms)
|
|
45
|
-
if elapsed_ms
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
44
|
+
if elapsed_ms < self.threshold:
|
|
45
|
+
return
|
|
46
|
+
ctx.queries.append(
|
|
47
|
+
Query(
|
|
48
|
+
timestamp=start,
|
|
49
|
+
sql=sql,
|
|
50
|
+
params=(
|
|
51
|
+
[repr(p) for p in params]
|
|
52
|
+
if params and self.log_params
|
|
53
|
+
else []
|
|
54
|
+
),
|
|
55
|
+
db=context["connection"].alias,
|
|
56
|
+
elapsed_ms=elapsed_ms,
|
|
57
|
+
success=success,
|
|
60
58
|
)
|
|
59
|
+
)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import re
|
|
2
2
|
|
|
3
3
|
from django.core.exceptions import MiddlewareNotUsed
|
|
4
4
|
from django.http import HttpRequest, HttpResponse
|
|
5
|
-
from django.utils import timezone
|
|
6
5
|
|
|
7
6
|
from ..events import Request
|
|
8
|
-
from .client import client
|
|
7
|
+
from .client import client
|
|
8
|
+
|
|
9
|
+
IP_REGEX = re.compile(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def get_ip(request: HttpRequest):
|
|
@@ -14,21 +15,24 @@ def get_ip(request: HttpRequest):
|
|
|
14
15
|
ip_address = ip_address.split(",")[0].strip()
|
|
15
16
|
if not ip_address:
|
|
16
17
|
ip_address = request.META.get("REMOTE_ADDR", "127.0.0.1").strip()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
except ValueError:
|
|
21
|
-
return ""
|
|
18
|
+
if not IP_REGEX.match(ip_address):
|
|
19
|
+
ip_address = None
|
|
20
|
+
return ip_address
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def request_headers(request: HttpRequest):
|
|
25
24
|
headers = {}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
if not client.include_headers:
|
|
26
|
+
return headers
|
|
27
|
+
if client.include_headers is True:
|
|
28
|
+
include = set(name.lower() for name in request.headers)
|
|
29
|
+
else:
|
|
30
|
+
include = set(name.lower() for name in client.include_headers)
|
|
31
|
+
if client.exclude_headers is None:
|
|
32
|
+
exclude = client.sensitive_headers
|
|
33
|
+
else:
|
|
34
|
+
exclude = set(name.lower() for name in client.exclude_headers)
|
|
35
|
+
for name in sorted(include - exclude):
|
|
32
36
|
value = request.headers.get(name)
|
|
33
37
|
if value is not None:
|
|
34
38
|
headers[name] = value
|
|
@@ -41,7 +45,7 @@ class VaranusMiddleware:
|
|
|
41
45
|
# TODO: warning
|
|
42
46
|
print("VaranusClient is not configured -- disabling middleware.")
|
|
43
47
|
raise MiddlewareNotUsed()
|
|
44
|
-
|
|
48
|
+
client.ping()
|
|
45
49
|
self.get_response = get_response
|
|
46
50
|
|
|
47
51
|
def process_exception(self, request, exception):
|
|
@@ -49,22 +53,14 @@ class VaranusMiddleware:
|
|
|
49
53
|
client.raw_exception(exception)
|
|
50
54
|
|
|
51
55
|
def __call__(self, request: HttpRequest):
|
|
52
|
-
if self.last_ping is None:
|
|
53
|
-
self.last_ping = timezone.now()
|
|
54
|
-
try:
|
|
55
|
-
client.ping()
|
|
56
|
-
except Exception:
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
56
|
with client.context(request.path) as varanus:
|
|
60
57
|
setattr(request, client.request_attr, varanus)
|
|
61
58
|
response = self.get_response(request)
|
|
62
59
|
# TODO: any need for request tags separate from context tags?
|
|
63
60
|
varanus.request = Request(
|
|
64
61
|
host=request.get_host(),
|
|
65
|
-
method=request.method or "",
|
|
66
62
|
path=request.path,
|
|
67
|
-
|
|
63
|
+
method=request.method or "",
|
|
68
64
|
status=response.status_code,
|
|
69
65
|
headers=request_headers(request),
|
|
70
66
|
size=(
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from urllib.parse import SplitResult
|
|
3
|
+
|
|
4
|
+
from django.db import transaction
|
|
5
|
+
|
|
6
|
+
from varanus import events
|
|
7
|
+
from varanus.server import models
|
|
8
|
+
|
|
9
|
+
from .base import BaseTransport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ModelTransport(BaseTransport):
|
|
13
|
+
def __init__(self, url: SplitResult, environment: str):
|
|
14
|
+
self.environment = environment
|
|
15
|
+
|
|
16
|
+
def ping(self, info: events.NodeInfo):
|
|
17
|
+
site = models.Site.objects.get()
|
|
18
|
+
models.Node.update(info, site=site, environment=self.environment)
|
|
19
|
+
|
|
20
|
+
@transaction.atomic
|
|
21
|
+
def send(self, event: events.Context):
|
|
22
|
+
site = models.Site.objects.get()
|
|
23
|
+
models.Context.from_event(
|
|
24
|
+
event,
|
|
25
|
+
event_id=uuid.uuid4(),
|
|
26
|
+
site=site,
|
|
27
|
+
environment=self.environment,
|
|
28
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
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)
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import inspect
|
|
2
1
|
import logging
|
|
3
2
|
from datetime import datetime, timezone
|
|
4
3
|
|
|
5
4
|
from msgspec import Struct, field
|
|
6
5
|
|
|
7
|
-
from .utils import safe_repr
|
|
8
|
-
|
|
9
6
|
|
|
10
7
|
def now():
|
|
11
8
|
return datetime.now(tz=timezone.utc)
|
|
@@ -19,50 +16,23 @@ class Event(Struct, kw_only=True, omit_defaults=True):
|
|
|
19
16
|
class NodeInfo(Struct):
|
|
20
17
|
name: str
|
|
21
18
|
platform: str
|
|
22
|
-
|
|
23
|
-
language_version: str
|
|
24
|
-
framework: str
|
|
25
|
-
framework_version: str
|
|
19
|
+
python_version: str
|
|
26
20
|
packages: dict[str, str]
|
|
27
|
-
settings: dict[str, str]
|
|
28
|
-
environment: dict[str, str]
|
|
29
|
-
version: str = ""
|
|
30
21
|
|
|
31
22
|
|
|
32
|
-
class
|
|
23
|
+
class ErrorLine(Struct):
|
|
33
24
|
file: str | None
|
|
34
25
|
lineno: int | None
|
|
35
26
|
function: str | None
|
|
36
27
|
module: str | None
|
|
37
|
-
linesrc: str | None
|
|
38
28
|
locals: dict[str, str]
|
|
39
29
|
|
|
40
30
|
|
|
41
|
-
def capture_stack(skip: int = 0, include_locals: bool = False) -> list[StackLine]:
|
|
42
|
-
lines = []
|
|
43
|
-
for frame in inspect.stack()[skip + 1 :]:
|
|
44
|
-
lines.append(
|
|
45
|
-
StackLine(
|
|
46
|
-
file=frame.filename,
|
|
47
|
-
lineno=frame.lineno,
|
|
48
|
-
linesrc=frame.code_context[0].strip() if frame.code_context else "",
|
|
49
|
-
function=frame.function,
|
|
50
|
-
module=frame.frame.f_globals.get("__name__", ""),
|
|
51
|
-
locals=(
|
|
52
|
-
{name: safe_repr(val) for name, val in frame.frame.f_locals.items()}
|
|
53
|
-
if include_locals
|
|
54
|
-
else {}
|
|
55
|
-
),
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
return lines
|
|
59
|
-
|
|
60
|
-
|
|
61
31
|
class Error(Event):
|
|
62
32
|
kind: str
|
|
63
33
|
module: str
|
|
64
34
|
message: str
|
|
65
|
-
|
|
35
|
+
lines: list[ErrorLine] = []
|
|
66
36
|
|
|
67
37
|
@classmethod
|
|
68
38
|
def from_exception(cls, exc_info, tags=None):
|
|
@@ -82,22 +52,14 @@ class Error(Event):
|
|
|
82
52
|
abs_path = f_code.co_filename if f_code else None
|
|
83
53
|
function = f_code.co_name if f_code else None
|
|
84
54
|
lineno = getattr(tb, "tb_lineno", None)
|
|
85
|
-
linesrc = None
|
|
86
|
-
if f_code and lineno:
|
|
87
|
-
try:
|
|
88
|
-
source, start = inspect.getsourcelines(f_code)
|
|
89
|
-
linesrc = source[lineno - start].strip()
|
|
90
|
-
except (OSError, TypeError):
|
|
91
|
-
pass
|
|
92
55
|
module = f_globals.get("__name__", "")
|
|
93
56
|
lines.append(
|
|
94
|
-
|
|
57
|
+
ErrorLine(
|
|
95
58
|
file=abs_path,
|
|
96
59
|
lineno=lineno,
|
|
97
60
|
function=function,
|
|
98
61
|
module=module,
|
|
99
|
-
|
|
100
|
-
locals={name: safe_repr(val) for name, val in f_locals.items()},
|
|
62
|
+
locals={name: repr(val) for name, val in f_locals.items()},
|
|
101
63
|
)
|
|
102
64
|
)
|
|
103
65
|
tb = tb.tb_next
|
|
@@ -106,7 +68,7 @@ class Error(Event):
|
|
|
106
68
|
kind=kind,
|
|
107
69
|
module=module,
|
|
108
70
|
message=message,
|
|
109
|
-
|
|
71
|
+
lines=lines,
|
|
110
72
|
)
|
|
111
73
|
|
|
112
74
|
|
|
@@ -154,8 +116,8 @@ class Metric(Event):
|
|
|
154
116
|
agg_count: int = 0
|
|
155
117
|
agg_sum: float = 0.0
|
|
156
118
|
agg_avg: float = 0.0
|
|
157
|
-
agg_min: float =
|
|
158
|
-
agg_max: float =
|
|
119
|
+
agg_min: float = 0.0
|
|
120
|
+
agg_max: float = 0.0
|
|
159
121
|
|
|
160
122
|
def update(self, value: float):
|
|
161
123
|
self.agg_count += 1
|
|
@@ -172,14 +134,12 @@ class Query(Event):
|
|
|
172
134
|
db: str
|
|
173
135
|
elapsed_ms: int
|
|
174
136
|
success: bool
|
|
175
|
-
stack: list[StackLine] = []
|
|
176
137
|
|
|
177
138
|
|
|
178
139
|
class Request(Event):
|
|
179
140
|
host: str
|
|
180
|
-
method: str
|
|
181
141
|
path: str
|
|
182
|
-
|
|
142
|
+
method: str
|
|
183
143
|
status: int
|
|
184
144
|
headers: dict = {}
|
|
185
145
|
size: int | None = None
|