datadog-async-handler 0.1.2__tar.gz → 0.1.4__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.
- datadog_async_handler-0.1.4/CHANGELOG.md +64 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/PKG-INFO +8 -8
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/README.md +1 -1
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/mkdocs.yml +3 -3
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/pyproject.toml +5 -5
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/src/datadog_http_handler/__init__.py +1 -1
- datadog_async_handler-0.1.4/src/datadog_http_handler/formatter.py +132 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/src/datadog_http_handler/handler.py +62 -2
- datadog_async_handler-0.1.2/CHANGELOG.md +0 -48
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/.gitignore +0 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/CONTRIBUTING.md +0 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/LICENSE +0 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/src/datadog_http_handler/py.typed +0 -0
- {datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/uv.lock +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.4] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fork safety: a process that called `fork()` while the worker thread was inside a TLS
|
|
12
|
+
send handed the child OpenSSL state mid-update, and the child deadlocked on its own
|
|
13
|
+
first TLS handshake (observed with Celery prefork workers). `os.register_at_fork`
|
|
14
|
+
hooks now hold the send lock across `fork()` and give the child a fresh queue, lock,
|
|
15
|
+
and worker thread. The lock covers only the network attempt, not retry back-off.
|
|
16
|
+
- The worker thread is named `datadog-http-handler`.
|
|
17
|
+
|
|
18
|
+
## [0.1.2] - 2025-08-25
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- First stable release of datadog-async-handler
|
|
22
|
+
- High-performance async HTTP logging handler for Datadog
|
|
23
|
+
- Asynchronous batching and background processing
|
|
24
|
+
- Automatic retry logic with exponential backoff
|
|
25
|
+
- Configurable batch size and flush intervals
|
|
26
|
+
- Rich metadata support (service, environment, tags)
|
|
27
|
+
- Multi-site Datadog support (US, EU, etc.)
|
|
28
|
+
- Full type hints and mypy compatibility
|
|
29
|
+
- Comprehensive documentation and examples
|
|
30
|
+
- GitHub Actions CI/CD with trusted PyPI publishing
|
|
31
|
+
|
|
32
|
+
### Features
|
|
33
|
+
- **Performance**: Non-blocking background processing
|
|
34
|
+
- **Reliability**: Comprehensive retry logic and error handling
|
|
35
|
+
- **Efficiency**: Memory-optimized batching reduces API calls
|
|
36
|
+
- **Integration**: Drop-in replacement for standard logging handlers
|
|
37
|
+
- **Framework Support**: Examples for Django, FastAPI, Flask, Celery
|
|
38
|
+
- **Testing**: Comprehensive test suite with >95% coverage
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
- Complete API reference with mkdocstrings
|
|
42
|
+
- Framework integration examples
|
|
43
|
+
- Configuration guide with all parameters
|
|
44
|
+
- Troubleshooting documentation
|
|
45
|
+
- Installation instructions
|
|
46
|
+
|
|
47
|
+
### Infrastructure
|
|
48
|
+
- Modern Python packaging with pyproject.toml
|
|
49
|
+
- Hatch build system with UV package manager
|
|
50
|
+
- Ruff for formatting and linting
|
|
51
|
+
- MyPy for type checking
|
|
52
|
+
- Pytest for testing
|
|
53
|
+
- MkDocs Material theme for documentation
|
|
54
|
+
- GitHub Actions for CI/CD
|
|
55
|
+
- Trusted publishing to PyPI
|
|
56
|
+
|
|
57
|
+
## [Unreleased]
|
|
58
|
+
|
|
59
|
+
### Planned
|
|
60
|
+
- Async/await support for truly asynchronous logging
|
|
61
|
+
- Metrics and tracing integration
|
|
62
|
+
- Advanced filtering and transformation options
|
|
63
|
+
- Performance optimizations
|
|
64
|
+
- Additional framework integrations
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: datadog-async-handler
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: High-performance async HTTP logging handler for Datadog with batching and retry logic
|
|
5
|
-
Project-URL: Homepage, https://github.com/enlyft/datadog-
|
|
6
|
-
Project-URL: Documentation, https://enlyft.github.io/datadog-
|
|
7
|
-
Project-URL: Repository, https://github.com/enlyft/datadog-
|
|
8
|
-
Project-URL: Changelog, https://github.com/enlyft/datadog-
|
|
9
|
-
Project-URL: Bug Tracker, https://github.com/enlyft/datadog-
|
|
5
|
+
Project-URL: Homepage, https://github.com/enlyft/datadog-async-handler
|
|
6
|
+
Project-URL: Documentation, https://enlyft.github.io/datadog-async-handler
|
|
7
|
+
Project-URL: Repository, https://github.com/enlyft/datadog-async-handler.git
|
|
8
|
+
Project-URL: Changelog, https://github.com/enlyft/datadog-async-handler/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/enlyft/datadog-async-handler/issues
|
|
10
10
|
Author-email: Parth <parth@enlyft.com>
|
|
11
11
|
Maintainer-email: Parth <parth@enlyft.com>
|
|
12
12
|
License-Expression: MIT
|
|
@@ -53,7 +53,7 @@ Description-Content-Type: text/markdown
|
|
|
53
53
|
|
|
54
54
|
# Datadog Async Handler
|
|
55
55
|
|
|
56
|
-
[](https://pypi.org/project/datadog-async-handler/)
|
|
57
57
|
[](https://pypi.org/project/datadog-async-handler/)
|
|
58
58
|
[](https://opensource.org/licenses/MIT)
|
|
59
59
|
[](https://github.com/astral-sh/ruff)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Datadog Async Handler
|
|
2
2
|
|
|
3
|
-
[](https://pypi.org/project/datadog-async-handler/)
|
|
4
4
|
[](https://pypi.org/project/datadog-async-handler/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://github.com/astral-sh/ruff)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
site_name: Datadog Async Handler
|
|
2
2
|
site_description: High-performance async HTTP logging handler for Datadog with batching and retry logic
|
|
3
|
-
site_url: https://enlyft.github.io/datadog-
|
|
4
|
-
repo_url: https://github.com/enlyft/datadog-
|
|
5
|
-
repo_name: enlyft/datadog-
|
|
3
|
+
site_url: https://enlyft.github.io/datadog-async-handler
|
|
4
|
+
repo_url: https://github.com/enlyft/datadog-async-handler
|
|
5
|
+
repo_name: enlyft/datadog-async-handler
|
|
6
6
|
|
|
7
7
|
theme:
|
|
8
8
|
name: material
|
|
@@ -71,11 +71,11 @@ test = [
|
|
|
71
71
|
]
|
|
72
72
|
|
|
73
73
|
[project.urls]
|
|
74
|
-
Homepage = "https://github.com/enlyft/datadog-
|
|
75
|
-
Documentation = "https://enlyft.github.io/datadog-
|
|
76
|
-
Repository = "https://github.com/enlyft/datadog-
|
|
77
|
-
Changelog = "https://github.com/enlyft/datadog-
|
|
78
|
-
"Bug Tracker" = "https://github.com/enlyft/datadog-
|
|
74
|
+
Homepage = "https://github.com/enlyft/datadog-async-handler"
|
|
75
|
+
Documentation = "https://enlyft.github.io/datadog-async-handler"
|
|
76
|
+
Repository = "https://github.com/enlyft/datadog-async-handler.git"
|
|
77
|
+
Changelog = "https://github.com/enlyft/datadog-async-handler/blob/main/CHANGELOG.md"
|
|
78
|
+
"Bug Tracker" = "https://github.com/enlyft/datadog-async-handler/issues"
|
|
79
79
|
|
|
80
80
|
[tool.hatch.version]
|
|
81
81
|
source = "vcs"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatadogJsonFormatter(logging.Formatter):
|
|
9
|
+
"""
|
|
10
|
+
JSON formatter optimized for Datadog log ingestion.
|
|
11
|
+
|
|
12
|
+
This formatter creates structured JSON logs with fields that Datadog
|
|
13
|
+
can automatically parse and index for better searchability and analysis.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, service_name: str, version: Optional[str] = None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the formatter.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
service_name: Name of the service (e.g., 'auth-service', 'embedding-service')
|
|
22
|
+
version: Version of the service (optional)
|
|
23
|
+
"""
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.service_name = service_name
|
|
26
|
+
self.version = version or os.getenv("SERVICE_VERSION", "unknown")
|
|
27
|
+
self.environment = os.getenv("DD_ENV", "development")
|
|
28
|
+
|
|
29
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Format a log record as JSON.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
record: The log record to format
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
JSON-formatted log string
|
|
38
|
+
"""
|
|
39
|
+
# Base log structure for Datadog
|
|
40
|
+
log_entry = {
|
|
41
|
+
"timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
|
|
42
|
+
"level": record.levelname,
|
|
43
|
+
"logger": record.name,
|
|
44
|
+
"message": record.getMessage(),
|
|
45
|
+
"service": self.service_name,
|
|
46
|
+
"version": self.version,
|
|
47
|
+
"env": self.environment,
|
|
48
|
+
"process_id": record.process,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# --- Datadog correlation IDs (must be at top level) ---
|
|
52
|
+
# First check nested dd fields
|
|
53
|
+
if hasattr(record, "dd"):
|
|
54
|
+
dd_fields = record.dd
|
|
55
|
+
if "trace_id" in dd_fields:
|
|
56
|
+
log_entry["dd.trace_id"] = dd_fields["trace_id"]
|
|
57
|
+
if "span_id" in dd_fields:
|
|
58
|
+
log_entry["dd.span_id"] = dd_fields["span_id"]
|
|
59
|
+
if "service" in dd_fields:
|
|
60
|
+
log_entry["dd.service"] = dd_fields["service"]
|
|
61
|
+
if "version" in dd_fields:
|
|
62
|
+
log_entry["dd.version"] = dd_fields["version"]
|
|
63
|
+
if "env" in dd_fields:
|
|
64
|
+
log_entry["dd.env"] = dd_fields["env"]
|
|
65
|
+
|
|
66
|
+
# Then check for flat Datadog attributes that ddtrace may inject
|
|
67
|
+
dd_flat_keys = ["dd.trace_id", "dd.span_id", "dd.service", "dd.version", "dd.env"]
|
|
68
|
+
for dd_key in dd_flat_keys:
|
|
69
|
+
# Only use flat key if we don't already have this field from record.dd
|
|
70
|
+
if dd_key not in log_entry and hasattr(record, dd_key):
|
|
71
|
+
log_entry[dd_key] = getattr(record, dd_key)
|
|
72
|
+
|
|
73
|
+
# --- Add source info ---
|
|
74
|
+
if record.pathname:
|
|
75
|
+
log_entry["source"] = {
|
|
76
|
+
"file": record.pathname,
|
|
77
|
+
"line": record.lineno,
|
|
78
|
+
"function": record.funcName,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# --- Collect remaining extras, except dd (already flattened) ---
|
|
82
|
+
extra_fields = {
|
|
83
|
+
k: v
|
|
84
|
+
for k, v in record.__dict__.items()
|
|
85
|
+
if k
|
|
86
|
+
not in {
|
|
87
|
+
"name",
|
|
88
|
+
"msg",
|
|
89
|
+
"args",
|
|
90
|
+
"levelname",
|
|
91
|
+
"levelno",
|
|
92
|
+
"pathname",
|
|
93
|
+
"filename",
|
|
94
|
+
"module",
|
|
95
|
+
"lineno",
|
|
96
|
+
"funcName",
|
|
97
|
+
"created",
|
|
98
|
+
"msecs",
|
|
99
|
+
"relativeCreated",
|
|
100
|
+
"thread",
|
|
101
|
+
"threadName",
|
|
102
|
+
"processName",
|
|
103
|
+
"process",
|
|
104
|
+
"getMessage",
|
|
105
|
+
"exc_info",
|
|
106
|
+
"exc_text",
|
|
107
|
+
"stack_info",
|
|
108
|
+
"message",
|
|
109
|
+
"dd", # skip dd because we handled it
|
|
110
|
+
"dd.trace_id", # skip flat dd keys because we handled them
|
|
111
|
+
"dd.span_id",
|
|
112
|
+
"dd.service",
|
|
113
|
+
"dd.version",
|
|
114
|
+
"dd.env",
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if extra_fields:
|
|
118
|
+
log_entry["extra"] = extra_fields
|
|
119
|
+
|
|
120
|
+
# --- Exception handling ---
|
|
121
|
+
if record.exc_info:
|
|
122
|
+
log_entry["exception"] = {
|
|
123
|
+
"class": record.exc_info[0].__name__ if record.exc_info[0] else None,
|
|
124
|
+
"message": str(record.exc_info[1]) if record.exc_info[1] else None,
|
|
125
|
+
"traceback": self.formatException(record.exc_info),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if record.stack_info:
|
|
129
|
+
log_entry["stack_trace"] = record.stack_info
|
|
130
|
+
|
|
131
|
+
return json.dumps(log_entry, default=str, ensure_ascii=False)
|
|
132
|
+
|
{datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/src/datadog_http_handler/handler.py
RENAMED
|
@@ -9,6 +9,7 @@ import logging
|
|
|
9
9
|
import os
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
|
+
import weakref
|
|
12
13
|
from queue import Empty, Queue
|
|
13
14
|
from typing import Optional
|
|
14
15
|
|
|
@@ -18,6 +19,17 @@ from datadog_api_client.v2.model.http_log import HTTPLog
|
|
|
18
19
|
from datadog_api_client.v2.model.http_log_item import HTTPLogItem
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _at_fork(ref: "weakref.ReferenceType[DatadogHTTPHandler]", method: str) -> None:
|
|
23
|
+
"""Dispatch an ``os.register_at_fork`` callback to a handler that is still alive.
|
|
24
|
+
|
|
25
|
+
``os.register_at_fork`` keeps its callables for the life of the process, so the
|
|
26
|
+
hooks hold only a weak reference and become no-ops once the handler is collected.
|
|
27
|
+
"""
|
|
28
|
+
handler = ref()
|
|
29
|
+
if handler is not None:
|
|
30
|
+
getattr(handler, method)()
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
class DatadogHTTPHandler(logging.Handler):
|
|
22
34
|
"""
|
|
23
35
|
High-performance logging handler that sends logs to Datadog via HTTP API.
|
|
@@ -26,6 +38,12 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
26
38
|
the main application thread. It includes retry logic with exponential backoff,
|
|
27
39
|
comprehensive error handling, and support for all Datadog sites.
|
|
28
40
|
|
|
41
|
+
The handler is fork-safe. Its worker thread sends over TLS, and a child forked
|
|
42
|
+
while that handshake is in flight inherits OpenSSL state mid-update and deadlocks
|
|
43
|
+
on its own first TLS handshake. ``os.register_at_fork`` hooks hold a send lock
|
|
44
|
+
across ``fork()`` so no child is forked mid-send, and give the child a fresh
|
|
45
|
+
queue, lock, and worker thread (threads do not survive ``fork()``).
|
|
46
|
+
|
|
29
47
|
Args:
|
|
30
48
|
api_key: Datadog API key (or set DD_API_KEY env var)
|
|
31
49
|
site: Datadog site (default: datadoghq.com)
|
|
@@ -60,6 +78,9 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
60
78
|
>>> logger.addHandler(handler)
|
|
61
79
|
"""
|
|
62
80
|
|
|
81
|
+
# Bounded so a worker wedged inside C can never freeze the parent's fork() forever.
|
|
82
|
+
_FORK_WAIT_SECONDS = 15.0
|
|
83
|
+
|
|
63
84
|
def __init__(
|
|
64
85
|
self,
|
|
65
86
|
api_key: Optional[str] = None,
|
|
@@ -103,7 +124,16 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
103
124
|
self._log_queue: Queue[HTTPLogItem] = Queue()
|
|
104
125
|
self._stop_event = threading.Event()
|
|
105
126
|
self._worker_thread: Optional[threading.Thread] = None
|
|
127
|
+
self._send_lock = threading.Lock()
|
|
128
|
+
self._fork_lock_held = False
|
|
106
129
|
self._start_worker()
|
|
130
|
+
if hasattr(os, "register_at_fork"): # POSIX only
|
|
131
|
+
ref = weakref.ref(self)
|
|
132
|
+
os.register_at_fork(
|
|
133
|
+
before=lambda: _at_fork(ref, "_before_fork"),
|
|
134
|
+
after_in_parent=lambda: _at_fork(ref, "_after_fork_in_parent"),
|
|
135
|
+
after_in_child=lambda: _at_fork(ref, "_after_fork_in_child"),
|
|
136
|
+
)
|
|
107
137
|
|
|
108
138
|
def _setup_api_client(self) -> None:
|
|
109
139
|
"""Setup the Datadog API client."""
|
|
@@ -117,7 +147,9 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
117
147
|
def _start_worker(self) -> None:
|
|
118
148
|
"""Start the background worker thread."""
|
|
119
149
|
if self._worker_thread is None or not self._worker_thread.is_alive():
|
|
120
|
-
self._worker_thread = threading.Thread(
|
|
150
|
+
self._worker_thread = threading.Thread(
|
|
151
|
+
target=self._worker, daemon=True, name="datadog-http-handler"
|
|
152
|
+
)
|
|
121
153
|
self._worker_thread.start()
|
|
122
154
|
|
|
123
155
|
def _worker(self) -> None:
|
|
@@ -161,7 +193,8 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
161
193
|
for attempt in range(self.max_retries + 1):
|
|
162
194
|
try:
|
|
163
195
|
http_log = HTTPLog(batch)
|
|
164
|
-
self.
|
|
196
|
+
with self._send_lock: # fork() waits for this, not for back-off
|
|
197
|
+
self.logs_api.submit_log(body=http_log)
|
|
165
198
|
return # Success
|
|
166
199
|
|
|
167
200
|
except Exception as e:
|
|
@@ -174,6 +207,33 @@ class DatadogHTTPHandler(logging.Handler):
|
|
|
174
207
|
# Wait before retry (exponential backoff)
|
|
175
208
|
time.sleep(2**attempt)
|
|
176
209
|
|
|
210
|
+
def _before_fork(self) -> None:
|
|
211
|
+
"""Hold the send lock across ``fork()`` so no child is forked mid-send."""
|
|
212
|
+
self._fork_lock_held = self._send_lock.acquire(timeout=self._FORK_WAIT_SECONDS)
|
|
213
|
+
if not self._fork_lock_held:
|
|
214
|
+
self._handle_error(
|
|
215
|
+
"forking while a log send is wedged; the child's TLS may deadlock"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _after_fork_in_parent(self) -> None:
|
|
219
|
+
"""Release the lock taken by ``_before_fork``."""
|
|
220
|
+
if self._fork_lock_held:
|
|
221
|
+
self._fork_lock_held = False
|
|
222
|
+
self._send_lock.release()
|
|
223
|
+
|
|
224
|
+
def _after_fork_in_child(self) -> None:
|
|
225
|
+
"""Give the child a fresh lock, queue, and worker thread.
|
|
226
|
+
|
|
227
|
+
The parent's worker thread does not exist in the child, and the inherited
|
|
228
|
+
lock may still be held by it.
|
|
229
|
+
"""
|
|
230
|
+
self._send_lock = threading.Lock()
|
|
231
|
+
self._fork_lock_held = False
|
|
232
|
+
self._log_queue = Queue()
|
|
233
|
+
self._stop_event = threading.Event()
|
|
234
|
+
self._worker_thread = None
|
|
235
|
+
self._start_worker()
|
|
236
|
+
|
|
177
237
|
def _handle_error(self, message: str) -> None:
|
|
178
238
|
"""Handle errors that occur during log submission."""
|
|
179
239
|
# Log to stderr to avoid infinite recursion
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
- Initial release of datadog-http-handler
|
|
12
|
-
- Asynchronous HTTP logging handler for Datadog
|
|
13
|
-
- Batching support with configurable batch size and flush intervals
|
|
14
|
-
- Automatic retry logic with exponential backoff
|
|
15
|
-
- Comprehensive error handling and graceful degradation
|
|
16
|
-
- Type hints and mypy compatibility
|
|
17
|
-
- Support for all Datadog sites (US, EU, Government, etc.)
|
|
18
|
-
- Environment variable configuration support
|
|
19
|
-
- Framework integration examples (Django, FastAPI, Flask)
|
|
20
|
-
- Background worker thread for non-blocking log processing
|
|
21
|
-
- Automatic tag enrichment (environment, service, version)
|
|
22
|
-
- Memory-efficient queue management
|
|
23
|
-
- Graceful shutdown handling
|
|
24
|
-
|
|
25
|
-
### Changed
|
|
26
|
-
- N/A (initial release)
|
|
27
|
-
|
|
28
|
-
### Deprecated
|
|
29
|
-
- N/A (initial release)
|
|
30
|
-
|
|
31
|
-
### Removed
|
|
32
|
-
- N/A (initial release)
|
|
33
|
-
|
|
34
|
-
### Fixed
|
|
35
|
-
- N/A (initial release)
|
|
36
|
-
|
|
37
|
-
### Security
|
|
38
|
-
- N/A (initial release)
|
|
39
|
-
|
|
40
|
-
## [0.1.0] - 2025-08-25
|
|
41
|
-
|
|
42
|
-
### Added
|
|
43
|
-
- Initial development version
|
|
44
|
-
- Core DatadogHTTPHandler implementation
|
|
45
|
-
- Basic test suite
|
|
46
|
-
- Documentation and examples
|
|
47
|
-
- Modern Python packaging with Hatch
|
|
48
|
-
- Comprehensive development tooling (Ruff, mypy, pytest)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datadog_async_handler-0.1.2 → datadog_async_handler-0.1.4}/src/datadog_http_handler/py.typed
RENAMED
|
File without changes
|
|
File without changes
|