Skip to content

Commit f2372d0

Browse files
Merge pull request #1579 from priya05-git/feature/unified-error-reporting-telemetry-dashboard
Add: undefied error reporting
2 parents abc43af + 986b1c6 commit f2372d0

4 files changed

Lines changed: 205 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
[![Python Version](https://img.shields.io/badge/python-3.10--3.12-blue.svg)](https://www.python.org/downloads/)
1010
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
1111

12+
### 🧪 New: Standardized Error Logging
13+
A shared error logger is now available for mini-projects. It captures exception type, message, timestamp, traceback, and project name into JSONL logs under the logs folder, making failures easier to review and analyze.
14+
15+
[Quick Start](#-quick-start)[Projects](#-projects)[Features](#-features)[Contributing](#-contributing)
1216
<p align="center">
1317
<a href="https://python-mini-project-lovat.vercel.app/">
1418
<img src="https://img.shields.io/badge/live_demo-View%20App-22c55e?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo" />

error_logger.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import traceback
5+
from collections import Counter
6+
from datetime import datetime, timezone
7+
from pathlib import Path
8+
from typing import Any
9+
10+
11+
DEFAULT_LOG_PATH = Path(__file__).resolve().parent / "logs" / "error-logs.jsonl"
12+
13+
14+
def _ensure_log_path(log_path: str | Path | None) -> Path:
15+
path = Path(log_path) if log_path is not None else DEFAULT_LOG_PATH
16+
path.parent.mkdir(parents=True, exist_ok=True)
17+
return path
18+
19+
20+
def log_exception(
21+
project_name: str,
22+
exception: Exception,
23+
log_path: str | Path | None = None,
24+
additional_data: dict[str, Any] | None = None,
25+
) -> dict[str, Any]:
26+
"""Write a structured exception entry to a JSON Lines log file."""
27+
path = _ensure_log_path(log_path)
28+
payload = {
29+
"project_name": project_name,
30+
"exception_type": type(exception).__name__,
31+
"error_message": str(exception),
32+
"timestamp": datetime.now(timezone.utc).isoformat(),
33+
"traceback": "".join(traceback.format_exception(type(exception), exception, exception.__traceback__)),
34+
}
35+
36+
if additional_data:
37+
payload["additional_data"] = additional_data
38+
39+
with path.open("a", encoding="utf-8") as handle:
40+
handle.write(json.dumps(payload, ensure_ascii=False) + "\n")
41+
42+
return payload
43+
44+
45+
def summarize_logs(log_path: str | Path | None = None) -> dict[str, Any]:
46+
"""Return a compact summary of the stored error logs."""
47+
path = _ensure_log_path(log_path)
48+
if not path.exists():
49+
return {
50+
"total_errors": 0,
51+
"exception_counts": {},
52+
"project_counts": {},
53+
"latest_errors": [],
54+
}
55+
56+
exception_counts: Counter[str] = Counter()
57+
project_counts: Counter[str] = Counter()
58+
latest_errors: list[dict[str, Any]] = []
59+
60+
with path.open("r", encoding="utf-8") as handle:
61+
for line in handle:
62+
line = line.strip()
63+
if not line:
64+
continue
65+
payload = json.loads(line)
66+
exception_counts[payload["exception_type"]] += 1
67+
project_counts[payload["project_name"]] += 1
68+
latest_errors.append(payload)
69+
70+
return {
71+
"total_errors": sum(exception_counts.values()),
72+
"exception_counts": dict(exception_counts),
73+
"project_counts": dict(project_counts),
74+
"latest_errors": latest_errors[-5:],
75+
}
76+
77+
78+
def safe_run(project_name: str, action, log_path: str | Path | None = None):
79+
"""Run an action while logging any unexpected exceptions."""
80+
try:
81+
return action()
82+
except Exception as exc: # pylint: disable=broad-except
83+
log_exception(project_name, exc, log_path=log_path)
84+
raise

tests/test_error_logger.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import json
2+
import os
3+
import tempfile
4+
import unittest
5+
from pathlib import Path
6+
7+
import sys
8+
9+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10+
11+
from error_logger import log_exception, summarize_logs
12+
13+
14+
class ErrorLoggerTests(unittest.TestCase):
15+
def test_log_exception_writes_structured_entry(self):
16+
with tempfile.TemporaryDirectory() as tmpdir:
17+
log_path = Path(tmpdir) / "error-logs.jsonl"
18+
log_exception(
19+
project_name="Sample Project",
20+
exception=ValueError("broken input"),
21+
log_path=log_path,
22+
)
23+
24+
self.assertTrue(log_path.exists())
25+
with log_path.open("r", encoding="utf-8") as handle:
26+
payload = json.loads(handle.read().strip())
27+
28+
self.assertEqual(payload["project_name"], "Sample Project")
29+
self.assertEqual(payload["exception_type"], "ValueError")
30+
self.assertIn("broken input", payload["error_message"])
31+
self.assertIn("timestamp", payload)
32+
self.assertIn("traceback", payload)
33+
34+
def test_summarize_logs_counts_by_exception(self):
35+
with tempfile.TemporaryDirectory() as tmpdir:
36+
log_path = Path(tmpdir) / "error-logs.jsonl"
37+
log_exception("Project A", FileNotFoundError("missing file"), log_path=log_path)
38+
log_exception("Project B", FileNotFoundError("missing file"), log_path=log_path)
39+
log_exception("Project B", IndexError("bad index"), log_path=log_path)
40+
41+
summary = summarize_logs(log_path)
42+
43+
self.assertEqual(summary["total_errors"], 3)
44+
self.assertEqual(summary["exception_counts"]["FileNotFoundError"], 2)
45+
self.assertEqual(summary["project_counts"]["Project B"], 2)
46+
self.assertEqual(summary["project_counts"]["Project A"], 1)
47+
48+
49+
if __name__ == "__main__":
50+
unittest.main()

utilities/Text-to-Morse/Text-to-Morse.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import sys
2+
from pathlib import Path
3+
4+
sys.path.append(str(Path(__file__).resolve().parents[2]))
5+
6+
from error_logger import log_exception
7+
8+
print("📻 Morse Code Translator 📻")
9+
print("Translate text to Morse code and vice versa\n")
210
import time
311

412
morse_code = {
@@ -25,6 +33,7 @@
2533
WORD_GAP = 700 # ms – silence for word separator '/'
2634
BEEP_FREQ = 700 # Hz – comfortable mid-range tone
2735

36+
def main():
2837

2938
def _sleep_ms(ms: int) -> None:
3039
"""Sleep for *ms* milliseconds."""
@@ -93,6 +102,59 @@ def main():
93102
print("3️⃣ View Morse Code Chart")
94103
print("4️⃣ Exit")
95104
print("=" * 50)
105+
106+
choice = input("\n➡️ Enter your choice (1-4): ")
107+
108+
if choice == '1':
109+
text = input("\n📝 Enter text to convert: ").upper()
110+
morse_result = []
111+
112+
for char in text:
113+
if char in morse_code:
114+
morse_result.append(morse_code[char])
115+
elif char == ' ':
116+
morse_result.append('/')
117+
118+
morse_output = ' '.join(morse_result)
119+
print(f"\n📻 Morse Code: {morse_output}\n")
120+
121+
elif choice == '2':
122+
morse_input = input("\n📻 Enter Morse code (separate letters with space, words with ' / '): ")
123+
morse_chars = morse_input.split(' ')
124+
text_result = []
125+
126+
for code in morse_chars:
127+
if code in reverse_morse:
128+
text_result.append(reverse_morse[code])
129+
elif code == '/':
130+
text_result.append(' ')
131+
else:
132+
text_result.append('?')
133+
134+
text_output = ''.join(text_result)
135+
print(f"\n📝 Text: {text_output}\n")
136+
137+
elif choice == '3':
138+
print("\n📊 Morse Code Chart:\n")
139+
print("Letters:")
140+
for i, (key, value) in enumerate(list(morse_code.items())[:26], 1):
141+
print(f" {key}: {value:8}", end='')
142+
if i % 4 == 0:
143+
print()
144+
145+
print("\n\nNumbers:")
146+
for key, value in list(morse_code.items())[26:36]:
147+
print(f" {key}: {value}")
148+
149+
print("\nSpecial Characters:")
150+
for key, value in list(morse_code.items())[36:]:
151+
print(f" {key}: {value}")
152+
print()
153+
154+
elif choice == '4':
155+
print("\n👋 Thanks for using Morse Code Translator! Goodbye!\n")
156+
break
157+
96158

97159
choice = input("\n➡️ Enter your choice (1-4): ")
98160

@@ -196,4 +258,9 @@ def main():
196258

197259

198260
if __name__ == "__main__":
261+
try:
262+
main()
263+
except Exception as exc:
264+
log_exception("Text-to-Morse", exc)
265+
raise
199266
main()

0 commit comments

Comments
 (0)