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 ):
@@ -877,15 +884,11 @@ def capture_event(
877884
878885 def _capture_experimental_log (self , current_scope , log ):
879886 # type: (Scope, Log) -> None
880- logs_enabled = self .options ["_experiments" ].get ("enable_sentry_logs " , False )
887+ logs_enabled = self .options ["_experiments" ].get ("enable_logs " , False )
881888 if not logs_enabled :
882889 return
883890 isolation_scope = current_scope .get_isolation_scope ()
884891
885- headers = {
886- "sent_at" : format_timestamp (datetime .now (timezone .utc )),
887- } # type: dict[str, object]
888-
889892 environment = self .options .get ("environment" )
890893 if environment is not None and "sentry.environment" not in log ["attributes" ]:
891894 log ["attributes" ]["sentry.environment" ] = environment
@@ -909,59 +912,18 @@ def _capture_experimental_log(self, current_scope, log):
909912 # If debug is enabled, log the log to the console
910913 debug = self .options .get ("debug" , False )
911914 if debug :
912- severity_text_to_logging_level = {
913- "trace" : logging .DEBUG ,
914- "debug" : logging .DEBUG ,
915- "info" : logging .INFO ,
916- "warn" : logging .WARNING ,
917- "error" : logging .ERROR ,
918- "fatal" : logging .CRITICAL ,
919- }
920- logger .log (
921- severity_text_to_logging_level .get (log ["severity_text" ], logging .DEBUG ),
922- f'[Sentry Logs] { log ["body" ]} ' ,
915+ logger .debug (
916+ f'[Sentry Logs] [{ log .get ("severity_text" )} ] { log .get ("body" )} '
923917 )
924918
925- envelope = Envelope (headers = headers )
926-
927- before_emit_log = self .options ["_experiments" ].get ("before_emit_log" )
928- if before_emit_log is not None :
929- log = before_emit_log (log , {})
919+ before_send_log = self .options ["_experiments" ].get ("before_send_log" )
920+ if before_send_log is not None :
921+ log = before_send_log (log , {})
930922 if log is None :
931923 return
932924
933- def format_attribute (key , val ):
934- # type: (str, int | float | str | bool) -> Any
935- if isinstance (val , bool ):
936- return {"key" : key , "value" : {"boolValue" : val }}
937- if isinstance (val , int ):
938- return {"key" : key , "value" : {"intValue" : str (val )}}
939- if isinstance (val , float ):
940- return {"key" : key , "value" : {"doubleValue" : val }}
941- if isinstance (val , str ):
942- return {"key" : key , "value" : {"stringValue" : val }}
943- return {"key" : key , "value" : {"stringValue" : json .dumps (val )}}
944-
945- otel_log = {
946- "severityText" : log ["severity_text" ],
947- "severityNumber" : log ["severity_number" ],
948- "body" : {"stringValue" : log ["body" ]},
949- "timeUnixNano" : str (log ["time_unix_nano" ]),
950- "attributes" : [
951- format_attribute (k , v ) for (k , v ) in log ["attributes" ].items ()
952- ],
953- }
954-
955- if "trace_id" in log :
956- otel_log ["traceId" ] = log ["trace_id" ]
957-
958- envelope .add_log (otel_log ) # TODO: batch these
959-
960- if self .spotlight :
961- self .spotlight .capture_envelope (envelope )
962-
963- if self .transport is not None :
964- self .transport .capture_envelope (envelope )
925+ if self .log_batcher :
926+ self .log_batcher .add (log )
965927
966928 def capture_session (
967929 self , session # type: Session
@@ -1015,6 +977,8 @@ def close(
1015977 self .session_flusher .kill ()
1016978 if self .metrics_aggregator is not None :
1017979 self .metrics_aggregator .kill ()
980+ if self .log_batcher is not None :
981+ self .log_batcher .kill ()
1018982 if self .monitor :
1019983 self .monitor .kill ()
1020984 self .transport .kill ()
@@ -1039,6 +1003,8 @@ def flush(
10391003 self .session_flusher .flush ()
10401004 if self .metrics_aggregator is not None :
10411005 self .metrics_aggregator .flush ()
1006+ if self .log_batcher is not None :
1007+ self .log_batcher .flush ()
10421008 self .transport .flush (timeout = timeout , callback = callback )
10431009
10441010 def __enter__ (self ):
0 commit comments