1717 "Install it with: pip install logtide-sdk[async]"
1818 )
1919
20+ from logtide_sdk ._base_client import BaseClient
21+ from logtide_sdk ._retry import classify_failure
22+ from logtide_sdk ._version import SDK_NAME , VERSION
2023from logtide_sdk .circuit_breaker import CircuitBreaker
21- from logtide_sdk .client import _process_value , serialize_exception
2224from logtide_sdk .enums import CircuitState , LogLevel
2325from logtide_sdk .exceptions import CircuitBreakerOpenError
2426from logtide_sdk .json_encoder import logtide_json_dumps
25- from logtide_sdk ._retry import classify_failure
26- from logtide_sdk ._version import SDK_NAME , VERSION
27- from logtide_sdk .scope import get_current_scope
28- from logtide_sdk .tracecontext import active_trace_context , generate_trace_id
2927from logtide_sdk .models import (
3028 AggregatedStatsOptions ,
3129 AggregatedStatsResponse ,
3230 ClientMetrics ,
3331 ClientOptions ,
3432 LogEntry ,
3533 LogsResponse ,
36- PayloadLimitsOptions ,
3734 QueryOptions ,
3835)
3936
4037
41- class AsyncLogTideClient :
38+ class AsyncLogTideClient ( BaseClient ) :
4239 """
4340 Async LogTide SDK Client.
4441
@@ -65,9 +62,9 @@ def __init__(self, options: ClientOptions) -> None:
6562 Args:
6663 options: Client configuration options (same as LogTideClient)
6764 """
68- self .options = options
65+ super ().__init__ (options = options )
66+
6967 self ._buffer : list [LogEntry ] = []
70- self ._trace_id : str | None = None
7168 self ._buffer_lock : asyncio .Lock | None = None # created lazily in first async call
7269 self ._metrics_lock = ThreadingLock ()
7370 self ._metrics = ClientMetrics ()
@@ -76,7 +73,6 @@ def __init__(self, options: ClientOptions) -> None:
7673 reset_timeout_ms = options .circuit_breaker_reset_ms ,
7774 )
7875 self ._latency_window : list [float ] = []
79- self ._payload_limits = options .payload_limits or PayloadLimitsOptions ()
8076 self ._session : aiohttp .ClientSession | None = None
8177 self ._flush_task : Any | None = None # asyncio.Task[None]
8278 self ._closed = False
@@ -130,18 +126,6 @@ async def close(self) -> None:
130126 if self .options .debug :
131127 print ("[LogTide] Async client closed" )
132128
133- # -----------------------------------------------------------------------
134- # Trace ID helpers
135- # -----------------------------------------------------------------------
136-
137- def set_trace_id (self , trace_id : str | None ) -> None :
138- """Set trace ID for subsequent logs."""
139- self ._trace_id = trace_id
140-
141- def get_trace_id (self ) -> str | None :
142- """Return the current trace ID."""
143- return self ._trace_id
144-
145129 # -----------------------------------------------------------------------
146130 # Logging methods
147131 # -----------------------------------------------------------------------
@@ -153,31 +137,10 @@ async def log(self, entry: LogEntry) -> None:
153137 Args:
154138 entry: Pre-built log entry
155139 """
156- if self ._closed :
140+ if self ._is_logging_disabled () :
157141 return
158142
159- if entry .metadata is None :
160- entry .metadata = {}
161-
162- # Active-span trace context (resolution order per spec 005 §4:
163- # explicit -> active span -> scope -> client context/generation).
164- if entry .trace_id is None :
165- active_trace , active_span = active_trace_context ()
166- if active_trace is not None :
167- entry .trace_id = active_trace
168- if entry .span_id is None :
169- entry .span_id = active_span
170-
171- # Merge the current scope (tags, user, breadcrumbs, session, trace ctx).
172- # Runs before trace-id injection so the scope's trace context wins
173- # over auto-generation.
174- get_current_scope ().apply_to_entry (entry )
175-
176- if entry .trace_id is None :
177- if self .options .auto_trace_id :
178- entry .trace_id = generate_trace_id ()
179- elif self ._trace_id is not None :
180- entry .trace_id = self ._trace_id
143+ self ._pin_trace_id_to_entry (entry )
181144
182145 if self .options .global_metadata :
183146 entry .metadata = {** self .options .global_metadata , ** entry .metadata }
@@ -507,12 +470,6 @@ def _get_session(self) -> aiohttp.ClientSession:
507470 self ._session = aiohttp .ClientSession ()
508471 return self ._session
509472
510- def _get_headers (self ) -> dict [str , str ]:
511- return {
512- "X-API-Key" : self .options .api_key ,
513- "Content-Type" : "application/json" ,
514- }
515-
516473 async def _flush_loop (self ) -> None :
517474 """Background coroutine: flush on a fixed interval until closed."""
518475 interval = self .options .flush_interval / 1000.0
@@ -602,29 +559,7 @@ async def _send_logs(self, logs: list[LogEntry]) -> None:
602559 ) as response :
603560 response .raise_for_status ()
604561
605- def _process_metadata_or_error (
606- self , metadata_or_error : dict [str , Any ] | Exception | None
607- ) -> dict [str , Any ]:
608- if metadata_or_error is None :
609- return {}
610- if isinstance (metadata_or_error , dict ):
611- return metadata_or_error
612- return {"exception" : serialize_exception (metadata_or_error )}
613-
614- # NOTE: this is twice. (both in async and regular clients, maybe need base class)
615- def _apply_payload_limits (self , entry : LogEntry ) -> None :
616- """Enforce payload limits on entry.metadata in-place."""
617- if not entry .metadata :
618- return
619- lim = self ._payload_limits
620- entry .metadata = _process_value (entry .metadata , "root" , lim )
621-
622- raw = logtide_json_dumps (entry )
623- if len (raw .encode ()) > lim .max_log_size :
624- if self .options .debug :
625- print (f"[LogTide] Log entry too large ({ len (raw )} bytes), truncating metadata" )
626- entry .metadata = {"_truncated" : True , "_original_size" : len (raw .encode ())}
627-
562+ # TODO: refactor update latency code repeat
628563 def _update_latency (self , latency : float ) -> None :
629564 with self ._metrics_lock :
630565 self ._latency_window .append (latency )
0 commit comments