Skip to content

Commit 707334c

Browse files
committed
add dsn support, service in options, sdk metadata
1 parent 0a208fa commit 707334c

9 files changed

Lines changed: 385 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.3] - 2026-06-11
9+
10+
### Added
11+
12+
- DSN support: `ClientOptions(dsn="https://lp_key@host[/path]")` (plus `parse_dsn` / `DsnParseError` exported from the package root). Explicit `api_url` + `api_key` keep working; a malformed DSN raises at init time
13+
- `ClientOptions.service`: configure the service once and call log methods with just the message — `client.info("user logged in")`, `client.error("boom", exc)`. The legacy `(service, message)` form keeps working
14+
- Every entry now carries `metadata.sdk = {"name": "logtide-python", "version": ...}` (caller-provided `sdk` key wins)
15+
16+
### Fixed
17+
18+
- `logtide_sdk.__version__` was stale (0.9.1); version now has a single runtime source (`logtide_sdk._version`)
19+
820
## [0.9.2] - 2026-06-11
921

1022
### Added

logtide_sdk/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""LogTide SDK - Official Python SDK for LogTide."""
22

33
from logtide_sdk.client import LogTideClient, serialize_exception
4+
from logtide_sdk.dsn import DsnParseError, DsnParts, parse_dsn
45
from logtide_sdk.tracecontext import (
56
TraceContext,
67
format_traceparent,
@@ -31,7 +32,7 @@
3132
except ImportError:
3233
pass
3334

34-
__version__ = "0.9.1"
35+
from logtide_sdk._version import VERSION as __version__
3536

3637
__all__ = [
3738
"AggregatedStatsOptions",
@@ -48,11 +49,14 @@
4849
"LogTideHandler",
4950
"LogsResponse",
5051
"PayloadLimitsOptions",
52+
"DsnParseError",
53+
"DsnParts",
5154
"QueryOptions",
5255
"TraceContext",
5356
"format_traceparent",
5457
"generate_span_id",
5558
"generate_trace_id",
59+
"parse_dsn",
5660
"parse_traceparent",
5761
"resolve_trace_id",
5862
"serialize_exception",

logtide_sdk/_version.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Single source of truth for the SDK version at runtime.
2+
3+
Kept in its own module (instead of ``__init__``) so internal modules can
4+
import it without circular imports. Bump together with ``pyproject.toml``.
5+
"""
6+
7+
VERSION = "0.9.3"
8+
SDK_NAME = "logtide-python"

logtide_sdk/async_client.py

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from logtide_sdk.enums import CircuitState, LogLevel
2222
from logtide_sdk.exceptions import CircuitBreakerOpenError
2323
from logtide_sdk.json_encoder import logtide_json_dumps
24+
from logtide_sdk._version import SDK_NAME, VERSION
2425
from logtide_sdk.tracecontext import generate_trace_id
2526
from logtide_sdk.models import (
2627
AggregatedStatsOptions,
@@ -164,6 +165,10 @@ async def log(self, entry: LogEntry) -> None:
164165
if self.options.global_metadata:
165166
entry.metadata = {**self.options.global_metadata, **entry.metadata}
166167

168+
# Stamp SDK identity (spec 003 §3); caller-provided value wins
169+
if "sdk" not in entry.metadata:
170+
entry.metadata["sdk"] = {"name": SDK_NAME, "version": VERSION}
171+
167172
self._apply_payload_limits(entry)
168173

169174
should_flush = False
@@ -183,85 +188,114 @@ async def log(self, entry: LogEntry) -> None:
183188
if should_flush:
184189
await self.flush()
185190

191+
def _resolve_call(
192+
self,
193+
service_or_message: str,
194+
message_or_payload: Any,
195+
payload: Any,
196+
) -> tuple[str, str, Any]:
197+
"""Support both call forms (spec 004 §3); see LogTideClient._resolve_call."""
198+
if isinstance(message_or_payload, str):
199+
return service_or_message, message_or_payload, payload
200+
if self.options.service is None:
201+
raise ValueError(
202+
"No service configured: set ClientOptions.service to call "
203+
"log methods with just a message, or pass (service, message)"
204+
)
205+
resolved_payload = message_or_payload if message_or_payload is not None else payload
206+
return self.options.service, service_or_message, resolved_payload
207+
186208
async def debug(
187209
self,
188-
service: str,
189-
message: str,
210+
service_or_message: str,
211+
message: str | dict[str, Any] | None = None,
190212
metadata: dict[str, Any] | None = None,
191213
*,
192214
trace_id: str | None = None,
193215
span_id: str | None = None,
194216
) -> None:
195217
"""Log a DEBUG-level message."""
218+
service, resolved_message, resolved_metadata = self._resolve_call(
219+
service_or_message, message, metadata
220+
)
196221
await self.log(
197222
LogEntry(
198223
service=service,
199224
level=LogLevel.DEBUG,
200-
message=message,
201-
metadata=metadata or {},
225+
message=resolved_message,
226+
metadata=resolved_metadata or {},
202227
trace_id=trace_id,
203228
span_id=span_id,
204229
)
205230
)
206231

207232
async def info(
208233
self,
209-
service: str,
210-
message: str,
234+
service_or_message: str,
235+
message: str | dict[str, Any] | None = None,
211236
metadata: dict[str, Any] | None = None,
212237
*,
213238
trace_id: str | None = None,
214239
span_id: str | None = None,
215240
) -> None:
216241
"""Log an INFO-level message."""
242+
service, resolved_message, resolved_metadata = self._resolve_call(
243+
service_or_message, message, metadata
244+
)
217245
await self.log(
218246
LogEntry(
219247
service=service,
220248
level=LogLevel.INFO,
221-
message=message,
222-
metadata=metadata or {},
249+
message=resolved_message,
250+
metadata=resolved_metadata or {},
223251
trace_id=trace_id,
224252
span_id=span_id,
225253
)
226254
)
227255

228256
async def warn(
229257
self,
230-
service: str,
231-
message: str,
258+
service_or_message: str,
259+
message: str | dict[str, Any] | None = None,
232260
metadata: dict[str, Any] | None = None,
233261
*,
234262
trace_id: str | None = None,
235263
span_id: str | None = None,
236264
) -> None:
237265
"""Log a WARN-level message."""
266+
service, resolved_message, resolved_metadata = self._resolve_call(
267+
service_or_message, message, metadata
268+
)
238269
await self.log(
239270
LogEntry(
240271
service=service,
241272
level=LogLevel.WARN,
242-
message=message,
243-
metadata=metadata or {},
273+
message=resolved_message,
274+
metadata=resolved_metadata or {},
244275
trace_id=trace_id,
245276
span_id=span_id,
246277
)
247278
)
248279

249280
async def error(
250281
self,
251-
service: str,
252-
message: str,
282+
service_or_message: str,
283+
message: str | dict[str, Any] | Exception | None = None,
253284
metadata_or_error: dict[str, Any] | Exception | None = None,
254285
*,
255286
trace_id: str | None = None,
256287
span_id: str | None = None,
257288
) -> None:
258289
"""Log an ERROR-level message. Accepts an Exception for automatic serialization."""
259-
metadata = self._process_metadata_or_error(metadata_or_error)
290+
service, resolved_message, payload = self._resolve_call(
291+
service_or_message, message, metadata_or_error
292+
)
293+
metadata = self._process_metadata_or_error(payload)
260294
await self.log(
261295
LogEntry(
262296
service=service,
263297
level=LogLevel.ERROR,
264-
message=message,
298+
message=resolved_message,
265299
metadata=metadata,
266300
trace_id=trace_id,
267301
span_id=span_id,
@@ -270,20 +304,23 @@ async def error(
270304

271305
async def critical(
272306
self,
273-
service: str,
274-
message: str,
307+
service_or_message: str,
308+
message: str | dict[str, Any] | Exception | None = None,
275309
metadata_or_error: dict[str, Any] | Exception | None = None,
276310
*,
277311
trace_id: str | None = None,
278312
span_id: str | None = None,
279313
) -> None:
280314
"""Log a CRITICAL-level message. Accepts an Exception for automatic serialization."""
281-
metadata = self._process_metadata_or_error(metadata_or_error)
315+
service, resolved_message, payload = self._resolve_call(
316+
service_or_message, message, metadata_or_error
317+
)
318+
metadata = self._process_metadata_or_error(payload)
282319
await self.log(
283320
LogEntry(
284321
service=service,
285322
level=LogLevel.CRITICAL,
286-
message=message,
323+
message=resolved_message,
287324
metadata=metadata,
288325
trace_id=trace_id,
289326
span_id=span_id,

0 commit comments

Comments
 (0)