structguru 1.3.0__tar.gz → 1.3.1__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 (43) hide show
  1. {structguru-1.3.0 → structguru-1.3.1}/Cargo.lock +2 -2
  2. {structguru-1.3.0 → structguru-1.3.1}/Cargo.toml +1 -1
  3. {structguru-1.3.0 → structguru-1.3.1}/PKG-INFO +1 -1
  4. {structguru-1.3.0 → structguru-1.3.1}/pyproject.toml +1 -1
  5. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/__init__.py +1 -1
  6. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/exceptions.py +41 -30
  7. {structguru-1.3.0 → structguru-1.3.1}/LICENSE +0 -0
  8. {structguru-1.3.0 → structguru-1.3.1}/README.md +0 -0
  9. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/Cargo.toml +0 -0
  10. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/clock.rs +0 -0
  11. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/filter.rs +0 -0
  12. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/lib.rs +0 -0
  13. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/render.rs +0 -0
  14. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/value.rs +0 -0
  15. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-core/src/worker.rs +0 -0
  16. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-py/Cargo.toml +0 -0
  17. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-py/src/dispatch.rs +0 -0
  18. {structguru-1.3.0 → structguru-1.3.1}/crates/structguru-py/src/lib.rs +0 -0
  19. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/_contextvars.py +0 -0
  20. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/_levels.py +0 -0
  21. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/_native_dispatch.py +0 -0
  22. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/_native_env.py +0 -0
  23. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/_runtime.py +0 -0
  24. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/config.py +0 -0
  25. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/core.py +0 -0
  26. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/__init__.py +0 -0
  27. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/_stdlib_env.py +0 -0
  28. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/_util.py +0 -0
  29. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/asgi.py +0 -0
  30. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/celery.py +0 -0
  31. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/django.py +0 -0
  32. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/flask.py +0 -0
  33. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/grpc.py +0 -0
  34. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/httpx.py +0 -0
  35. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/requests.py +0 -0
  36. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/sentry.py +0 -0
  37. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/sqlalchemy.py +0 -0
  38. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/integrations/stdlib.py +0 -0
  39. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/metrics.py +0 -0
  40. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/otel.py +0 -0
  41. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/py.typed +0 -0
  42. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/redaction.py +0 -0
  43. {structguru-1.3.0 → structguru-1.3.1}/src/structguru/settings.py +0 -0
@@ -245,7 +245,7 @@ dependencies = [
245
245
 
246
246
  [[package]]
247
247
  name = "structguru-core"
248
- version = "1.3.0"
248
+ version = "1.3.1"
249
249
  dependencies = [
250
250
  "fancy-regex",
251
251
  "fastrand",
@@ -257,7 +257,7 @@ dependencies = [
257
257
 
258
258
  [[package]]
259
259
  name = "structguru-py"
260
- version = "1.3.0"
260
+ version = "1.3.1"
261
261
  dependencies = [
262
262
  "pyo3",
263
263
  "regex",
@@ -3,7 +3,7 @@ members = ["crates/structguru-core", "crates/structguru-py"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "1.3.0"
6
+ version = "1.3.1"
7
7
  edition = "2024"
8
8
  rust-version = "1.89"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structguru
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "structguru"
7
- version = "1.3.0"
7
+ version = "1.3.1"
8
8
  description = "Native structured logging with a loguru-style Python API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -23,7 +23,7 @@ from structguru.otel import add_otel_context
23
23
  from structguru.redaction import DEFAULT_SENSITIVE_KEYS
24
24
  from structguru.settings import Settings
25
25
 
26
- __version__ = "1.3.0"
26
+ __version__ = "1.3.1"
27
27
 
28
28
  __all__ = [
29
29
  "DEFAULT_SENSITIVE_KEYS",
@@ -109,37 +109,48 @@ def _build_frames(
109
109
  max_local_repr: int,
110
110
  keys: frozenset[str],
111
111
  ) -> list[dict[str, Any]]:
112
- """Collect bounded traceback frames using the same options for every member."""
113
- frames = []
112
+ """Collect the last ``max_frames`` traceback frames with the same fields either way.
113
+
114
+ The traceback is walked to the first frame that will be kept *before*
115
+ ``traceback.extract_tb()`` runs: it reads and dedents source for every frame
116
+ it visits, so extracting the whole traceback and slicing afterwards paid for
117
+ frames that were then discarded, which made a ``RecursionError`` cost
118
+ hundreds of frames of source lookup for the twenty it reported. Locals, when
119
+ requested, come from the same selected frames, so the ``line`` field is
120
+ present whether or not locals are.
121
+ """
122
+ if max_frames <= 0:
123
+ return []
124
+ depth = 0
125
+ node = exc_tb
126
+ while node is not None:
127
+ depth += 1
128
+ node = node.tb_next
129
+ node = exc_tb
130
+ for _ in range(max(depth - max_frames, 0)):
131
+ node = node.tb_next
132
+
133
+ summaries = traceback.extract_tb(node)
134
+ raw_frames: list[Any] = []
114
135
  if include_locals:
115
- # Walk raw traceback to capture local variables, since
116
- # traceback.extract_tb() does not populate FrameSummary.locals.
117
- raw_frames: list[tuple[Any, int]] = []
118
- tb = exc_tb
119
- while tb is not None:
120
- raw_frames.append((tb.tb_frame, tb.tb_lineno))
121
- tb = tb.tb_next
122
- selected_frames = raw_frames[-max_frames:] if max_frames else []
123
- for frame_obj, lineno in selected_frames:
124
- frame_info: dict[str, Any] = {
125
- "filename": frame_obj.f_code.co_filename,
126
- "lineno": lineno,
127
- "name": frame_obj.f_code.co_name,
128
- "line": None,
129
- "locals": _format_locals(frame_obj.f_locals, keys, max_local_repr),
130
- }
131
- frames.append(frame_info)
132
- else:
133
- extracted_frames = traceback.extract_tb(exc_tb)
134
- selected_summaries = extracted_frames[-max_frames:] if max_frames else []
135
- for fs in selected_summaries:
136
- frame_info = {
137
- "filename": fs.filename,
138
- "lineno": fs.lineno,
139
- "name": fs.name,
140
- "line": fs.line,
141
- }
142
- frames.append(frame_info)
136
+ # ``FrameSummary`` never carries ``f_locals``; pair each summary with
137
+ # its live frame from the same starting node.
138
+ current = node
139
+ while current is not None:
140
+ raw_frames.append(current.tb_frame)
141
+ current = current.tb_next
142
+
143
+ frames: list[dict[str, Any]] = []
144
+ for index, summary in enumerate(summaries):
145
+ frame_info: dict[str, Any] = {
146
+ "filename": summary.filename,
147
+ "lineno": summary.lineno,
148
+ "name": summary.name,
149
+ "line": summary.line,
150
+ }
151
+ if include_locals:
152
+ frame_info["locals"] = _format_locals(raw_frames[index].f_locals, keys, max_local_repr)
153
+ frames.append(frame_info)
143
154
 
144
155
  return frames
145
156
 
File without changes
File without changes