@@ -893,21 +893,33 @@ def add_span_processor(span_processor: SpanProcessor) -> None:
893893 # try loading credentials (and thus token) from file if a token is not already available
894894 # this takes the lowest priority, behind the token passed to `configure` and the environment variable
895895 if self .token is None :
896- credentials = LogfireCredentials .load_creds_file (self .data_dir )
897-
898- # if we still don't have a token, try initializing a new project and writing a new creds file
899- # note, we only do this if `send_to_logfire` is explicitly `True`, not 'if-token-present'
900- if self .send_to_logfire is True and credentials is None :
901- credentials = LogfireCredentials .initialize_project (
902- logfire_api_url = self .advanced .base_url ,
903- session = requests .Session (),
904- )
905- credentials .write_creds_file (self .data_dir )
906-
907- if credentials is not None :
908- self .token = credentials .token
909- self .advanced .base_url = self .advanced .base_url or credentials .logfire_api_url
910-
896+ try :
897+ credentials = LogfireCredentials .load_creds_file (self .data_dir )
898+
899+ # if we still don't have a token, try initializing a new project and writing a new creds file
900+ # note, we only do this if `send_to_logfire` is explicitly `True`, not 'if-token-present'
901+ if self .send_to_logfire is True and credentials is None :
902+ credentials = LogfireCredentials .initialize_project (
903+ logfire_api_url = self .advanced .base_url ,
904+ session = requests .Session (),
905+ )
906+ credentials .write_creds_file (self .data_dir )
907+
908+ if credentials is not None :
909+ self .token = credentials .token
910+ self .advanced .base_url = self .advanced .base_url or credentials .logfire_api_url
911+ except LogfireConfigError :
912+ if self .advanced .base_url is not None :
913+ # if sending to a custom base url, we allow no
914+ # token (advanced use case, maybe e.g. otel
915+ # collector which has the token configured there)
916+ pass
917+ else :
918+ raise
919+
920+ base_url = None
921+ # NB: grpc (http/2) requires headers to be lowercase
922+ headers = {'user-agent' : f'logfire/{ VERSION } ' }
911923 if self .token is not None :
912924
913925 def check_token ():
@@ -923,14 +935,70 @@ def check_token():
923935 thread .start ()
924936
925937 base_url = self .advanced .generate_base_url (self .token )
926- headers = {'User-Agent' : f'logfire/{ VERSION } ' , 'Authorization' : self .token }
927- session = OTLPExporterHttpSession ()
928- session .headers .update (headers )
929- span_exporter = BodySizeCheckingOTLPSpanExporter (
930- endpoint = urljoin (base_url , '/v1/traces' ),
931- session = session ,
932- compression = Compression .Gzip ,
933- )
938+ headers ['authorization' ] = self .token
939+ elif self .send_to_logfire is True and self .advanced .base_url is not None :
940+ # We may not need a token if we are sending to a custom
941+ # base URL
942+ base_url = self .advanced .base_url
943+
944+ if base_url is not None :
945+ if base_url .startswith ('grpc://' ):
946+ from grpc import Compression as GrpcCompression
947+ from opentelemetry .exporter .otlp .proto .grpc ._log_exporter import (
948+ OTLPLogExporter as GrpcOTLPLogExporter ,
949+ )
950+ from opentelemetry .exporter .otlp .proto .grpc .metric_exporter import (
951+ OTLPMetricExporter as GrpcOTLPMetricExporter ,
952+ )
953+ from opentelemetry .exporter .otlp .proto .grpc .trace_exporter import (
954+ OTLPSpanExporter as GrpcOTLPSpanExporter ,
955+ )
956+
957+ span_exporter = GrpcOTLPSpanExporter (
958+ endpoint = base_url , headers = headers , compression = GrpcCompression .Gzip
959+ )
960+ metric_exporter = GrpcOTLPMetricExporter (
961+ endpoint = base_url ,
962+ headers = headers ,
963+ compression = GrpcCompression .Gzip ,
964+ # I'm pretty sure that this line here is redundant,
965+ # and that passing it to the QuietMetricExporter is what matters
966+ # because the PeriodicExportingMetricReader will read it from there.
967+ preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
968+ )
969+ log_exporter = GrpcOTLPLogExporter (
970+ endpoint = base_url ,
971+ headers = headers ,
972+ compression = GrpcCompression .Gzip ,
973+ )
974+ elif base_url .startswith ('http://' ) or base_url .startswith ('https://' ):
975+ session = OTLPExporterHttpSession ()
976+ session .headers .update (headers )
977+ span_exporter = BodySizeCheckingOTLPSpanExporter (
978+ endpoint = urljoin (base_url , '/v1/traces' ),
979+ session = session ,
980+ compression = Compression .Gzip ,
981+ )
982+ metric_exporter = OTLPMetricExporter (
983+ endpoint = urljoin (base_url , '/v1/metrics' ),
984+ headers = headers ,
985+ session = session ,
986+ compression = Compression .Gzip ,
987+ # I'm pretty sure that this line here is redundant,
988+ # and that passing it to the QuietMetricExporter is what matters
989+ # because the PeriodicExportingMetricReader will read it from there.
990+ preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
991+ )
992+ log_exporter = OTLPLogExporter (
993+ endpoint = urljoin (base_url , '/v1/logs' ),
994+ session = session ,
995+ compression = Compression .Gzip ,
996+ )
997+ else :
998+ raise ValueError (
999+ "Invalid base_url: {base_url}. Must start with 'http://', 'https://', or 'grpc://'."
1000+ )
1001+
9341002 span_exporter = QuietSpanExporter (span_exporter )
9351003 span_exporter = RetryFewerSpansSpanExporter (span_exporter )
9361004 span_exporter = RemovePendingSpansExporter (span_exporter )
@@ -946,30 +1014,17 @@ def check_token():
9461014
9471015 # TODO should we warn here if we have metrics but we're in emscripten?
9481016 # I guess we could do some hack to use InMemoryMetricReader and call it after user code has run?
1017+ # (The point is that PeriodicExportingMetricReader uses threads which fail in Pyodide / Emscripten)
9491018 if metric_readers is not None and not emscripten :
9501019 metric_readers .append (
9511020 PeriodicExportingMetricReader (
9521021 QuietMetricExporter (
953- OTLPMetricExporter (
954- endpoint = urljoin (base_url , '/v1/metrics' ),
955- headers = headers ,
956- session = session ,
957- compression = Compression .Gzip ,
958- # I'm pretty sure that this line here is redundant,
959- # and that passing it to the QuietMetricExporter is what matters
960- # because the PeriodicExportingMetricReader will read it from there.
961- preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
962- ),
1022+ metric_exporter ,
9631023 preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
9641024 )
9651025 )
9661026 )
9671027
968- log_exporter = OTLPLogExporter (
969- endpoint = urljoin (base_url , '/v1/logs' ),
970- session = session ,
971- compression = Compression .Gzip ,
972- )
9731028 log_exporter = QuietLogExporter (log_exporter )
9741029
9751030 if emscripten : # pragma: no cover
0 commit comments