@@ -901,18 +901,31 @@ def add_span_processor(span_processor: SpanProcessor) -> None:
901
901
# try loading credentials (and thus token) from file if a token is not already available
902
902
# this takes the lowest priority, behind the token passed to `configure` and the environment variable
903
903
if not self .token :
904
- credentials = LogfireCredentials .load_creds_file (self .data_dir )
905
-
906
- # if we still don't have a token, try initializing a new project and writing a new creds file
907
- # note, we only do this if `send_to_logfire` is explicitly `True`, not 'if-token-present'
908
- if self .send_to_logfire is True and credentials is None :
909
- client = LogfireClient .from_url (self .advanced .base_url )
910
- credentials = LogfireCredentials .initialize_project (client = client )
911
- credentials .write_creds_file (self .data_dir )
912
-
913
- if credentials is not None :
914
- self .token = credentials .token
915
- self .advanced .base_url = self .advanced .base_url or credentials .logfire_api_url
904
+ try :
905
+ credentials = LogfireCredentials .load_creds_file (self .data_dir )
906
+
907
+ # if we still don't have a token, try initializing a new project and writing a new creds file
908
+ # note, we only do this if `send_to_logfire` is explicitly `True`, not 'if-token-present'
909
+ if self .send_to_logfire is True and credentials is None :
910
+ client = LogfireClient .from_url (self .advanced .base_url )
911
+ credentials = LogfireCredentials .initialize_project (client = client )
912
+ credentials .write_creds_file (self .data_dir )
913
+
914
+ if credentials is not None :
915
+ self .token = credentials .token
916
+ self .advanced .base_url = self .advanced .base_url or credentials .logfire_api_url
917
+ except LogfireConfigError :
918
+ if self .advanced .base_url is not None and self .advanced .base_url .startswith ('grpc://' ):
919
+ # if sending to a custom GRPC endpoint, we allow no
920
+ # token (advanced use case, maybe e.g. otel
921
+ # collector which has the token configured there)
922
+ pass
923
+ else :
924
+ raise
925
+
926
+ base_url = None
927
+ # NB: grpc (http/2) requires headers to be lowercase
928
+ headers = {'user-agent' : f'logfire/{ VERSION } ' }
916
929
917
930
if self .token :
918
931
@@ -929,14 +942,74 @@ def check_token():
929
942
thread .start ()
930
943
931
944
base_url = self .advanced .generate_base_url (self .token )
932
- headers = {'User-Agent' : f'logfire/{ VERSION } ' , 'Authorization' : self .token }
933
- session = OTLPExporterHttpSession ()
934
- session .headers .update (headers )
935
- span_exporter = BodySizeCheckingOTLPSpanExporter (
936
- endpoint = urljoin (base_url , '/v1/traces' ),
937
- session = session ,
938
- compression = Compression .Gzip ,
939
- )
945
+ headers ['authorization' ] = self .token
946
+ elif (
947
+ self .send_to_logfire is True
948
+ and (provided_base_url := self .advanced .base_url ) is not None
949
+ and provided_base_url .startswith ('grpc' )
950
+ ):
951
+ # We may not need a token if we are sending to a grpc
952
+ # endpoint; it could be an otel collector acting as a proxy
953
+ base_url = provided_base_url
954
+
955
+ if base_url is not None :
956
+ if base_url .startswith ('grpc://' ):
957
+ from grpc import Compression as GrpcCompression
958
+ from opentelemetry .exporter .otlp .proto .grpc ._log_exporter import (
959
+ OTLPLogExporter as GrpcOTLPLogExporter ,
960
+ )
961
+ from opentelemetry .exporter .otlp .proto .grpc .metric_exporter import (
962
+ OTLPMetricExporter as GrpcOTLPMetricExporter ,
963
+ )
964
+ from opentelemetry .exporter .otlp .proto .grpc .trace_exporter import (
965
+ OTLPSpanExporter as GrpcOTLPSpanExporter ,
966
+ )
967
+
968
+ span_exporter = GrpcOTLPSpanExporter (
969
+ endpoint = base_url , headers = headers , compression = GrpcCompression .Gzip
970
+ )
971
+ metric_exporter = GrpcOTLPMetricExporter (
972
+ endpoint = base_url ,
973
+ headers = headers ,
974
+ compression = GrpcCompression .Gzip ,
975
+ # I'm pretty sure that this line here is redundant,
976
+ # and that passing it to the QuietMetricExporter is what matters
977
+ # because the PeriodicExportingMetricReader will read it from there.
978
+ preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
979
+ )
980
+ log_exporter = GrpcOTLPLogExporter (
981
+ endpoint = base_url ,
982
+ headers = headers ,
983
+ compression = GrpcCompression .Gzip ,
984
+ )
985
+ elif base_url .startswith ('http://' ) or base_url .startswith ('https://' ):
986
+ session = OTLPExporterHttpSession ()
987
+ session .headers .update (headers )
988
+ span_exporter = BodySizeCheckingOTLPSpanExporter (
989
+ endpoint = urljoin (base_url , '/v1/traces' ),
990
+ session = session ,
991
+ compression = Compression .Gzip ,
992
+ )
993
+ metric_exporter = OTLPMetricExporter (
994
+ endpoint = urljoin (base_url , '/v1/metrics' ),
995
+ headers = headers ,
996
+ session = session ,
997
+ compression = Compression .Gzip ,
998
+ # I'm pretty sure that this line here is redundant,
999
+ # and that passing it to the QuietMetricExporter is what matters
1000
+ # because the PeriodicExportingMetricReader will read it from there.
1001
+ preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
1002
+ )
1003
+ log_exporter = OTLPLogExporter (
1004
+ endpoint = urljoin (base_url , '/v1/logs' ),
1005
+ session = session ,
1006
+ compression = Compression .Gzip ,
1007
+ )
1008
+ else :
1009
+ raise ValueError (
1010
+ "Invalid base_url: {base_url}. Must start with 'http://', 'https://', or 'grpc://'."
1011
+ )
1012
+
940
1013
span_exporter = QuietSpanExporter (span_exporter )
941
1014
span_exporter = RetryFewerSpansSpanExporter (span_exporter )
942
1015
span_exporter = RemovePendingSpansExporter (span_exporter )
@@ -949,30 +1022,17 @@ def check_token():
949
1022
950
1023
# TODO should we warn here if we have metrics but we're in emscripten?
951
1024
# I guess we could do some hack to use InMemoryMetricReader and call it after user code has run?
1025
+ # (The point is that PeriodicExportingMetricReader uses threads which fail in Pyodide / Emscripten)
952
1026
if metric_readers is not None and not emscripten :
953
1027
metric_readers .append (
954
1028
PeriodicExportingMetricReader (
955
1029
QuietMetricExporter (
956
- OTLPMetricExporter (
957
- endpoint = urljoin (base_url , '/v1/metrics' ),
958
- headers = headers ,
959
- session = session ,
960
- compression = Compression .Gzip ,
961
- # I'm pretty sure that this line here is redundant,
962
- # and that passing it to the QuietMetricExporter is what matters
963
- # because the PeriodicExportingMetricReader will read it from there.
964
- preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
965
- ),
1030
+ metric_exporter ,
966
1031
preferred_temporality = METRICS_PREFERRED_TEMPORALITY ,
967
1032
)
968
1033
)
969
1034
)
970
1035
971
- log_exporter = OTLPLogExporter (
972
- endpoint = urljoin (base_url , '/v1/logs' ),
973
- session = session ,
974
- compression = Compression .Gzip ,
975
- )
976
1036
log_exporter = QuietLogExporter (log_exporter )
977
1037
978
1038
if emscripten : # pragma: no cover
0 commit comments