datadog-async-handler 0.1.2__py3-none-any.whl

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,294 @@
1
+ Metadata-Version: 2.4
2
+ Name: datadog-async-handler
3
+ Version: 0.1.2
4
+ Summary: High-performance async HTTP logging handler for Datadog with batching and retry logic
5
+ Project-URL: Homepage, https://github.com/enlyft/datadog-http-handler
6
+ Project-URL: Documentation, https://enlyft.github.io/datadog-http-handler
7
+ Project-URL: Repository, https://github.com/enlyft/datadog-http-handler.git
8
+ Project-URL: Changelog, https://github.com/enlyft/datadog-http-handler/blob/main/CHANGELOG.md
9
+ Project-URL: Bug Tracker, https://github.com/enlyft/datadog-http-handler/issues
10
+ Author-email: Parth <parth@enlyft.com>
11
+ Maintainer-email: Parth <parth@enlyft.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Keywords: async,batching,datadog,handler,logging,observability
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Topic :: System :: Logging
27
+ Classifier: Topic :: System :: Monitoring
28
+ Requires-Python: >=3.9
29
+ Requires-Dist: datadog-api-client<3.0.0,>=2.0.0
30
+ Provides-Extra: async
31
+ Requires-Dist: aiohttp>=3.8.0; extra == 'async'
32
+ Provides-Extra: dev
33
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
34
+ Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
36
+ Requires-Dist: pytest-benchmark>=4.0.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
38
+ Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
39
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
40
+ Requires-Dist: ruff>=0.2.0; extra == 'dev'
41
+ Requires-Dist: tox>=4.0.0; extra == 'dev'
42
+ Provides-Extra: docs
43
+ Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
44
+ Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
45
+ Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'docs'
46
+ Provides-Extra: test
47
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
48
+ Requires-Dist: pytest-benchmark>=4.0.0; extra == 'test'
49
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
50
+ Requires-Dist: pytest-mock>=3.12.0; extra == 'test'
51
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
52
+ Description-Content-Type: text/markdown
53
+
54
+ # Datadog Async Handler
55
+
56
+ [![PyPI version](https://badge.fury.io/py/datadog-async-handler.svg)](https://badge.fury.io/py/datadog-async-handler)
57
+ [![Python versions](https://img.shields.io/pypi/pyversions/datadog-async-handler.svg)](https://pypi.org/project/datadog-async-handler/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+ [![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
60
+
61
+ A modern, high-performance Python logging handler that sends logs directly to Datadog via HTTP API with asynchronous batching, retry logic, and comprehensive error handling.
62
+
63
+ ## ✨ Features
64
+
65
+ - **🚀 High Performance**: Asynchronous batching and background processing
66
+ - **🔄 Reliable Delivery**: Automatic retry with exponential backoff
67
+ - **📊 Batching**: Configurable batch size and flush intervals
68
+ - **🏷️ Rich Metadata**: Automatic service, environment, and custom tag support
69
+ - **🔧 Easy Integration**: Drop-in replacement for standard logging handlers
70
+ - **🌐 Multi-Site Support**: Works with all Datadog sites (US, EU, etc.)
71
+ - **📝 Type Safe**: Full type hints and mypy compatibility
72
+ - **⚡ Modern**: Built with Python 3.9+ and latest best practices
73
+
74
+ ## 🚀 Quick Start
75
+
76
+ ### Installation
77
+
78
+ ```bash
79
+ pip install datadog-async-handler
80
+ ```
81
+
82
+ ### Basic Usage
83
+
84
+ ```python
85
+ import logging
86
+ from datadog_http_handler import DatadogHTTPHandler
87
+
88
+ # Configure the handler
89
+ handler = DatadogHTTPHandler(
90
+ api_key="your-datadog-api-key", # or set DD_API_KEY env var
91
+ service="my-application",
92
+ source="python",
93
+ tags="env:production,team:backend"
94
+ )
95
+
96
+ # Set up logging
97
+ logger = logging.getLogger(__name__)
98
+ logger.addHandler(handler)
99
+ logger.setLevel(logging.INFO)
100
+
101
+ # Start logging!
102
+ logger.info("Application started successfully", extra={
103
+ "user_id": "12345",
104
+ "action": "startup"
105
+ })
106
+ ```
107
+
108
+ ### Environment Variables
109
+
110
+ The handler automatically picks up standard Datadog environment variables:
111
+
112
+ ```bash
113
+ export DD_API_KEY="your-api-key"
114
+ export DD_SERVICE="my-application"
115
+ export DD_ENV="production"
116
+ export DD_VERSION="1.2.3"
117
+ export DD_TAGS="team:backend,component:api"
118
+ export DD_SITE="datadoghq.com" # or datadoghq.eu, ddog-gov.com, etc.
119
+ ```
120
+
121
+ ## 📖 Documentation
122
+
123
+ ### Configuration Options
124
+
125
+ | Parameter | Type | Default | Description |
126
+ |-----------|------|---------|-------------|
127
+ | `api_key` | `str` | `None` | Datadog API key (required) |
128
+ | `site` | `str` | `"datadoghq.com"` | Datadog site |
129
+ | `service` | `str` | `None` | Service name |
130
+ | `source` | `str` | `"python"` | Log source |
131
+ | `hostname` | `str` | `None` | Hostname |
132
+ | `tags` | `str` | `None` | Comma-separated tags |
133
+ | `batch_size` | `int` | `10` | Number of logs per batch |
134
+ | `flush_interval_seconds` | `float` | `5.0` | Batch flush interval |
135
+ | `timeout_seconds` | `float` | `10.0` | Request timeout |
136
+ | `max_retries` | `int` | `3` | Maximum retry attempts |
137
+
138
+ ### Framework Integration Examples
139
+
140
+ #### Django
141
+
142
+ ```python
143
+ # settings.py
144
+ LOGGING = {
145
+ 'version': 1,
146
+ 'disable_existing_loggers': False,
147
+ 'handlers': {
148
+ 'datadog': {
149
+ 'class': 'datadog_http_handler.DatadogHTTPHandler',
150
+ 'api_key': 'your-api-key',
151
+ 'service': 'django-app',
152
+ 'source': 'django',
153
+ },
154
+ },
155
+ 'root': {
156
+ 'handlers': ['datadog'],
157
+ 'level': 'INFO',
158
+ },
159
+ }
160
+ ```
161
+
162
+ #### FastAPI
163
+
164
+ ```python
165
+ import logging
166
+ from fastapi import FastAPI
167
+ from datadog_http_handler import DatadogHTTPHandler
168
+
169
+ app = FastAPI()
170
+
171
+ # Configure logging
172
+ handler = DatadogHTTPHandler(service="fastapi-app", source="fastapi")
173
+ logging.getLogger().addHandler(handler)
174
+ logging.getLogger().setLevel(logging.INFO)
175
+
176
+ @app.get("/")
177
+ async def root():
178
+ logging.info("API endpoint called", extra={"endpoint": "/"})
179
+ return {"message": "Hello World"}
180
+ ```
181
+
182
+ #### Flask
183
+
184
+ ```python
185
+ import logging
186
+ from flask import Flask
187
+ from datadog_http_handler import DatadogHTTPHandler
188
+
189
+ app = Flask(__name__)
190
+
191
+ # Configure logging
192
+ handler = DatadogHTTPHandler(service="flask-app", source="flask")
193
+ app.logger.addHandler(handler)
194
+ app.logger.setLevel(logging.INFO)
195
+
196
+ @app.route("/")
197
+ def hello():
198
+ app.logger.info("Flask endpoint called", extra={"endpoint": "/"})
199
+ return "Hello World!"
200
+ ```
201
+
202
+ ## 🔧 Development
203
+
204
+ ### Setup
205
+
206
+ ```bash
207
+ # Clone the repository
208
+ git clone https://github.com/enlyft/datadog-http-handler.git
209
+ cd datadog-http-handler
210
+
211
+ # Install UV (modern Python package manager)
212
+ curl -LsSf https://astral.sh/uv/install.sh | sh
213
+
214
+ # Install dependencies
215
+ uv pip install -e ".[dev]"
216
+
217
+ # Install pre-commit hooks
218
+ pre-commit install
219
+ ```
220
+
221
+ ### Running Tests
222
+
223
+ ```bash
224
+ # Run all tests
225
+ hatch run test
226
+
227
+ # Run with coverage
228
+ hatch run test-cov
229
+
230
+ # Run specific tests
231
+ hatch run test tests/test_handler.py::test_basic_logging
232
+ ```
233
+
234
+ ### Code Quality
235
+
236
+ ```bash
237
+ # Format code
238
+ hatch run format
239
+
240
+ # Lint code
241
+ hatch run lint
242
+
243
+ # Type checking
244
+ hatch run type-check
245
+
246
+ # Run all checks
247
+ hatch run all
248
+ ```
249
+
250
+ ## 🤝 Contributing
251
+
252
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
253
+
254
+ 1. Fork the repository
255
+ 2. Create a feature branch
256
+ 3. Make your changes
257
+ 4. Add tests
258
+ 5. Run the test suite
259
+ 6. Submit a pull request
260
+
261
+ ## 📋 Requirements
262
+
263
+ - Python 3.9+
264
+ - `datadog-api-client>=2.0.0`
265
+
266
+ ## 📄 License
267
+
268
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
269
+
270
+ ## 🔗 Links
271
+
272
+ - [Documentation](https://enlyft.github.io/datadog-http-handler)
273
+ - [PyPI Package](https://pypi.org/project/datadog-async-handler/)
274
+ - [GitHub Repository](https://github.com/enlyft/datadog-http-handler)
275
+ - [Issue Tracker](https://github.com/enlyft/datadog-http-handler/issues)
276
+ - [Datadog Logs API Documentation](https://docs.datadoghq.com/api/latest/logs/)
277
+
278
+ ## 🆚 Comparison with Other Solutions
279
+
280
+ | Feature | datadog-async-handler | datadog-http-handler | python-datadog | datadog-logger |
281
+ |---------|----------------------|---------------------|----------------|----------------|
282
+ | Async Batching | ✅ | ❌ | ❌ | ❌ |
283
+ | Retry Logic | ✅ | ❌ | ❌ | ❌ |
284
+ | Type Hints | ✅ | ❌ | ❌ | ❌ |
285
+ | Modern Python | ✅ (3.9+) | ❌ (3.6+) | ❌ (2.7+) | ❌ (3.6+) |
286
+ | Official API Client | ✅ | ❌ | ❌ | ❌ |
287
+ | Background Processing | ✅ | ❌ | ❌ | ❌ |
288
+ | Memory Efficient | ✅ | ❌ | ❌ | ❌ |
289
+ | Active Maintenance | ✅ | ❌ (2019) | ✅ | ❌ |
290
+ | Comprehensive Tests | ✅ | ❌ | ✅ | ❌ |
291
+
292
+ ---
293
+
294
+ Made with ❤️ for the Python and Datadog communities.
@@ -0,0 +1,7 @@
1
+ datadog_http_handler/__init__.py,sha256=opKEnLnS-qXQ-alt7EPRQQk91E8MEwEznQ4-3k4-92s,744
2
+ datadog_http_handler/handler.py,sha256=_pJAK1iKgVO4mR2dTL9FDlNi4oJVkq_D59R69aH7Sok,10660
3
+ datadog_http_handler/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ datadog_async_handler-0.1.2.dist-info/METADATA,sha256=CQilYl9Lm_A0zDvb6Cqlj5VZQUUVSdBWKNqwLqiOGhc,9017
5
+ datadog_async_handler-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ datadog_async_handler-0.1.2.dist-info/licenses/LICENSE,sha256=E4Va_3PEQq8kxwFMY3NDGGjDbiklFYi-Z6QJI0NEfq8,1061
7
+ datadog_async_handler-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Parth
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,27 @@
1
+ """
2
+ Datadog HTTP Handler - Modern Python logging handler for Datadog.
3
+
4
+ This package provides a high-performance, asynchronous logging handler that sends
5
+ logs directly to Datadog's HTTP intake API with batching, retry logic, and
6
+ comprehensive error handling.
7
+
8
+ Example:
9
+ Basic usage:
10
+
11
+ >>> import logging
12
+ >>> from datadog_http_handler import DatadogHTTPHandler
13
+ >>>
14
+ >>> handler = DatadogHTTPHandler(
15
+ ... api_key="your-api-key",
16
+ ... service="my-app",
17
+ ... tags="env:production"
18
+ ... )
19
+ >>> logger = logging.getLogger(__name__)
20
+ >>> logger.addHandler(handler)
21
+ >>> logger.info("Hello Datadog!")
22
+ """
23
+
24
+ from .handler import DatadogHTTPHandler
25
+
26
+ __version__ = "0.1.0"
27
+ __all__ = ["DatadogHTTPHandler"]
@@ -0,0 +1,309 @@
1
+ """
2
+ Datadog HTTP logging handler implementation.
3
+
4
+ This module provides the DatadogHTTPHandler class, a modern Python logging handler
5
+ that sends logs to Datadog via HTTP API with asynchronous batching and retry logic.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ import threading
11
+ import time
12
+ from queue import Empty, Queue
13
+ from typing import Optional
14
+
15
+ from datadog_api_client import ApiClient, Configuration
16
+ from datadog_api_client.v2.api.logs_api import LogsApi
17
+ from datadog_api_client.v2.model.http_log import HTTPLog
18
+ from datadog_api_client.v2.model.http_log_item import HTTPLogItem
19
+
20
+
21
+ class DatadogHTTPHandler(logging.Handler):
22
+ """
23
+ High-performance logging handler that sends logs to Datadog via HTTP API.
24
+
25
+ This handler batches logs and sends them asynchronously to avoid blocking
26
+ the main application thread. It includes retry logic with exponential backoff,
27
+ comprehensive error handling, and support for all Datadog sites.
28
+
29
+ Args:
30
+ api_key: Datadog API key (or set DD_API_KEY env var)
31
+ site: Datadog site (default: datadoghq.com)
32
+ service: Service name for logs
33
+ source: Log source (default: python)
34
+ hostname: Hostname for logs
35
+ tags: Tags to add to logs (comma-separated string)
36
+ batch_size: Number of logs to batch before sending
37
+ flush_interval_seconds: How often to flush logs (seconds)
38
+ timeout_seconds: Request timeout
39
+ max_retries: Maximum retry attempts
40
+ level: Logging level
41
+
42
+ Example:
43
+ Basic usage:
44
+
45
+ >>> import logging
46
+ >>> handler = DatadogHTTPHandler(
47
+ ... api_key="your-api-key",
48
+ ... service="my-app",
49
+ ... tags="env:production,team:backend"
50
+ ... )
51
+ >>> logger = logging.getLogger(__name__)
52
+ >>> logger.addHandler(handler)
53
+ >>> logger.info("Application started")
54
+
55
+ With environment variables:
56
+
57
+ >>> # Set DD_API_KEY, DD_SERVICE, DD_ENV, etc.
58
+ >>> handler = DatadogHTTPHandler() # Uses env vars
59
+ >>> logger = logging.getLogger(__name__)
60
+ >>> logger.addHandler(handler)
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ api_key: Optional[str] = None,
66
+ site: str = "datadoghq.com",
67
+ service: Optional[str] = None,
68
+ source: str = "python",
69
+ hostname: Optional[str] = None,
70
+ tags: Optional[str] = None,
71
+ batch_size: int = 10,
72
+ flush_interval_seconds: float = 5.0,
73
+ timeout_seconds: float = 10.0,
74
+ max_retries: int = 3,
75
+ level: int = logging.NOTSET,
76
+ ) -> None:
77
+ """Initialize the Datadog HTTP handler."""
78
+ super().__init__(level)
79
+
80
+ # API Configuration
81
+ self.api_key = api_key or os.getenv("DD_API_KEY")
82
+ if not self.api_key:
83
+ raise ValueError(
84
+ "Datadog API key is required. Set DD_API_KEY env var or pass api_key parameter."
85
+ )
86
+
87
+ self.site = site or os.getenv("DD_SITE", "datadoghq.com")
88
+ self.service = service or os.getenv("DD_SERVICE", "unknown")
89
+ self.source = source
90
+ self.hostname = hostname or os.getenv("DD_HOSTNAME")
91
+ self.tags = tags or os.getenv("DD_TAGS", "")
92
+
93
+ # Batch settings
94
+ self.batch_size = max(1, batch_size) # Ensure at least 1
95
+ self.flush_interval = max(0.1, flush_interval_seconds) # Minimum 100ms
96
+ self.timeout = max(1.0, timeout_seconds) # Minimum 1 second
97
+ self.max_retries = max(0, max_retries) # No negative retries
98
+
99
+ # Initialize API client
100
+ self._setup_api_client()
101
+
102
+ # Background processing
103
+ self._log_queue: Queue[HTTPLogItem] = Queue()
104
+ self._stop_event = threading.Event()
105
+ self._worker_thread: Optional[threading.Thread] = None
106
+ self._start_worker()
107
+
108
+ def _setup_api_client(self) -> None:
109
+ """Setup the Datadog API client."""
110
+ configuration = Configuration()
111
+ configuration.api_key["apiKeyAuth"] = self.api_key
112
+ configuration.server_variables["site"] = self.site
113
+
114
+ self.api_client = ApiClient(configuration)
115
+ self.logs_api = LogsApi(self.api_client)
116
+
117
+ def _start_worker(self) -> None:
118
+ """Start the background worker thread."""
119
+ if self._worker_thread is None or not self._worker_thread.is_alive():
120
+ self._worker_thread = threading.Thread(target=self._worker, daemon=True)
121
+ self._worker_thread.start()
122
+
123
+ def _worker(self) -> None:
124
+ """Background worker that processes log batches."""
125
+ batch: list[HTTPLogItem] = []
126
+ last_flush = time.time()
127
+
128
+ while not self._stop_event.is_set():
129
+ try:
130
+ # Get log from queue with timeout
131
+ log_item = self._log_queue.get(timeout=0.1)
132
+ batch.append(log_item)
133
+
134
+ # Check if we should flush
135
+ should_flush = len(batch) >= self.batch_size or (
136
+ batch and time.time() - last_flush >= self.flush_interval
137
+ )
138
+
139
+ if should_flush:
140
+ self._send_batch(batch)
141
+ batch.clear()
142
+ last_flush = time.time()
143
+
144
+ except Empty:
145
+ # No logs in queue, check if we should flush existing batch
146
+ if batch and time.time() - last_flush >= self.flush_interval:
147
+ self._send_batch(batch)
148
+ batch.clear()
149
+ last_flush = time.time()
150
+ continue
151
+
152
+ # Flush remaining logs on shutdown
153
+ if batch:
154
+ self._send_batch(batch)
155
+
156
+ def _send_batch(self, batch: list[HTTPLogItem]) -> None:
157
+ """Send a batch of logs to Datadog."""
158
+ if not batch:
159
+ return
160
+
161
+ for attempt in range(self.max_retries + 1):
162
+ try:
163
+ http_log = HTTPLog(batch)
164
+ self.logs_api.submit_log(body=http_log)
165
+ return # Success
166
+
167
+ except Exception as e:
168
+ if attempt == self.max_retries:
169
+ # Last attempt failed, give up
170
+ self._handle_error(
171
+ f"Failed to send logs after {self.max_retries + 1} attempts: {e}"
172
+ )
173
+ else:
174
+ # Wait before retry (exponential backoff)
175
+ time.sleep(2**attempt)
176
+
177
+ def _handle_error(self, message: str) -> None:
178
+ """Handle errors that occur during log submission."""
179
+ # Log to stderr to avoid infinite recursion
180
+ print(f"DatadogHTTPHandler error: {message}", file=__import__("sys").stderr)
181
+
182
+ def emit(self, record: logging.LogRecord) -> None:
183
+ """Emit a log record."""
184
+ try:
185
+ # Ensure handler is properly initialized
186
+ if not hasattr(self, "_log_queue") or not hasattr(self, "api_key"):
187
+ # Handler not properly initialized, skip logging to avoid errors
188
+ return
189
+
190
+ log_item = self._format_log_item(record)
191
+ self._log_queue.put(log_item, block=False)
192
+ except Exception:
193
+ self.handleError(record)
194
+
195
+ def _format_log_item(self, record: logging.LogRecord) -> HTTPLogItem:
196
+ """Convert a LogRecord to a Datadog HTTPLogItem."""
197
+ # Format the message
198
+ message = self.format(record)
199
+
200
+ # Build the log item
201
+ log_item = HTTPLogItem(
202
+ message=message,
203
+ ddsource=self.source,
204
+ service=self.service,
205
+ hostname=self.hostname,
206
+ )
207
+
208
+ # Add tags
209
+ tags_list = []
210
+ if self.tags:
211
+ tags_list.extend(self.tags.split(","))
212
+
213
+ # Add log level as tag
214
+ tags_list.append(f"level:{record.levelname.lower()}")
215
+
216
+ # Add logger name as tag
217
+ tags_list.append(f"logger:{record.name}")
218
+
219
+ # Add environment if available
220
+ env = os.getenv("DD_ENV")
221
+ if env:
222
+ tags_list.append(f"env:{env}")
223
+
224
+ # Add version if available
225
+ version = os.getenv("DD_VERSION")
226
+ if version:
227
+ tags_list.append(f"version:{version}")
228
+
229
+ # Add any extra fields from the log record
230
+ if hasattr(record, "__dict__"):
231
+ for key, value in record.__dict__.items():
232
+ if key.startswith("dd_") and isinstance(value, (str, int, float, bool)):
233
+ # Custom Datadog fields
234
+ tags_list.append(f"{key[3:]}:{value}")
235
+
236
+ if tags_list:
237
+ log_item.ddtags = ",".join(tags_list)
238
+
239
+ return log_item
240
+
241
+ def flush(self) -> None:
242
+ """Flush any pending logs."""
243
+ # Signal worker to process remaining logs
244
+ if (
245
+ hasattr(self, "_worker_thread")
246
+ and self._worker_thread
247
+ and self._worker_thread.is_alive()
248
+ ):
249
+ # Wait a bit for the worker to process
250
+ time.sleep(0.1)
251
+
252
+ def close(self) -> None:
253
+ """Close the handler and cleanup resources."""
254
+ # Stop the worker
255
+ if hasattr(self, "_stop_event"):
256
+ self._stop_event.set()
257
+
258
+ # Wait for worker to finish
259
+ if (
260
+ hasattr(self, "_worker_thread")
261
+ and self._worker_thread
262
+ and self._worker_thread.is_alive()
263
+ ):
264
+ self._worker_thread.join(timeout=5.0)
265
+
266
+ # Close API client
267
+ if hasattr(self, "api_client"):
268
+ self.api_client.close()
269
+
270
+ super().close()
271
+
272
+ def health_check(self) -> bool:
273
+ """
274
+ Perform a health check to verify the handler is working.
275
+
276
+ Returns:
277
+ True if the handler is healthy, False otherwise.
278
+ """
279
+ try:
280
+ # Check if worker thread is alive
281
+ if not self._worker_thread or not self._worker_thread.is_alive():
282
+ return False
283
+
284
+ # Check if we can create a test log item
285
+ test_record = logging.LogRecord(
286
+ name="health_check",
287
+ level=logging.INFO,
288
+ pathname="",
289
+ lineno=0,
290
+ msg="Health check",
291
+ args=(),
292
+ exc_info=None,
293
+ )
294
+ self._format_log_item(test_record)
295
+ return True
296
+
297
+ except Exception:
298
+ return False
299
+
300
+ def get_queue_size(self) -> int:
301
+ """Get the current size of the log queue."""
302
+ return self._log_queue.qsize() if hasattr(self, "_log_queue") else 0
303
+
304
+ def __repr__(self) -> str:
305
+ """Return a string representation of the handler."""
306
+ return (
307
+ f"DatadogHTTPHandler(service={self.service!r}, "
308
+ f"source={self.source!r}, site={self.site!r})"
309
+ )
File without changes