tracelify-sdk 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tracelify Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracelify-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Tracelify - Error Tracking and Performance Monitoring.
5
+ Author-email: Tracelify Team <support@tracelify.io>
6
+ Project-URL: Homepage, https://github.com/tracelify-io/tracelify-python
7
+ Project-URL: Bug Tracker, https://github.com/tracelify-io/tracelify-python/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.25.0
18
+ Dynamic: license-file
19
+
20
+ # Tracelify Python SDK
21
+
22
+ [![PyPI version](https://img.shields.io/pypi/v/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
23
+ [![Python versions](https://img.shields.io/pypi/pyversions/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
25
+
26
+ Official Python SDK for Tracelify - Effortless Error Tracking and Performance Monitoring for your Python applications.
27
+
28
+ ---
29
+
30
+ ## ⚡ Quick Start
31
+
32
+ ### 1. Install
33
+
34
+ ```bash
35
+ pip install tracelify-sdk
36
+ ```
37
+
38
+ ### 2. Initialize
39
+
40
+ Initialize the client with your DSN early in your application's entry point:
41
+
42
+ ```python
43
+ from tracelify import Client
44
+
45
+ # Initialize the client (automatically captures unhandled exceptions)
46
+ client = Client("https://your-key@tracelify.io/project-id")
47
+ ```
48
+
49
+ ### 3. Capture Exceptions Manually
50
+
51
+ ```python
52
+ try:
53
+ 1 / 0
54
+ except Exception as e:
55
+ client.capture_exception(e)
56
+ ```
57
+
58
+ ### 4. Enrich context
59
+
60
+ ```python
61
+ # Set user context
62
+ client.set_user({"id": "123", "email": "user@example.com"})
63
+
64
+ # Set custom tags
65
+ client.set_tag("environment", "production")
66
+
67
+ # Add breadcrumbs for debugging
68
+ client.add_breadcrumb("User clicked 'Submit' button")
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 🛠️ Components
74
+
75
+ - **Client**: Main interface to the SDK.
76
+ - **Config**: Configuration and DSN parsing.
77
+ - **Scope**: Manages user context, tags, and breadcrumbs.
78
+ - **Transport**: Sends events asynchronously to the Tracelify server with automatic retries and exponential backoff.
79
+ - **Handlers**: Automatic global exception capture for unhandled errors.
80
+
81
+ ---
82
+
83
+ ## ⚙️ Configuration
84
+
85
+ The Tracelify SDK is designed to work with minimal configuration. Simply provide a valid DSN:
86
+
87
+ | DSN Part | Description |
88
+ | --- | --- |
89
+ | `protocol` | http or https |
90
+ | `key` | Your public authentication key |
91
+ | `host` | Tracelify server hostname |
92
+ | `port` | Server port (optional) |
93
+ | `project_id`| Your project ID |
94
+
95
+ Example DSN: `https://abcd1234@api.tracelify.io:8080/1`
96
+
97
+ ---
98
+
99
+ ## 🤝 Contributing
100
+
101
+ We welcome contributions! Please check out our [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
102
+
103
+ ## 📄 License
104
+
105
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,86 @@
1
+ # Tracelify Python SDK
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Official Python SDK for Tracelify - Effortless Error Tracking and Performance Monitoring for your Python applications.
8
+
9
+ ---
10
+
11
+ ## ⚡ Quick Start
12
+
13
+ ### 1. Install
14
+
15
+ ```bash
16
+ pip install tracelify-sdk
17
+ ```
18
+
19
+ ### 2. Initialize
20
+
21
+ Initialize the client with your DSN early in your application's entry point:
22
+
23
+ ```python
24
+ from tracelify import Client
25
+
26
+ # Initialize the client (automatically captures unhandled exceptions)
27
+ client = Client("https://your-key@tracelify.io/project-id")
28
+ ```
29
+
30
+ ### 3. Capture Exceptions Manually
31
+
32
+ ```python
33
+ try:
34
+ 1 / 0
35
+ except Exception as e:
36
+ client.capture_exception(e)
37
+ ```
38
+
39
+ ### 4. Enrich context
40
+
41
+ ```python
42
+ # Set user context
43
+ client.set_user({"id": "123", "email": "user@example.com"})
44
+
45
+ # Set custom tags
46
+ client.set_tag("environment", "production")
47
+
48
+ # Add breadcrumbs for debugging
49
+ client.add_breadcrumb("User clicked 'Submit' button")
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 🛠️ Components
55
+
56
+ - **Client**: Main interface to the SDK.
57
+ - **Config**: Configuration and DSN parsing.
58
+ - **Scope**: Manages user context, tags, and breadcrumbs.
59
+ - **Transport**: Sends events asynchronously to the Tracelify server with automatic retries and exponential backoff.
60
+ - **Handlers**: Automatic global exception capture for unhandled errors.
61
+
62
+ ---
63
+
64
+ ## ⚙️ Configuration
65
+
66
+ The Tracelify SDK is designed to work with minimal configuration. Simply provide a valid DSN:
67
+
68
+ | DSN Part | Description |
69
+ | --- | --- |
70
+ | `protocol` | http or https |
71
+ | `key` | Your public authentication key |
72
+ | `host` | Tracelify server hostname |
73
+ | `port` | Server port (optional) |
74
+ | `project_id`| Your project ID |
75
+
76
+ Example DSN: `https://abcd1234@api.tracelify.io:8080/1`
77
+
78
+ ---
79
+
80
+ ## 🤝 Contributing
81
+
82
+ We welcome contributions! Please check out our [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
83
+
84
+ ## 📄 License
85
+
86
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tracelify-sdk"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Tracelify Team", email = "support@tracelify.io" },
10
+ ]
11
+ description = "Official Python SDK for Tracelify - Error Tracking and Performance Monitoring."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ ]
22
+ dependencies = [
23
+ "requests>=2.25.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ "Homepage" = "https://github.com/tracelify-io/tracelify-python"
28
+ "Bug Tracker" = "https://github.com/tracelify-io/tracelify-python/issues"
29
+
30
+ [tool.setuptools.packages.find]
31
+ include = ["tracelify*"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+ python_files = ["test_*.py"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ import pytest
2
+ from tracelify import Client, Config, ConfigError
3
+
4
+ def test_config_parsing_valid():
5
+ """Test valid DSN parsing."""
6
+ dsn = "http://public_key@localhost:8000/1"
7
+ config = Config(dsn)
8
+
9
+ assert config.protocol == "http"
10
+ assert config.public_key == "public_key"
11
+ assert config.host == "localhost"
12
+ assert config.port == 8000
13
+ assert config.project_id == "1"
14
+ assert config.get_endpoint() == "http://localhost:8000/api/1/events"
15
+
16
+ def test_config_parsing_invalid():
17
+ """Test invalid DSN parsing raises ConfigError."""
18
+ # Missing public key
19
+ with pytest.raises(ConfigError):
20
+ Config("http://localhost:8000/1")
21
+
22
+ # Missing host
23
+ with pytest.raises(ConfigError):
24
+ Config("http://key@/1")
25
+
26
+ def test_client_init_success(monkeypatch):
27
+ """Test successful client initialization."""
28
+ # Mocking background worker start
29
+ monkeypatch.setattr("tracelify.client.start_worker", lambda config: None)
30
+
31
+ dsn = "http://key@localhost:8000/1"
32
+ client = Client(dsn)
33
+
34
+ assert client.config.host == "localhost"
35
+ assert client.scope is not None
36
+
37
+ def test_scope_context():
38
+ """Test that scope correctly accumulates context."""
39
+ from tracelify.scope import Scope
40
+ scope = Scope()
41
+
42
+ scope.set_user({"id": "1"})
43
+ scope.set_tag("env", "prod")
44
+ scope.add_breadcrumb("test message")
45
+
46
+ context = scope.get()
47
+ assert context["user"] == {"id": "1"}
48
+ assert context["tags"] == {"env": "prod"}
49
+ assert len(context["breadcrumbs"]) == 1
50
+ assert context["breadcrumbs"][0]["message"] == "test message"
51
+
52
+ def test_event_model():
53
+ """Test that the event model dictionary conversion is correct."""
54
+ from tracelify.models import Event
55
+ from tracelify.utils import get_timestamp
56
+
57
+ ts = get_timestamp()
58
+ event = Event(
59
+ project_id="123",
60
+ timestamp=ts,
61
+ error={"type": "ValueError", "message": "error msg", "stacktrace": "..."}
62
+ )
63
+
64
+ d = event.to_dict()
65
+ assert d["project_id"] == "123"
66
+ assert d["timestamp"] == ts
67
+ assert d["error"]["type"] == "ValueError"
68
+ assert d["client"]["sdk"] == "tracelify-python"
@@ -0,0 +1,6 @@
1
+ from .client import Client
2
+ from .config import Config
3
+ from .exceptions import TracelifyError, ConfigError, TransportError, APIError
4
+ from .version import __version__
5
+
6
+ __all__ = ["Client", "Config", "TracelifyError", "ConfigError", "TransportError", "APIError", "__version__"]
@@ -0,0 +1,103 @@
1
+ import traceback
2
+ import logging
3
+ from typing import Any, Dict, Optional, List
4
+ from .config import Config
5
+ from .hub import set_client
6
+ from .scope import Scope
7
+ from .queue import event_queue
8
+ from .worker import start_worker
9
+ from .handlers import setup_global_handler
10
+ from .utils import get_runtime_context, get_timestamp
11
+ from .models import Event
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class Client:
16
+ """
17
+ The main client for the Tracelify SDK.
18
+
19
+ This class orchestrates exception capture, context management, and background event sending.
20
+
21
+ Attributes:
22
+ config (Config): Configuration for the SDK.
23
+ scope (Scope): The current scope for user and tag context.
24
+ """
25
+
26
+ def __init__(self, dsn: str):
27
+ """
28
+ Initialize the Tracelify client.
29
+
30
+ Args:
31
+ dsn: DSN of the project.
32
+ """
33
+ try:
34
+ self.config = Config(dsn)
35
+ self.scope = Scope()
36
+
37
+ # Global registration
38
+ set_client(self)
39
+
40
+ # Start background worker for asynchronous events
41
+ start_worker(self.config)
42
+
43
+ # Automatically capture unhandled exceptions
44
+ setup_global_handler()
45
+
46
+ logger.info("Tracelify SDK client initialized successfully.")
47
+
48
+ except Exception as e:
49
+ logger.error(f"Failed to initialize Tracelify SDK client: {e}")
50
+ raise
51
+
52
+ def capture_exception(self, error: Exception, stacktrace: Optional[str] = None):
53
+ """
54
+ Manually capture an exception and send it to Tracelify.
55
+
56
+ Args:
57
+ error: The exception to capture.
58
+ stacktrace: Optional manual stacktrace string.
59
+ """
60
+ try:
61
+ if stacktrace is None:
62
+ tb = getattr(error, "__traceback__", None)
63
+ if tb is not None:
64
+ stacktrace = "".join(traceback.format_exception(type(error), error, tb))
65
+ else:
66
+ stacktrace = "".join(traceback.format_stack())
67
+
68
+ if not stacktrace:
69
+ stacktrace = "No stacktrace available"
70
+
71
+ # Prepare the event data
72
+ event_data = Event(
73
+ project_id=self.config.project_id,
74
+ timestamp=get_timestamp(),
75
+ error={
76
+ "type": type(error).__name__,
77
+ "message": str(error),
78
+ "stacktrace": stacktrace
79
+ },
80
+ context=get_runtime_context(),
81
+ **self.scope.get()
82
+ )
83
+
84
+ # Send the event to the background queue
85
+ event_queue.put(event_data.to_dict())
86
+ logger.debug(f"Exception captured and queued: {type(error).__name__}")
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error while capturing exception: {e}")
90
+
91
+ # -------- CONTEXT METHODS --------
92
+
93
+ def set_user(self, user: Dict[str, Any]):
94
+ """Set the user for the current scope."""
95
+ self.scope.set_user(user)
96
+
97
+ def set_tag(self, key: str, value: str):
98
+ """Set a custom tag for context."""
99
+ self.scope.set_tag(key, value)
100
+
101
+ def add_breadcrumb(self, message: str, level: str = "info"):
102
+ """Add a breadcrumb to the current scope."""
103
+ self.scope.add_breadcrumb(message, level)
@@ -0,0 +1,60 @@
1
+ from urllib.parse import urlparse
2
+ from typing import Optional
3
+ from .exceptions import ConfigError
4
+
5
+
6
+ class Config:
7
+ """
8
+ Configuration for the Tracelify client.
9
+
10
+ Attributes:
11
+ dsn (str): The Data Source Name for the project.
12
+ protocol (str): The protocol (http/https).
13
+ host (str): The host of the server.
14
+ port (int): The port of the server.
15
+ public_key (str): The public key for authentication.
16
+ project_id (str): The project ID to which events will be sent.
17
+ """
18
+
19
+ def __init__(self, dsn: str):
20
+ """
21
+ Initialize the config from a DSN string.
22
+
23
+ Args:
24
+ dsn: DSN of the project (e.g., http://key@host:port/1)
25
+
26
+ Raises:
27
+ ConfigError: If the DSN is invalid.
28
+ """
29
+ try:
30
+ parsed = urlparse(dsn)
31
+ self.dsn = dsn
32
+ self.protocol = parsed.scheme or "https"
33
+ self.host = parsed.hostname
34
+ self.port = parsed.port or (443 if self.protocol == "https" else 80)
35
+ self.public_key = parsed.username
36
+
37
+ if not self.host:
38
+ raise ConfigError("Invalid DSN: Missing host")
39
+ if not self.public_key:
40
+ raise ConfigError("Invalid DSN: Missing public key (username)")
41
+
42
+ path_parts = parsed.path.strip("/").split("/")
43
+ if not path_parts or not path_parts[0]:
44
+ self.project_id = "1"
45
+ else:
46
+ self.project_id = path_parts[0]
47
+
48
+ except Exception as e:
49
+ if isinstance(e, ConfigError):
50
+ raise
51
+ raise ConfigError(f"Failed to parse DSN: {e}") from e
52
+
53
+ def get_endpoint(self) -> str:
54
+ """
55
+ Get the endpoint URL for sending events.
56
+
57
+ Returns:
58
+ The complete URL for the events endpoint.
59
+ """
60
+ return f"{self.protocol}://{self.host}:{self.port}/api/{self.project_id}/events"
@@ -0,0 +1,17 @@
1
+ class TracelifyError(Exception):
2
+ """Base exception for all Tracelify SDK errors."""
3
+ pass
4
+
5
+ class ConfigError(TracelifyError):
6
+ """Raised when there is a configuration error."""
7
+ pass
8
+
9
+ class TransportError(TracelifyError):
10
+ """Raised when there is an issue with the underlying HTTP transport."""
11
+ pass
12
+
13
+ class APIError(TracelifyError):
14
+ """Raised when the API returns an error response."""
15
+ def __init__(self, message, status_code=None):
16
+ super().__init__(message)
17
+ self.status_code = status_code
@@ -0,0 +1,30 @@
1
+ import sys
2
+ import traceback
3
+ import logging
4
+ from typing import Optional, Any
5
+ from .hub import get_client
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ def setup_global_handler():
10
+ """Register the SDK with sys.excepthook to automatically capture unhandled exceptions."""
11
+
12
+ def handle_exception(exc_type, exc_value, exc_traceback):
13
+ """Global exception handler callback."""
14
+ # Don't capture KeyboardInterrupt or system exit
15
+ if issubclass(exc_type, KeyboardInterrupt):
16
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
17
+ return
18
+
19
+ client = get_client()
20
+ if client:
21
+ try:
22
+ # Capture the exception
23
+ client.capture_exception(exc_value)
24
+ except Exception as e:
25
+ logger.error(f"Error while capturing exception in global handler: {e}")
26
+
27
+ # Still call the original excepthook to avoid hiding the error from the user
28
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
29
+
30
+ sys.excepthook = handle_exception
@@ -0,0 +1,13 @@
1
+ from typing import Optional, Any
2
+
3
+ # Global reference to the current client
4
+ _current_client = None
5
+
6
+ def set_client(client: Any) -> None:
7
+ """Set the globally available client."""
8
+ global _current_client
9
+ _current_client = client
10
+
11
+ def get_client() -> Optional[Any]:
12
+ """Get the currently active client."""
13
+ return _current_client
@@ -0,0 +1,3 @@
1
+ from .event import Event, User, Breadcrumb
2
+
3
+ __all__ = ["Event", "User", "Breadcrumb"]
@@ -0,0 +1,40 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict, Any, List, Optional
3
+ from ..utils import get_timestamp
4
+
5
+ @dataclass
6
+ class User:
7
+ id: Optional[str] = None
8
+ email: Optional[str] = None
9
+ username: Optional[str] = None
10
+ ip_address: Optional[str] = None
11
+
12
+ @dataclass
13
+ class Breadcrumb:
14
+ message: str
15
+ timestamp: str = field(default_factory=get_timestamp)
16
+ level: str = "info"
17
+ category: Optional[str] = None
18
+
19
+ @dataclass
20
+ class Event:
21
+ project_id: str
22
+ timestamp: str
23
+ error: Dict[str, Any]
24
+ client: Dict[str, str] = field(default_factory=lambda: {"sdk": "tracelify-python"})
25
+ user: Optional[Dict[str, Any]] = None
26
+ tags: Dict[str, str] = field(default_factory=dict)
27
+ breadcrumbs: List[Dict[str, Any]] = field(default_factory=list)
28
+ context: Dict[str, Any] = field(default_factory=dict)
29
+
30
+ def to_dict(self) -> Dict[str, Any]:
31
+ return {
32
+ "project_id": self.project_id,
33
+ "timestamp": self.timestamp,
34
+ "error": self.error,
35
+ "client": self.client,
36
+ "user": self.user,
37
+ "tags": self.tags,
38
+ "breadcrumbs": self.breadcrumbs,
39
+ "context": self.context,
40
+ }
@@ -0,0 +1,5 @@
1
+ import queue
2
+ from typing import Any
3
+
4
+ # Standard thread-safe queue for events
5
+ event_queue = queue.Queue(maxsize=1000)
@@ -0,0 +1,47 @@
1
+ from typing import Dict, Any, List, Optional
2
+ from .models.event import Breadcrumb
3
+
4
+ class Scope:
5
+ """
6
+ Manages the current context (user, tags, breadcrumbs) for events.
7
+ """
8
+
9
+ def __init__(self):
10
+ """Initialize an empty scope."""
11
+ self.user: Dict[str, Any] = {}
12
+ self.tags: Dict[str, str] = {}
13
+ self.breadcrumbs: List[Dict[str, Any]] = []
14
+
15
+ def set_user(self, user: Dict[str, Any]) -> None:
16
+ """Set user information for the current context."""
17
+ self.user = user
18
+
19
+ def set_tag(self, key: str, value: str) -> None:
20
+ """Set a tag for the current context."""
21
+ self.tags[key] = value
22
+
23
+ def add_breadcrumb(self, message: str, level: str = "info") -> None:
24
+ """Add a breadcrumb to the current context."""
25
+ breadcrumb = Breadcrumb(message=message, level=level)
26
+ self.breadcrumbs.append({
27
+ "message": breadcrumb.message,
28
+ "timestamp": breadcrumb.timestamp,
29
+ "level": breadcrumb.level
30
+ })
31
+
32
+ # Keep only the last 100 breadcrumbs for memory safety
33
+ if len(self.breadcrumbs) > 100:
34
+ self.breadcrumbs.pop(0)
35
+
36
+ def get(self) -> Dict[str, Any]:
37
+ """
38
+ Get the current scope as a dictionary.
39
+
40
+ Returns:
41
+ A dictionary containing user, tags, and breadcrumbs.
42
+ """
43
+ return {
44
+ "user": self.user,
45
+ "tags": self.tags,
46
+ "breadcrumbs": self.breadcrumbs[-20:] # Only include last 20 in the final event
47
+ }
@@ -0,0 +1,68 @@
1
+ import requests
2
+ import logging
3
+ from requests.adapters import HTTPAdapter
4
+ from urllib3.util.retry import Retry
5
+ from typing import Any, Dict
6
+ from .config import Config
7
+ from .exceptions import TransportError
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Use a session with retries for efficiency and resilience
12
+ _session = None
13
+
14
+ def get_session():
15
+ """Get or create a requests session with retries."""
16
+ global _session
17
+ if _session is None:
18
+ _session = requests.Session()
19
+
20
+ # Configure retry logic: 3 retries, exponential backoff, handle status codes 429, 500, 502, 503, 504
21
+ retries = Retry(
22
+ total=3,
23
+ backoff_factor=0.3,
24
+ status_forcelist=[429, 500, 502, 503, 504],
25
+ allowed_methods=["POST"]
26
+ )
27
+ _session.mount("http://", HTTPAdapter(max_retries=retries))
28
+ _session.mount("https://", HTTPAdapter(max_retries=retries))
29
+
30
+ return _session
31
+
32
+ def send_event(config: Config, data: Dict[str, Any]):
33
+ """
34
+ Send an event to the API using the provided configuration.
35
+
36
+ Args:
37
+ config: The SDK configuration.
38
+ data: The event data to send.
39
+
40
+ Raises:
41
+ TransportError: If the event fails to send.
42
+ """
43
+ try:
44
+ session = get_session()
45
+ response = session.post(
46
+ config.get_endpoint(),
47
+ json=data,
48
+ headers={
49
+ "X-Tracelify-Key": config.public_key,
50
+ "Content-Type": "application/json",
51
+ "User-Agent": "tracelify-python-sdk/0.1.0"
52
+ },
53
+ timeout=5
54
+ )
55
+
56
+ # Raise an exception for 4xx or 5xx responses (not handled by Retry)
57
+ response.raise_for_status()
58
+
59
+ logger.debug(f"Event sent successfully: {response.status_code}")
60
+
61
+ except requests.exceptions.RequestException as e:
62
+ logger.error(f"Failed to send event to Tracelify: {e}")
63
+ # We don't raise here to avoid crashing the user's application
64
+ # unless it was a configuration error. We just log the failure.
65
+ pass
66
+ except Exception as e:
67
+ logger.error(f"Unexpected error in transport: {e}")
68
+ pass
@@ -0,0 +1,25 @@
1
+ import platform
2
+ from datetime import datetime, timezone
3
+ from typing import Dict, Any
4
+
5
+ def get_runtime_context() -> Dict[str, Any]:
6
+ """
7
+ Get information about the system and runtime.
8
+
9
+ Returns:
10
+ A dictionary containing OS and Python runtime information.
11
+ """
12
+ return {
13
+ "os": platform.system(),
14
+ "runtime": "python",
15
+ "python_version": platform.python_version()
16
+ }
17
+
18
+ def get_timestamp() -> str:
19
+ """
20
+ Get current UTC timestamp in ISO 8601 format.
21
+
22
+ Returns:
23
+ ISO format timestamp (UTC).
24
+ """
25
+ return datetime.now(timezone.utc).isoformat()
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,38 @@
1
+ import threading
2
+ import logging
3
+ from .queue import event_queue
4
+ from .transport import send_event
5
+ from .config import Config
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ def start_worker(config: Config) -> None:
10
+ """
11
+ Start a background thread to send events from the queue to the API.
12
+
13
+ Args:
14
+ config: SDK configuration for the transport.
15
+ """
16
+
17
+ def worker():
18
+ """Background worker loop."""
19
+ while True:
20
+ try:
21
+ # Wait for an event to be added to the queue
22
+ event = event_queue.get()
23
+ if event is None: # Shutdown signal
24
+ break
25
+
26
+ # Send the event via transport
27
+ send_event(config, event)
28
+
29
+ except Exception as e:
30
+ logger.error(f"Error in background worker: {e}")
31
+ finally:
32
+ # Always mark the task as done
33
+ event_queue.task_done()
34
+
35
+ # Create a daemon thread so it doesn't block process exit
36
+ thread = threading.Thread(target=worker, daemon=True, name="tracelify-worker")
37
+ thread.start()
38
+ logger.debug("Background worker thread started.")
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracelify-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Tracelify - Error Tracking and Performance Monitoring.
5
+ Author-email: Tracelify Team <support@tracelify.io>
6
+ Project-URL: Homepage, https://github.com/tracelify-io/tracelify-python
7
+ Project-URL: Bug Tracker, https://github.com/tracelify-io/tracelify-python/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.25.0
18
+ Dynamic: license-file
19
+
20
+ # Tracelify Python SDK
21
+
22
+ [![PyPI version](https://img.shields.io/pypi/v/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
23
+ [![Python versions](https://img.shields.io/pypi/pyversions/tracelify-sdk.svg)](https://pypi.org/project/tracelify-sdk/)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
25
+
26
+ Official Python SDK for Tracelify - Effortless Error Tracking and Performance Monitoring for your Python applications.
27
+
28
+ ---
29
+
30
+ ## ⚡ Quick Start
31
+
32
+ ### 1. Install
33
+
34
+ ```bash
35
+ pip install tracelify-sdk
36
+ ```
37
+
38
+ ### 2. Initialize
39
+
40
+ Initialize the client with your DSN early in your application's entry point:
41
+
42
+ ```python
43
+ from tracelify import Client
44
+
45
+ # Initialize the client (automatically captures unhandled exceptions)
46
+ client = Client("https://your-key@tracelify.io/project-id")
47
+ ```
48
+
49
+ ### 3. Capture Exceptions Manually
50
+
51
+ ```python
52
+ try:
53
+ 1 / 0
54
+ except Exception as e:
55
+ client.capture_exception(e)
56
+ ```
57
+
58
+ ### 4. Enrich context
59
+
60
+ ```python
61
+ # Set user context
62
+ client.set_user({"id": "123", "email": "user@example.com"})
63
+
64
+ # Set custom tags
65
+ client.set_tag("environment", "production")
66
+
67
+ # Add breadcrumbs for debugging
68
+ client.add_breadcrumb("User clicked 'Submit' button")
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 🛠️ Components
74
+
75
+ - **Client**: Main interface to the SDK.
76
+ - **Config**: Configuration and DSN parsing.
77
+ - **Scope**: Manages user context, tags, and breadcrumbs.
78
+ - **Transport**: Sends events asynchronously to the Tracelify server with automatic retries and exponential backoff.
79
+ - **Handlers**: Automatic global exception capture for unhandled errors.
80
+
81
+ ---
82
+
83
+ ## ⚙️ Configuration
84
+
85
+ The Tracelify SDK is designed to work with minimal configuration. Simply provide a valid DSN:
86
+
87
+ | DSN Part | Description |
88
+ | --- | --- |
89
+ | `protocol` | http or https |
90
+ | `key` | Your public authentication key |
91
+ | `host` | Tracelify server hostname |
92
+ | `port` | Server port (optional) |
93
+ | `project_id`| Your project ID |
94
+
95
+ Example DSN: `https://abcd1234@api.tracelify.io:8080/1`
96
+
97
+ ---
98
+
99
+ ## 🤝 Contributing
100
+
101
+ We welcome contributions! Please check out our [CONTRIBUTING.md](CONTRIBUTING.md) and [CHANGELOG.md](CHANGELOG.md).
102
+
103
+ ## 📄 License
104
+
105
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_client.py
5
+ tracelify/__init__.py
6
+ tracelify/client.py
7
+ tracelify/config.py
8
+ tracelify/exceptions.py
9
+ tracelify/handlers.py
10
+ tracelify/hub.py
11
+ tracelify/queue.py
12
+ tracelify/scope.py
13
+ tracelify/transport.py
14
+ tracelify/utils.py
15
+ tracelify/version.py
16
+ tracelify/worker.py
17
+ tracelify/models/__init__.py
18
+ tracelify/models/event.py
19
+ tracelify_sdk.egg-info/PKG-INFO
20
+ tracelify_sdk.egg-info/SOURCES.txt
21
+ tracelify_sdk.egg-info/dependency_links.txt
22
+ tracelify_sdk.egg-info/requires.txt
23
+ tracelify_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.25.0
@@ -0,0 +1 @@
1
+ tracelify