plain.observer 0.0.0__tar.gz → 0.1.0__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.
Potentially problematic release.
This version of plain.observer might be problematic. Click here for more details.
- {plain_observer-0.0.0 → plain_observer-0.1.0}/PKG-INFO +1 -1
- plain_observer-0.1.0/plain/observer/CHANGELOG.md +15 -0
- plain_observer-0.1.0/plain/observer/config.py +44 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/otel.py +56 -2
- {plain_observer-0.0.0 → plain_observer-0.1.0}/pyproject.toml +1 -1
- plain_observer-0.0.0/plain/observer/CHANGELOG.md +0 -1
- plain_observer-0.0.0/plain/observer/config.py +0 -36
- {plain_observer-0.0.0 → plain_observer-0.1.0}/.gitignore +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/LICENSE +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/README.md +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/README.md +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/__init__.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/admin.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/cli.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/core.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/default_settings.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/migrations/0001_initial.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/migrations/__init__.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/models.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/admin/observer/trace_detail.html +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/observer/_trace_detail.html +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/observer/traces.html +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/toolbar/observer.html +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/toolbar/observer_button.html +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/urls.py +0 -0
- {plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/views.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# plain-observer changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0](https://github.com/dropseed/plain/releases/plain-observer@0.1.0) (2025-07-19)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- Initial release of plain-observer package providing OpenTelemetry-based observability and monitoring for Plain applications ([b0224d0](https://github.com/dropseed/plain/commit/b0224d0418))
|
|
8
|
+
- Added real-time trace monitoring with summary and persist modes via signed cookies ([b0224d0](https://github.com/dropseed/plain/commit/b0224d0418))
|
|
9
|
+
- Added admin interface for viewing detailed trace information and spans ([b0224d0](https://github.com/dropseed/plain/commit/b0224d0418))
|
|
10
|
+
- Added toolbar integration showing performance summaries for current requests ([b0224d0](https://github.com/dropseed/plain/commit/b0224d0418))
|
|
11
|
+
- Observer can now combine with existing OpenTelemetry trace providers instead of replacing them ([7e55779](https://github.com/dropseed/plain/commit/7e55779548))
|
|
12
|
+
|
|
13
|
+
### Upgrade instructions
|
|
14
|
+
|
|
15
|
+
- No changes required
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from opentelemetry import trace
|
|
2
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
3
|
+
|
|
4
|
+
from plain.packages import PackageConfig, register_config
|
|
5
|
+
|
|
6
|
+
from .otel import (
|
|
7
|
+
ObserverCombinedSampler,
|
|
8
|
+
ObserverSampler,
|
|
9
|
+
ObserverSpanProcessor,
|
|
10
|
+
get_observer_span_processor,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register_config
|
|
15
|
+
class Config(PackageConfig):
|
|
16
|
+
package_label = "plainobserver"
|
|
17
|
+
|
|
18
|
+
def ready(self):
|
|
19
|
+
sampler = ObserverSampler()
|
|
20
|
+
span_processor = ObserverSpanProcessor()
|
|
21
|
+
|
|
22
|
+
if provider := self.get_existing_trace_provider():
|
|
23
|
+
# There is already a trace provider, so combine our sampler
|
|
24
|
+
# and add an additional span processor for Observer
|
|
25
|
+
if hasattr(provider, "sampler"):
|
|
26
|
+
provider.sampler = ObserverCombinedSampler(provider.sampler, sampler)
|
|
27
|
+
|
|
28
|
+
if not get_observer_span_processor():
|
|
29
|
+
provider.add_span_processor(span_processor)
|
|
30
|
+
else:
|
|
31
|
+
# Start our own provider, new sampler, and span processor
|
|
32
|
+
provider = TracerProvider(sampler=sampler)
|
|
33
|
+
provider.add_span_processor(span_processor)
|
|
34
|
+
trace.set_tracer_provider(provider)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_existing_trace_provider():
|
|
38
|
+
"""Return the currently configured provider if set."""
|
|
39
|
+
current_provider = trace.get_tracer_provider()
|
|
40
|
+
if current_provider and not isinstance(
|
|
41
|
+
current_provider, trace.ProxyTracerProvider
|
|
42
|
+
):
|
|
43
|
+
return current_provider
|
|
44
|
+
return None
|
|
@@ -18,7 +18,7 @@ from .core import Observer, ObserverMode
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
21
|
+
def get_observer_span_processor():
|
|
22
22
|
"""Get the span collector instance from the tracer provider."""
|
|
23
23
|
if not (current_provider := trace.get_tracer_provider()):
|
|
24
24
|
return None
|
|
@@ -41,7 +41,7 @@ def get_current_trace_summary() -> str | None:
|
|
|
41
41
|
if not (current_span := trace.get_current_span()):
|
|
42
42
|
return None
|
|
43
43
|
|
|
44
|
-
if not (processor :=
|
|
44
|
+
if not (processor := get_observer_span_processor()):
|
|
45
45
|
return None
|
|
46
46
|
|
|
47
47
|
trace_id = f"0x{format_trace_id(current_span.get_span_context().trace_id)}"
|
|
@@ -126,6 +126,50 @@ class ObserverSampler(sampling.Sampler):
|
|
|
126
126
|
return "ObserverSampler"
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
class ObserverCombinedSampler(sampling.Sampler):
|
|
130
|
+
"""Combine another sampler with ``ObserverSampler``."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, primary: sampling.Sampler, secondary: sampling.Sampler):
|
|
133
|
+
self.primary = primary
|
|
134
|
+
self.secondary = secondary
|
|
135
|
+
|
|
136
|
+
def should_sample(
|
|
137
|
+
self,
|
|
138
|
+
parent_context,
|
|
139
|
+
trace_id,
|
|
140
|
+
name,
|
|
141
|
+
kind: SpanKind | None = None,
|
|
142
|
+
attributes=None,
|
|
143
|
+
links=None,
|
|
144
|
+
trace_state=None,
|
|
145
|
+
):
|
|
146
|
+
result = self.primary.should_sample(
|
|
147
|
+
parent_context,
|
|
148
|
+
trace_id,
|
|
149
|
+
name,
|
|
150
|
+
kind=kind,
|
|
151
|
+
attributes=attributes,
|
|
152
|
+
links=links,
|
|
153
|
+
trace_state=trace_state,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if result.decision is sampling.Decision.DROP:
|
|
157
|
+
return self.secondary.should_sample(
|
|
158
|
+
parent_context,
|
|
159
|
+
trace_id,
|
|
160
|
+
name,
|
|
161
|
+
kind=kind,
|
|
162
|
+
attributes=attributes,
|
|
163
|
+
links=links,
|
|
164
|
+
trace_state=trace_state,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
def get_description(self) -> str:
|
|
170
|
+
return f"ObserverCombinedSampler({self.primary.get_description()}, {self.secondary.get_description()})"
|
|
171
|
+
|
|
172
|
+
|
|
129
173
|
class ObserverSpanProcessor(SpanProcessor):
|
|
130
174
|
"""Collects spans in real-time for current trace performance monitoring.
|
|
131
175
|
|
|
@@ -148,6 +192,9 @@ class ObserverSpanProcessor(SpanProcessor):
|
|
|
148
192
|
}
|
|
149
193
|
)
|
|
150
194
|
self._traces_lock = threading.Lock()
|
|
195
|
+
self._ignore_url_paths = [
|
|
196
|
+
re.compile(p) for p in settings.OBSERVER_IGNORE_URL_PATTERNS
|
|
197
|
+
]
|
|
151
198
|
|
|
152
199
|
def on_start(self, span, parent_context=None):
|
|
153
200
|
"""Called when a span starts."""
|
|
@@ -295,6 +342,13 @@ class ObserverSpanProcessor(SpanProcessor):
|
|
|
295
342
|
)
|
|
296
343
|
|
|
297
344
|
def _get_recording_mode(self, span, parent_context) -> str | None:
|
|
345
|
+
# Again check the span attributes, in case we relied on another sampler
|
|
346
|
+
if span.attributes:
|
|
347
|
+
if url_path := span.attributes.get(url_attributes.URL_PATH, ""):
|
|
348
|
+
for pattern in self._ignore_url_paths:
|
|
349
|
+
if pattern.match(url_path):
|
|
350
|
+
return None
|
|
351
|
+
|
|
298
352
|
# If the span has links, then we are going to export if the linked span is also exported
|
|
299
353
|
for link in span.links:
|
|
300
354
|
if link.context.is_valid and link.context.span_id:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# plain-observer changelog
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from opentelemetry import trace
|
|
2
|
-
from opentelemetry.sdk.trace import TracerProvider
|
|
3
|
-
|
|
4
|
-
from plain.packages import PackageConfig, register_config
|
|
5
|
-
|
|
6
|
-
from .otel import ObserverSampler, ObserverSpanProcessor
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@register_config
|
|
10
|
-
class Config(PackageConfig):
|
|
11
|
-
package_label = "plainobserver"
|
|
12
|
-
|
|
13
|
-
def ready(self):
|
|
14
|
-
if self.has_existing_trace_provider():
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
self.setup_observer()
|
|
18
|
-
|
|
19
|
-
@staticmethod
|
|
20
|
-
def has_existing_trace_provider() -> bool:
|
|
21
|
-
"""Check if there is an existing trace provider."""
|
|
22
|
-
current_provider = trace.get_tracer_provider()
|
|
23
|
-
return current_provider and not isinstance(
|
|
24
|
-
current_provider, trace.ProxyTracerProvider
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
@staticmethod
|
|
28
|
-
def setup_observer() -> None:
|
|
29
|
-
sampler = ObserverSampler()
|
|
30
|
-
provider = TracerProvider(sampler=sampler)
|
|
31
|
-
|
|
32
|
-
# Add our combined processor that handles both memory storage and export
|
|
33
|
-
observer_processor = ObserverSpanProcessor()
|
|
34
|
-
provider.add_span_processor(observer_processor)
|
|
35
|
-
|
|
36
|
-
trace.set_tracer_provider(provider)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/observer/_trace_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
{plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/toolbar/observer.html
RENAMED
|
File without changes
|
{plain_observer-0.0.0 → plain_observer-0.1.0}/plain/observer/templates/toolbar/observer_button.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|