1- import json
21import os
32import uuid
43import random
54import socket
6- import logging
75from collections .abc import Mapping
86from datetime import datetime , timezone
97from importlib import import_module
6563 from sentry_sdk .session import Session
6664 from sentry_sdk .spotlight import SpotlightClient
6765 from sentry_sdk .transport import Transport
66+ from sentry_sdk ._log_batcher import LogBatcher
6867
6968 I = TypeVar ("I" , bound = Integration ) # noqa: E741
7069
@@ -178,6 +177,7 @@ def __init__(self, options=None):
178177 self .transport = None # type: Optional[Transport]
179178 self .monitor = None # type: Optional[Monitor]
180179 self .metrics_aggregator = None # type: Optional[MetricsAggregator]
180+ self .log_batcher = None # type: Optional[LogBatcher]
181181
182182 def __getstate__ (self , * args , ** kwargs ):
183183 # type: (*Any, **Any) -> Any
@@ -375,6 +375,12 @@ def _capture_envelope(envelope):
375375 "Metrics not supported on Python 3.6 and lower with gevent."
376376 )
377377
378+ self .log_batcher = None
379+ if experiments .get ("enable_logs" , False ):
380+ from sentry_sdk ._log_batcher import LogBatcher
381+
382+ self .log_batcher = LogBatcher (capture_func = _capture_envelope )
383+
378384 max_request_body_size = ("always" , "never" , "small" , "medium" )
379385 if self .options ["max_request_body_size" ] not in max_request_body_size :
380386 raise ValueError (
@@ -451,6 +457,7 @@ def _capture_envelope(envelope):
451457 if (
452458 self .monitor
453459 or self .metrics_aggregator
460+ or self .log_batcher
454461 or has_profiling_enabled (self .options )
455462 or isinstance (self .transport , BaseHttpTransport )
456463 ):
@@ -868,15 +875,11 @@ def capture_event(
868875
869876 def _capture_experimental_log (self , current_scope , log ):
870877 # type: (Scope, Log) -> None
871- logs_enabled = self .options ["_experiments" ].get ("enable_sentry_logs " , False )
878+ logs_enabled = self .options ["_experiments" ].get ("enable_logs " , False )
872879 if not logs_enabled :
873880 return
874881 isolation_scope = current_scope .get_isolation_scope ()
875882
876- headers = {
877- "sent_at" : format_timestamp (datetime .now (timezone .utc )),
878- } # type: dict[str, object]
879-
880883 environment = self .options .get ("environment" )
881884 if environment is not None and "sentry.environment" not in log ["attributes" ]:
882885 log ["attributes" ]["sentry.environment" ] = environment
@@ -900,59 +903,18 @@ def _capture_experimental_log(self, current_scope, log):
900903 # If debug is enabled, log the log to the console
901904 debug = self .options .get ("debug" , False )
902905 if debug :
903- severity_text_to_logging_level = {
904- "trace" : logging .DEBUG ,
905- "debug" : logging .DEBUG ,
906- "info" : logging .INFO ,
907- "warn" : logging .WARNING ,
908- "error" : logging .ERROR ,
909- "fatal" : logging .CRITICAL ,
910- }
911- logger .log (
912- severity_text_to_logging_level .get (log ["severity_text" ], logging .DEBUG ),
913- f'[Sentry Logs] { log ["body" ]} ' ,
906+ logger .debug (
907+ f'[Sentry Logs] [{ log .get ("severity_text" )} ] { log .get ("body" )} '
914908 )
915909
916- envelope = Envelope (headers = headers )
917-
918- before_emit_log = self .options ["_experiments" ].get ("before_emit_log" )
919- if before_emit_log is not None :
920- log = before_emit_log (log , {})
910+ before_send_log = self .options ["_experiments" ].get ("before_send_log" )
911+ if before_send_log is not None :
912+ log = before_send_log (log , {})
921913 if log is None :
922914 return
923915
924- def format_attribute (key , val ):
925- # type: (str, int | float | str | bool) -> Any
926- if isinstance (val , bool ):
927- return {"key" : key , "value" : {"boolValue" : val }}
928- if isinstance (val , int ):
929- return {"key" : key , "value" : {"intValue" : str (val )}}
930- if isinstance (val , float ):
931- return {"key" : key , "value" : {"doubleValue" : val }}
932- if isinstance (val , str ):
933- return {"key" : key , "value" : {"stringValue" : val }}
934- return {"key" : key , "value" : {"stringValue" : json .dumps (val )}}
935-
936- otel_log = {
937- "severityText" : log ["severity_text" ],
938- "severityNumber" : log ["severity_number" ],
939- "body" : {"stringValue" : log ["body" ]},
940- "timeUnixNano" : str (log ["time_unix_nano" ]),
941- "attributes" : [
942- format_attribute (k , v ) for (k , v ) in log ["attributes" ].items ()
943- ],
944- }
945-
946- if "trace_id" in log :
947- otel_log ["traceId" ] = log ["trace_id" ]
948-
949- envelope .add_log (otel_log ) # TODO: batch these
950-
951- if self .spotlight :
952- self .spotlight .capture_envelope (envelope )
953-
954- if self .transport is not None :
955- self .transport .capture_envelope (envelope )
916+ if self .log_batcher :
917+ self .log_batcher .add (log )
956918
957919 def capture_session (
958920 self , session # type: Session
@@ -1006,6 +968,8 @@ def close(
1006968 self .session_flusher .kill ()
1007969 if self .metrics_aggregator is not None :
1008970 self .metrics_aggregator .kill ()
971+ if self .log_batcher is not None :
972+ self .log_batcher .kill ()
1009973 if self .monitor :
1010974 self .monitor .kill ()
1011975 self .transport .kill ()
@@ -1030,6 +994,8 @@ def flush(
1030994 self .session_flusher .flush ()
1031995 if self .metrics_aggregator is not None :
1032996 self .metrics_aggregator .flush ()
997+ if self .log_batcher is not None :
998+ self .log_batcher .flush ()
1033999 self .transport .flush (timeout = timeout , callback = callback )
10341000
10351001 def __enter__ (self ):
0 commit comments