PyObservability 1.0.0__py3-none-any.whl → 1.1.0__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.
- pyobservability/__init__.py +1 -1
- pyobservability/config/enums.py +18 -0
- pyobservability/config/settings.py +52 -15
- pyobservability/main.py +40 -3
- pyobservability/monitor.py +48 -6
- pyobservability/transport.py +13 -2
- pyobservability/version.py +1 -1
- {pyobservability-1.0.0.dist-info → pyobservability-1.1.0.dist-info}/METADATA +19 -17
- pyobservability-1.1.0.dist-info/RECORD +16 -0
- pyobservability-1.0.0.dist-info/RECORD +0 -16
- {pyobservability-1.0.0.dist-info → pyobservability-1.1.0.dist-info}/WHEEL +0 -0
- {pyobservability-1.0.0.dist-info → pyobservability-1.1.0.dist-info}/entry_points.txt +0 -0
- {pyobservability-1.0.0.dist-info → pyobservability-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-1.0.0.dist-info → pyobservability-1.1.0.dist-info}/top_level.txt +0 -0
pyobservability/__init__.py
CHANGED
|
@@ -32,7 +32,7 @@ def _cli() -> None:
|
|
|
32
32
|
)
|
|
33
33
|
args = [arg.lower() for arg in sys.argv[1:]]
|
|
34
34
|
try:
|
|
35
|
-
assert len(args)
|
|
35
|
+
assert len(args) >= 1
|
|
36
36
|
except (IndexError, AttributeError, AssertionError):
|
|
37
37
|
print(f"Cannot proceed without a valid arbitrary command. Please choose from {choices}")
|
|
38
38
|
exit(1)
|
pyobservability/config/enums.py
CHANGED
|
@@ -2,5 +2,23 @@ from enum import StrEnum
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class APIEndpoints(StrEnum):
|
|
5
|
+
"""API endpoints for all the routes.
|
|
6
|
+
|
|
7
|
+
>>> APIEndpoints
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
health = "/health"
|
|
5
12
|
root = "/"
|
|
6
13
|
ws = "/ws"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Log(StrEnum):
|
|
17
|
+
"""Log output options.
|
|
18
|
+
|
|
19
|
+
>>> Log
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
file = "file"
|
|
24
|
+
stdout = "stdout"
|
|
@@ -5,12 +5,38 @@ import socket
|
|
|
5
5
|
from typing import Any, Dict, List
|
|
6
6
|
|
|
7
7
|
import yaml
|
|
8
|
-
from pydantic import BaseModel, Field, FilePath, HttpUrl, PositiveInt
|
|
8
|
+
from pydantic import BaseModel, Field, FilePath, HttpUrl, PositiveInt, DirectoryPath
|
|
9
9
|
from pydantic.aliases import AliasChoices
|
|
10
10
|
from pydantic_settings import BaseSettings
|
|
11
11
|
|
|
12
|
+
from pyobservability.config import enums
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
def detailed_log_config(filename: str | None = None, debug: bool = False) -> Dict[str, Any]:
|
|
16
|
+
"""Generate a detailed logging configuration.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
filename: Optional log file name. If None, logs to stdout.
|
|
20
|
+
debug: If True, sets log level to DEBUG, else INFO.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict[str, Any]:
|
|
24
|
+
Returns the logging configuration dictionary.
|
|
25
|
+
"""
|
|
26
|
+
if filename:
|
|
27
|
+
log_handler = {
|
|
28
|
+
"class": "logging.FileHandler",
|
|
29
|
+
"formatter": "default",
|
|
30
|
+
"filename": filename,
|
|
31
|
+
"mode": "a",
|
|
32
|
+
}
|
|
33
|
+
else:
|
|
34
|
+
log_handler = {
|
|
35
|
+
"class": "logging.StreamHandler",
|
|
36
|
+
"formatter": "default",
|
|
37
|
+
"stream": "ext://sys.stdout",
|
|
38
|
+
}
|
|
39
|
+
level = "DEBUG" if debug else "INFO"
|
|
14
40
|
return {
|
|
15
41
|
"version": 1,
|
|
16
42
|
"disable_existing_loggers": False,
|
|
@@ -20,19 +46,13 @@ def detailed_log_config() -> Dict[str, Any]:
|
|
|
20
46
|
"datefmt": "%b-%d-%Y %I:%M:%S %p",
|
|
21
47
|
}
|
|
22
48
|
},
|
|
23
|
-
"handlers": {
|
|
24
|
-
"default": {
|
|
25
|
-
"class": "logging.StreamHandler",
|
|
26
|
-
"formatter": "default",
|
|
27
|
-
"stream": "ext://sys.stdout",
|
|
28
|
-
}
|
|
29
|
-
},
|
|
49
|
+
"handlers": {"default": log_handler},
|
|
30
50
|
"loggers": {
|
|
31
|
-
"uvicorn": {"handlers": ["default"], "level":
|
|
32
|
-
"uvicorn.error": {"handlers": ["default"], "level":
|
|
33
|
-
"uvicorn.access": {"handlers": ["default"], "level":
|
|
51
|
+
"uvicorn": {"handlers": ["default"], "level": level},
|
|
52
|
+
"uvicorn.error": {"handlers": ["default"], "level": level, "propagate": False},
|
|
53
|
+
"uvicorn.access": {"handlers": ["default"], "level": level, "propagate": False},
|
|
34
54
|
},
|
|
35
|
-
"root": {"handlers": ["default"], "level":
|
|
55
|
+
"root": {"handlers": ["default"], "level": level},
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
|
|
@@ -52,11 +72,24 @@ class PydanticEnvConfig(BaseSettings):
|
|
|
52
72
|
dotenv_settings,
|
|
53
73
|
file_secret_settings,
|
|
54
74
|
):
|
|
55
|
-
"""
|
|
56
|
-
|
|
75
|
+
"""Customize the order of settings sources."""
|
|
76
|
+
# Precedence (last wins):
|
|
77
|
+
# env < dotenv < file secrets < init
|
|
78
|
+
return (
|
|
79
|
+
env_settings,
|
|
80
|
+
dotenv_settings,
|
|
81
|
+
file_secret_settings,
|
|
82
|
+
init_settings,
|
|
83
|
+
)
|
|
57
84
|
|
|
58
85
|
|
|
59
86
|
class MonitorTarget(BaseModel):
|
|
87
|
+
"""Model representing a monitoring target.
|
|
88
|
+
|
|
89
|
+
>>> MonitorTarget
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
60
93
|
name: str
|
|
61
94
|
base_url: HttpUrl
|
|
62
95
|
apikey: str
|
|
@@ -89,6 +122,9 @@ class EnvConfig(PydanticEnvConfig):
|
|
|
89
122
|
targets: List[MonitorTarget] = Field(..., validation_alias=alias_choices("TARGETS"))
|
|
90
123
|
interval: PositiveInt = Field(3, validation_alias=alias_choices("INTERVAL"))
|
|
91
124
|
|
|
125
|
+
log: enums.Log | None = None
|
|
126
|
+
logs_path: str = "logs"
|
|
127
|
+
debug: bool = False
|
|
92
128
|
log_config: Dict[str, Any] | FilePath | None = None
|
|
93
129
|
|
|
94
130
|
username: str | None = Field(None, validation_alias=alias_choices("USERNAME"))
|
|
@@ -99,6 +135,7 @@ class EnvConfig(PydanticEnvConfig):
|
|
|
99
135
|
|
|
100
136
|
env_prefix = ""
|
|
101
137
|
extra = "forbid"
|
|
138
|
+
hide_input_in_errors = True
|
|
102
139
|
|
|
103
140
|
@classmethod
|
|
104
141
|
def from_env_file(cls, filename: pathlib.Path) -> "EnvConfig":
|
pyobservability/main.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import os
|
|
2
3
|
import pathlib
|
|
3
4
|
import warnings
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Dict
|
|
4
7
|
|
|
5
8
|
import uiauth
|
|
6
9
|
import uvicorn
|
|
@@ -32,23 +35,47 @@ async def index(request: Request):
|
|
|
32
35
|
|
|
33
36
|
Args:
|
|
34
37
|
request: FastAPI request object.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
TemplateResponse:
|
|
41
|
+
Rendered HTML template with targets and version.
|
|
35
42
|
"""
|
|
36
43
|
return templates.TemplateResponse(
|
|
37
44
|
"index.html", {"request": request, "targets": settings.env.targets, "version": __version__}
|
|
38
45
|
)
|
|
39
46
|
|
|
40
47
|
|
|
41
|
-
def
|
|
48
|
+
async def health() -> Dict[str, str]:
|
|
49
|
+
"""Health check endpoint.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
dict:
|
|
53
|
+
Health status.
|
|
54
|
+
"""
|
|
55
|
+
return {"status": "ok"}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def include_routes() -> None:
|
|
59
|
+
"""Include routes in the FastAPI app with or without authentication."""
|
|
60
|
+
PyObservability.routes.append(
|
|
61
|
+
APIRoute(
|
|
62
|
+
path=enums.APIEndpoints.health,
|
|
63
|
+
endpoint=health,
|
|
64
|
+
methods=["GET"],
|
|
65
|
+
include_in_schema=False,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
42
68
|
if all((settings.env.username, settings.env.password)):
|
|
43
69
|
uiauth.protect(
|
|
44
70
|
app=PyObservability,
|
|
45
71
|
username=settings.env.username,
|
|
46
72
|
password=settings.env.password,
|
|
73
|
+
custom_logger=LOGGER,
|
|
47
74
|
params=[
|
|
48
75
|
uiauth.Parameters(
|
|
49
76
|
path=enums.APIEndpoints.root,
|
|
50
77
|
function=index,
|
|
51
|
-
methods=[
|
|
78
|
+
methods=[uiauth.enums.APIMethods.GET],
|
|
52
79
|
),
|
|
53
80
|
uiauth.Parameters(
|
|
54
81
|
path=enums.APIEndpoints.ws,
|
|
@@ -75,7 +102,8 @@ def include_routes():
|
|
|
75
102
|
)
|
|
76
103
|
|
|
77
104
|
|
|
78
|
-
def start(**kwargs):
|
|
105
|
+
def start(**kwargs) -> None:
|
|
106
|
+
"""Start the FastAPI app with Uvicorn server."""
|
|
79
107
|
settings.env = settings.env_loader(**kwargs)
|
|
80
108
|
settings.env.targets = [{k: str(v) for k, v in target.model_dump().items()} for target in settings.env.targets]
|
|
81
109
|
settings.targets_by_url = {t["base_url"]: t for t in settings.env.targets}
|
|
@@ -85,6 +113,15 @@ def start(**kwargs):
|
|
|
85
113
|
port=settings.env.port,
|
|
86
114
|
app=PyObservability,
|
|
87
115
|
)
|
|
116
|
+
if settings.env.log:
|
|
117
|
+
if settings.env.log == enums.Log.stdout:
|
|
118
|
+
uvicorn_args["log_config"] = settings.detailed_log_config(debug=settings.env.debug)
|
|
119
|
+
else:
|
|
120
|
+
logs_path = pathlib.Path(settings.env.logs_path)
|
|
121
|
+
log_file = logs_path / f"pyobservability_{datetime.now():%d-%m-%Y}.log"
|
|
122
|
+
logs_path.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
uvicorn_args["log_config"] = settings.detailed_log_config(filename=log_file.resolve(), debug=settings.env.debug)
|
|
124
|
+
# log_config will take precedence if both log and log_config are set
|
|
88
125
|
if settings.env.log_config:
|
|
89
126
|
uvicorn_args["log_config"] = (
|
|
90
127
|
settings.env.log_config if isinstance(settings.env.log_config, dict) else str(settings.env.log_config)
|
pyobservability/monitor.py
CHANGED
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
from asyncio import CancelledError
|
|
5
5
|
from collections.abc import Generator
|
|
6
|
-
from typing import Any, Dict, List
|
|
6
|
+
from typing import Any, AsyncGenerator, Dict, List
|
|
7
7
|
|
|
8
8
|
import aiohttp
|
|
9
9
|
|
|
@@ -14,6 +14,15 @@ OBS_PATH = "/observability"
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def refine_service(service_list: List[Dict[str, Any]]) -> Generator[Dict[str, Dict[str, str]]]:
|
|
17
|
+
"""Refine service stats to only include relevant fields and round CPU values.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
service_list: List of service statistics dictionaries.
|
|
21
|
+
|
|
22
|
+
Yields:
|
|
23
|
+
Dict[str, Dict[str, str]]:
|
|
24
|
+
Refined service statistics dictionary.
|
|
25
|
+
"""
|
|
17
26
|
for service in service_list:
|
|
18
27
|
service["memory"] = dict(rss=service.get("memory", {}).get("rss"), vms=service.get("memory", {}).get("vms"))
|
|
19
28
|
service["cpu"] = dict(
|
|
@@ -24,7 +33,18 @@ def refine_service(service_list: List[Dict[str, Any]]) -> Generator[Dict[str, Di
|
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
class Monitor:
|
|
36
|
+
"""Monitor class to stream observability data from a target.
|
|
37
|
+
|
|
38
|
+
>>> Monitor
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
27
42
|
def __init__(self, target: Dict[str, str]):
|
|
43
|
+
"""Initialize Monitor with target configuration.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
target: Dictionary containing target configuration with keys 'name', 'base_url', and 'apikey'.
|
|
47
|
+
"""
|
|
28
48
|
self.name = target["name"]
|
|
29
49
|
self.base_url = target["base_url"]
|
|
30
50
|
self.apikey = target["apikey"]
|
|
@@ -40,11 +60,22 @@ class Monitor:
|
|
|
40
60
|
# SUBSCRIBE / UNSUBSCRIBE
|
|
41
61
|
# ------------------------------
|
|
42
62
|
def subscribe(self) -> asyncio.Queue:
|
|
63
|
+
"""Subscribe to the monitor's data stream.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
asyncio.Queue:
|
|
67
|
+
Queue to receive streamed data.
|
|
68
|
+
"""
|
|
43
69
|
q = asyncio.Queue(maxsize=10)
|
|
44
70
|
self._ws_subscribers.append(q)
|
|
45
71
|
return q
|
|
46
72
|
|
|
47
|
-
def unsubscribe(self, q: asyncio.Queue):
|
|
73
|
+
def unsubscribe(self, q: asyncio.Queue) -> None:
|
|
74
|
+
"""Unsubscribe from the monitor's data stream.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
q: Queue to be removed from subscribers.
|
|
78
|
+
"""
|
|
48
79
|
if q in self._ws_subscribers:
|
|
49
80
|
self._ws_subscribers.remove(q)
|
|
50
81
|
|
|
@@ -52,6 +83,7 @@ class Monitor:
|
|
|
52
83
|
# START / STOP
|
|
53
84
|
# ------------------------------
|
|
54
85
|
async def start(self):
|
|
86
|
+
"""Start the monitor's data streaming."""
|
|
55
87
|
if self._task:
|
|
56
88
|
return # already running
|
|
57
89
|
|
|
@@ -61,6 +93,7 @@ class Monitor:
|
|
|
61
93
|
self._task = asyncio.create_task(self._stream_target())
|
|
62
94
|
|
|
63
95
|
async def stop(self):
|
|
96
|
+
"""Stop the monitor's data streaming."""
|
|
64
97
|
self._stop.set()
|
|
65
98
|
if self._task:
|
|
66
99
|
self._task.cancel()
|
|
@@ -74,7 +107,8 @@ class Monitor:
|
|
|
74
107
|
await self.session.close()
|
|
75
108
|
self.session = None
|
|
76
109
|
|
|
77
|
-
async def update_flags(self, **kwargs):
|
|
110
|
+
async def update_flags(self, **kwargs) -> None:
|
|
111
|
+
"""Update monitor flags and restart the stream."""
|
|
78
112
|
for k, v in kwargs.items():
|
|
79
113
|
if k in self.flags:
|
|
80
114
|
self.flags[k] = v
|
|
@@ -82,14 +116,21 @@ class Monitor:
|
|
|
82
116
|
# restart stream with new params
|
|
83
117
|
await self._restart_stream()
|
|
84
118
|
|
|
85
|
-
async def _restart_stream(self):
|
|
119
|
+
async def _restart_stream(self) -> None:
|
|
120
|
+
"""Restart the monitor's data streaming."""
|
|
86
121
|
await self.stop()
|
|
87
122
|
await self.start()
|
|
88
123
|
|
|
89
124
|
# ------------------------------
|
|
90
125
|
# FETCH STREAM
|
|
91
126
|
# ------------------------------
|
|
92
|
-
async def _fetch_stream(self):
|
|
127
|
+
async def _fetch_stream(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
128
|
+
"""Fetch the observability data stream from the target.
|
|
129
|
+
|
|
130
|
+
Yields:
|
|
131
|
+
Dict[str, Any]:
|
|
132
|
+
Parsed observability data.
|
|
133
|
+
"""
|
|
93
134
|
query = f"?interval={settings.env.interval}"
|
|
94
135
|
if self.flags["all_services"]:
|
|
95
136
|
query += "&all_services=true"
|
|
@@ -122,7 +163,8 @@ class Monitor:
|
|
|
122
163
|
# ------------------------------
|
|
123
164
|
# STREAM LOOP
|
|
124
165
|
# ------------------------------
|
|
125
|
-
async def _stream_target(self):
|
|
166
|
+
async def _stream_target(self) -> None:
|
|
167
|
+
"""Stream observability data from the target and notify subscribers."""
|
|
126
168
|
errors = {}
|
|
127
169
|
while not self._stop.is_set():
|
|
128
170
|
try:
|
pyobservability/transport.py
CHANGED
|
@@ -10,13 +10,24 @@ from pyobservability.monitor import Monitor
|
|
|
10
10
|
LOGGER = logging.getLogger("uvicorn.default")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
async def _forward_metrics(websocket: WebSocket, q: asyncio.Queue):
|
|
13
|
+
async def _forward_metrics(websocket: WebSocket, q: asyncio.Queue) -> None:
|
|
14
|
+
"""Forward metrics from the monitor's queue to the websocket.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
websocket: FastAPI WebSocket connection.
|
|
18
|
+
q: asyncio.Queue to receive metrics from the monitor.
|
|
19
|
+
"""
|
|
14
20
|
while True:
|
|
15
21
|
payload = await q.get()
|
|
16
22
|
await websocket.send_json(payload)
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
async def websocket_endpoint(websocket: WebSocket):
|
|
25
|
+
async def websocket_endpoint(websocket: WebSocket) -> None:
|
|
26
|
+
"""Websocket endpoint to handle observability data streaming.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
websocket: FastAPI WebSocket connection.
|
|
30
|
+
"""
|
|
20
31
|
await websocket.accept()
|
|
21
32
|
|
|
22
33
|
monitor: Monitor | None = None
|
pyobservability/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.
|
|
1
|
+
__version__ = "1.1.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyObservability
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Lightweight OS-agnostic observability UI for PyNinja
|
|
5
5
|
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -29,11 +29,11 @@ Project-URL: Homepage, https://github.com/thevickypedia/PyObservability
|
|
|
29
29
|
Project-URL: Docs, https://thevickypedia.github.io/PyObservability
|
|
30
30
|
Project-URL: Source, https://github.com/thevickypedia/PyObservability
|
|
31
31
|
Project-URL: Bug Tracker, https://github.com/thevickypedia/PyObservability/issues
|
|
32
|
-
Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.
|
|
32
|
+
Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.md
|
|
33
33
|
Keywords: PyObservability,observability,system-monitor,PyNinja
|
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
|
35
35
|
Classifier: Programming Language :: Python :: 3
|
|
36
|
-
Classifier: Development Status ::
|
|
36
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
37
37
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
38
38
|
Classifier: Operating System :: Microsoft :: Windows
|
|
39
39
|
Classifier: Operating System :: POSIX :: Linux
|
|
@@ -50,10 +50,7 @@ Requires-Dist: pydantic-settings==2.12.*
|
|
|
50
50
|
Requires-Dist: python-dotenv==1.2.*
|
|
51
51
|
Requires-Dist: uvicorn[standard]==0.38.*
|
|
52
52
|
Provides-Extra: dev
|
|
53
|
-
Requires-Dist: sphinx==5.1.1; extra == "dev"
|
|
54
53
|
Requires-Dist: pre-commit; extra == "dev"
|
|
55
|
-
Requires-Dist: recommonmark; extra == "dev"
|
|
56
|
-
Requires-Dist: gitverse; extra == "dev"
|
|
57
54
|
Dynamic: license-file
|
|
58
55
|
|
|
59
56
|
# PyObservability
|
|
@@ -69,6 +66,7 @@ Dynamic: license-file
|
|
|
69
66
|
[![pypi][label-actions-pypi]][gha_pypi]
|
|
70
67
|
[![notes][label-actions-notes]][gha_notes]
|
|
71
68
|
[![release][label-actions-release]][gha_release]
|
|
69
|
+
[![docker][label-actions-docker]][gha_docker]
|
|
72
70
|
|
|
73
71
|
[![Pypi][label-pypi]][pypi]
|
|
74
72
|
[![Pypi-format][label-pypi-format]][pypi-files]
|
|
@@ -102,13 +100,25 @@ pyobservability start
|
|
|
102
100
|
|
|
103
101
|
> Use `pyobservability --help` for usage instructions.
|
|
104
102
|
|
|
103
|
+
**Containerized Deployment**
|
|
104
|
+
```shell
|
|
105
|
+
docker pull thevickypedia/pyobservability:latest
|
|
106
|
+
|
|
107
|
+
docker run \
|
|
108
|
+
--name observability \
|
|
109
|
+
-p 8080:80 \
|
|
110
|
+
-v /home/user/config:/config \
|
|
111
|
+
--restart=no \
|
|
112
|
+
thevickypedia/pyobservability
|
|
113
|
+
```
|
|
114
|
+
|
|
105
115
|
## Environment Variables
|
|
106
116
|
|
|
107
117
|
<details>
|
|
108
118
|
<summary><strong>Sourcing environment variables from an env file</strong></summary>
|
|
109
119
|
|
|
110
120
|
> _By default, `PyObservability` will look for a `.env` file in the current working directory._
|
|
111
|
-
> _Other file options are supported with a custom kwarg or env var `env_file` pointing to the filepath._
|
|
121
|
+
> _Other file options (like JSON and YAML) are supported with a custom kwarg or env var `env_file` pointing to the filepath._
|
|
112
122
|
</details>
|
|
113
123
|
|
|
114
124
|
**Mandatory**
|
|
@@ -142,22 +152,14 @@ Licensed under the [MIT License][license]
|
|
|
142
152
|
[label-pypi-status]: https://img.shields.io/pypi/status/PyObservability
|
|
143
153
|
[label-actions-notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml/badge.svg
|
|
144
154
|
[label-actions-release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml/badge.svg
|
|
155
|
+
[label-actions-docker]: https://github.com/thevickypedia/PyObservability/actions/workflows/docker.yml/badge.svg
|
|
145
156
|
|
|
146
157
|
[3.11]: https://docs.python.org/3/whatsnew/3.11.html
|
|
147
158
|
[virtual environment]: https://docs.python.org/3/tutorial/venv.html
|
|
148
|
-
[release-notes]: https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
|
|
149
159
|
[gha_pypi]: https://github.com/thevickypedia/PyObservability/actions/workflows/python-publish.yml
|
|
150
160
|
[gha_notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml
|
|
151
161
|
[gha_release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml
|
|
152
|
-
[
|
|
153
|
-
[pep8]: https://www.python.org/dev/peps/pep-0008/
|
|
154
|
-
[isort]: https://pycqa.github.io/isort/
|
|
155
|
-
[sphinx]: https://www.sphinx-doc.org/en/master/man/sphinx-autogen.html
|
|
162
|
+
[gha_docker]: https://github.com/thevickypedia/PyObservability/actions/workflows/docker.yml
|
|
156
163
|
[pypi]: https://pypi.org/project/PyObservability
|
|
157
164
|
[pypi-files]: https://pypi.org/project/PyObservability/#files
|
|
158
|
-
[pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/
|
|
159
165
|
[license]: https://github.com/thevickypedia/PyObservability/blob/main/LICENSE
|
|
160
|
-
[runbook]: https://thevickypedia.github.io/PyObservability/
|
|
161
|
-
[samples]: https://github.com/thevickypedia/PyObservability/tree/main/samples
|
|
162
|
-
[PyUdisk]: https://github.com/thevickypedia/PyUdisk
|
|
163
|
-
[PyArchitecture]: https://github.com/thevickypedia/PyArchitecture
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
|
|
2
|
+
pyobservability/main.py,sha256=WC57zDTv588aNPViV352WcWmJxURF5EvY9X26roNuW0,4316
|
|
3
|
+
pyobservability/monitor.py,sha256=Y38zDJFPDbQqz_2jQcNmJBnRpeobC_SV2uhN-13e9RU,7591
|
|
4
|
+
pyobservability/transport.py,sha256=zHLAodX20bKkPOuacKjzv1Dqj3JbNAB75o1ABwzum0U,2534
|
|
5
|
+
pyobservability/version.py,sha256=LGVQyDsWifdACo7qztwb8RWWHds1E7uQ-ZqD8SAjyw4,22
|
|
6
|
+
pyobservability/config/enums.py,sha256=EhvD9kB5EMW3ARxr5KmISmf-rP3D4IKqOIjw6Tb8SB8,294
|
|
7
|
+
pyobservability/config/settings.py,sha256=adeTQCV5_bf_8zBggkdIXniyXsbqJYAAUupB2tvOVNI,5820
|
|
8
|
+
pyobservability/static/app.js,sha256=6hjFy2jt4ndYJUI1DZT08CpuxHyWj9iSAZ2vxaTqH2A,20664
|
|
9
|
+
pyobservability/static/styles.css,sha256=dnYSXNeXd6Ohu4h8sJCO87vzPbkYcw9XVGGwB8IJbxw,4703
|
|
10
|
+
pyobservability/templates/index.html,sha256=2aQdb0QlDY5Hboev-_lJPlpnGxiC6h3fp0FlvL72S9k,5227
|
|
11
|
+
pyobservability-1.1.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
12
|
+
pyobservability-1.1.0.dist-info/METADATA,sha256=KZoudD6ox48fOOmhPEW7WXPgYO26bps3C9kq68wkFy0,6539
|
|
13
|
+
pyobservability-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
pyobservability-1.1.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
15
|
+
pyobservability-1.1.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
16
|
+
pyobservability-1.1.0.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
|
|
2
|
-
pyobservability/main.py,sha256=Ty0bS7ZWyKXuo-xuKoFQESvT-B-19W04d_Nk0KveNtg,3004
|
|
3
|
-
pyobservability/monitor.py,sha256=4Xd8k7gcOmHM-WvQpgFiDVGtzMu_RpFPZXPPoz4GoA4,6224
|
|
4
|
-
pyobservability/transport.py,sha256=FyzJAMZPn7JUZIGgxnSw3on1K6T4ciZE1EuGdAcxt_w,2188
|
|
5
|
-
pyobservability/version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
6
|
-
pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
|
|
7
|
-
pyobservability/config/settings.py,sha256=BjTZnkGS1pZKbN3Fq_5HQX8C6oEjWn_gVJLXcZZ6EWE,4853
|
|
8
|
-
pyobservability/static/app.js,sha256=6hjFy2jt4ndYJUI1DZT08CpuxHyWj9iSAZ2vxaTqH2A,20664
|
|
9
|
-
pyobservability/static/styles.css,sha256=dnYSXNeXd6Ohu4h8sJCO87vzPbkYcw9XVGGwB8IJbxw,4703
|
|
10
|
-
pyobservability/templates/index.html,sha256=2aQdb0QlDY5Hboev-_lJPlpnGxiC6h3fp0FlvL72S9k,5227
|
|
11
|
-
pyobservability-1.0.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
12
|
-
pyobservability-1.0.0.dist-info/METADATA,sha256=4Wj2FH4KYhsDbEntXxnMginYE9QR9AluZyYLMHvwhbg,6822
|
|
13
|
-
pyobservability-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
pyobservability-1.0.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
15
|
-
pyobservability-1.0.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
16
|
-
pyobservability-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|