Skip to content

Commit 195c13f

Browse files
committed
support setting LOGFIRE_BASE_URL to grpc endpoint of otel collector
1 parent f84e5a7 commit 195c13f

File tree

2 files changed

+49
-25
lines changed

2 files changed

+49
-25
lines changed

src/exporters.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub fn span_exporter(
4545
endpoint: &str,
4646
headers: Option<HashMap<String, String>>,
4747
) -> Result<impl SpanExporter + use<>, ConfigureError> {
48-
let (source, protocol) = protocol_from_env("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")?;
48+
let (source, protocol) = protocol_from_env("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", endpoint)?;
4949

5050
let builder = opentelemetry_otlp::SpanExporter::builder();
5151

@@ -63,6 +63,10 @@ pub fn span_exporter(
6363
builder
6464
.with_tonic()
6565
.with_channel(
66+
// FIXME: .connect_lazy() requires a tokio runtime. A workaround (which
67+
// may complicate things is to create a tokio runtime in a background
68+
// thread and use that to drive the channel. This is the same as the
69+
// way that reqwest does a "blocking" client on top of the sync one.
6670
tonic::transport::Channel::builder(endpoint.try_into().map_err(
6771
|e: http::uri::InvalidUri| ConfigureError::Other(e.into()),
6872
)?)
@@ -116,7 +120,7 @@ pub fn metric_exporter(
116120
endpoint: &str,
117121
headers: Option<HashMap<String, String>>,
118122
) -> Result<impl PushMetricExporter + use<>, ConfigureError> {
119-
let (source, protocol) = protocol_from_env("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL")?;
123+
let (source, protocol) = protocol_from_env("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", endpoint)?;
120124

121125
let builder =
122126
MetricExporter::builder().with_temporality(opentelemetry_sdk::metrics::Temporality::Delta);
@@ -188,9 +192,6 @@ fn build_metadata_from_headers(
188192
Ok(tonic::metadata::MetadataMap::from_headers(header_map))
189193
}
190194

191-
// current default logfire protocol is to export over HTTP in binary format
192-
const DEFAULT_LOGFIRE_PROTOCOL: Protocol = Protocol::HttpBinary;
193-
194195
// standard OTLP protocol values in configuration
195196
const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc";
196197
const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf";
@@ -210,7 +211,12 @@ fn protocol_from_str(value: &str) -> Result<Protocol, ConfigureError> {
210211

211212
/// Get a protocol from the environment (or default value), returning a string describing the source
212213
/// plus the parsed protocol.
213-
fn protocol_from_env(data_env_var: &str) -> Result<(String, Protocol), ConfigureError> {
214+
///
215+
/// If the env var is not set, the default protocol is inferred from the endpoint.
216+
fn protocol_from_env(
217+
data_env_var: &str,
218+
endpoint: &str,
219+
) -> Result<(String, Protocol), ConfigureError> {
214220
// try both data-specific env var and general protocol
215221
[data_env_var, "OTEL_EXPORTER_OTLP_PROTOCOL"]
216222
.into_iter()
@@ -221,12 +227,20 @@ fn protocol_from_env(data_env_var: &str) -> Result<(String, Protocol), Configure
221227
})
222228
.transpose()?
223229
.map_or_else(
224-
|| {
225-
Ok((
226-
"the default logfire export protocol".to_string(),
227-
DEFAULT_LOGFIRE_PROTOCOL,
228-
))
229-
},
230+
|| protocol_from_endpoint(endpoint),
230231
|(var_name, value)| Ok((format!("`{var_name}={value}`"), protocol_from_str(&value)?)),
231232
)
232233
}
234+
235+
fn protocol_from_endpoint(endpoint: &str) -> Result<(String, Protocol), ConfigureError> {
236+
let protocol = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
237+
Protocol::HttpBinary
238+
} else if endpoint.starts_with("grpc://") {
239+
Protocol::Grpc
240+
} else {
241+
return Err(ConfigureError::Other(
242+
format!("unsupported scheme: {endpoint}").into(),
243+
));
244+
};
245+
Ok((format!("the inferred protocol from {endpoint}"), protocol))
246+
}

src/lib.rs

+23-13
Original file line numberDiff line numberDiff line change
@@ -506,21 +506,31 @@ impl LogfireConfigBuilder {
506506

507507
let mut http_headers: Option<HashMap<String, String>> = None;
508508

509+
let mut base_url = advanced_options.base_url;
510+
if base_url.is_none() {
511+
base_url = get_optional_env("LOGFIRE_BASE_URL", env)?;
512+
}
509513
let logfire_base_url = if send_to_logfire {
510-
let Some(token) = token else {
514+
if let Some(token) = token {
515+
http_headers
516+
.get_or_insert_default()
517+
.insert("Authorization".to_string(), format!("Bearer {token}"));
518+
519+
Some(
520+
base_url
521+
.as_deref()
522+
.unwrap_or_else(|| get_base_url_from_token(&token)),
523+
)
524+
} else if base_url
525+
.as_deref()
526+
.is_some_and(|url| url.starts_with("grpc://"))
527+
{
528+
// For gRPC endpoints, we allow there to be no token (this allows for the
529+
// possibility that logfire SDK is sending via an otel collector)
530+
base_url.as_deref()
531+
} else {
511532
return Err(ConfigureError::TokenRequired);
512-
};
513-
514-
http_headers
515-
.get_or_insert_default()
516-
.insert("Authorization".to_string(), format!("Bearer {token}"));
517-
518-
Some(
519-
advanced_options
520-
.base_url
521-
.as_deref()
522-
.unwrap_or_else(|| get_base_url_from_token(&token)),
523-
)
533+
}
524534
} else {
525535
None
526536
};

0 commit comments

Comments
 (0)