@@ -893,21 +893,33 @@ def add_span_processor(span_processor: SpanProcessor) -> None:
893
893
# try loading credentials (and thus token) from file if a token is not already available
894
894
# this takes the lowest priority, behind the token passed to `configure` and the environment variable
895
895
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 } ' }
911
923
if self .token is not None :
912
924
913
925
def check_token ():
@@ -923,14 +935,70 @@ def check_token():
923
935
thread .start ()
924
936
925
937
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
+
934
1002
span_exporter = QuietSpanExporter (span_exporter )
935
1003
span_exporter = RetryFewerSpansSpanExporter (span_exporter )
936
1004
span_exporter = RemovePendingSpansExporter (span_exporter )
@@ -946,30 +1014,17 @@ def check_token():
946
1014
947
1015
# TODO should we warn here if we have metrics but we're in emscripten?
948
1016
# 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)
949
1018
if metric_readers is not None and not emscripten :
950
1019
metric_readers .append (
951
1020
PeriodicExportingMetricReader (
952
1021
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 ,
963
1023
preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
964
1024
)
965
1025
)
966
1026
)
967
1027
968
- log_exporter = OTLPLogExporter (
969
- endpoint = urljoin (base_url , '/v1/logs' ),
970
- session = session ,
971
- compression = Compression .Gzip ,
972
- )
973
1028
log_exporter = QuietLogExporter (log_exporter )
974
1029
975
1030
if emscripten : # pragma: no cover
0 commit comments