Skip to content

Commit 6ff2b5a

Browse files
committed
feat(telemetry): Send logs to OTel collector directly using OTel libraries
Signed-off-by: Caleb Schoepp <[email protected]>
1 parent 9f1d9db commit 6ff2b5a

File tree

12 files changed

+208
-33
lines changed

12 files changed

+208
-33
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/telemetry/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ anyhow = { workspace = true }
99
http0 = { version = "0.2.9", package = "http" }
1010
http1 = { version = "1.0.0", package = "http" }
1111
opentelemetry = { version = "0.22.0", features = [ "metrics", "trace"] }
12-
opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio"] }
13-
opentelemetry-otlp = { version = "0.15.0", default-features=false, features = ["http-proto", "trace", "http", "reqwest-client", "metrics", "grpc-tonic"] }
12+
opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio", "logs_level_enabled"] }
13+
opentelemetry-otlp = { version = "0.15.0", default-features=false, features = ["http-proto", "trace", "http", "reqwest-client", "metrics", "grpc-tonic", "logs"] }
1414
opentelemetry-semantic-conventions = "0.14.0"
1515
tracing = { version = "0.1.37", features = ["log"] }
1616
tracing-appender = "0.2.2"

crates/telemetry/src/env.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use std::env::VarError;
22

33
use opentelemetry_otlp::{
4-
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL,
4+
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
5+
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL,
56
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
67
};
78

89
const OTEL_SDK_DISABLED: &str = "OTEL_SDK_DISABLED";
910
const OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL";
1011
const OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL";
12+
const OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL";
1113
const SPIN_DISABLE_LOG_TO_TRACING: &str = "SPIN_DISABLE_LOG_TO_TRACING";
1214

1315
/// Returns a boolean indicating if the OTEL tracing layer should be enabled.
@@ -38,6 +40,20 @@ pub(crate) fn otel_metrics_enabled() -> bool {
3840
]) && !otel_sdk_disabled()
3941
}
4042

43+
/// Returns a boolean indicating if the OTEL log layer should be enabled.
44+
///
45+
/// It is considered enabled if any of the following environment variables are set and not empty:
46+
/// - `OTEL_EXPORTER_OTLP_ENDPOINT`
47+
/// - `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`
48+
///
49+
/// Note that this is overridden if OTEL_SDK_DISABLED is set and not empty.
50+
pub(crate) fn otel_logs_enabled() -> bool {
51+
any_vars_set(&[
52+
OTEL_EXPORTER_OTLP_ENDPOINT,
53+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
54+
]) && !otel_sdk_disabled()
55+
}
56+
4157
/// Returns a boolean indicating if the compatibility layer that emits tracing events from
4258
/// applications logs should be disabled.
4359
///
@@ -84,6 +100,14 @@ impl OtlpProtocol {
84100
)
85101
}
86102

103+
/// Returns the protocol to be used for exporting logs as defined by the environment.
104+
pub(crate) fn logs_protocol_from_env() -> Self {
105+
Self::protocol_from_env(
106+
std::env::var(OTEL_EXPORTER_OTLP_LOGS_PROTOCOL),
107+
std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL),
108+
)
109+
}
110+
87111
fn protocol_from_env(
88112
specific_protocol: Result<String, VarError>,
89113
general_protocol: Result<String, VarError>,

crates/telemetry/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::io::IsTerminal;
22

3+
use env::otel_logs_enabled;
34
use env::otel_metrics_enabled;
45
use env::otel_tracing_enabled;
56
use opentelemetry_sdk::propagation::TraceContextPropagator;
67
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter, Layer};
78

89
pub mod detector;
910
mod env;
10-
pub mod log;
11+
pub mod logs;
1112
pub mod metrics;
1213
mod propagation;
1314
mod traces;
@@ -73,7 +74,7 @@ pub fn init(spin_version: String) -> anyhow::Result<ShutdownGuard> {
7374
};
7475

7576
let otel_metrics_layer = if otel_metrics_enabled() {
76-
Some(metrics::otel_metrics_layer(spin_version)?)
77+
Some(metrics::otel_metrics_layer(spin_version.clone())?)
7778
} else {
7879
None
7980
};
@@ -89,6 +90,10 @@ pub fn init(spin_version: String) -> anyhow::Result<ShutdownGuard> {
8990
// layer is disabled we still want to propagate trace context.
9091
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
9192

93+
if otel_logs_enabled() {
94+
logs::init_otel_logging_backend(spin_version)?;
95+
}
96+
9297
Ok(ShutdownGuard)
9398
}
9499

crates/telemetry/src/log.rs

Lines changed: 0 additions & 25 deletions
This file was deleted.

crates/telemetry/src/logs.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use std::{ascii::escape_default, sync::OnceLock, time::Duration};
2+
3+
use anyhow::bail;
4+
use opentelemetry::{
5+
global,
6+
logs::{Logger, LoggerProvider},
7+
};
8+
use opentelemetry_otlp::LogExporterBuilder;
9+
use opentelemetry_sdk::{
10+
logs::{BatchConfigBuilder, BatchLogProcessor},
11+
resource::{EnvResourceDetector, TelemetryResourceDetector},
12+
Resource,
13+
};
14+
15+
use crate::{
16+
detector::SpinResourceDetector,
17+
env::{self, otel_logs_enabled, OtlpProtocol},
18+
};
19+
20+
/// Handle an application log. Has the potential to both forward the log to OTel and to emit it as a
21+
/// tracing event.
22+
pub fn handle_app_log(buf: &[u8]) {
23+
app_log_to_otel(buf);
24+
app_log_to_tracing_event(buf);
25+
}
26+
27+
/// Forward the app log to OTel.
28+
fn app_log_to_otel(buf: &[u8]) {
29+
if !otel_logs_enabled() {
30+
return;
31+
}
32+
33+
let logger = global::logger_provider().logger("spin");
34+
if let Ok(s) = std::str::from_utf8(buf) {
35+
logger.emit(
36+
opentelemetry::logs::LogRecord::builder()
37+
.with_body(s.to_owned())
38+
.build(),
39+
);
40+
} else {
41+
logger.emit(
42+
opentelemetry::logs::LogRecord::builder()
43+
.with_body(escape_non_utf8_buf(buf))
44+
.with_attribute("app_log_non_utf8", true)
45+
.build(),
46+
);
47+
}
48+
}
49+
50+
/// Takes a Spin application log and emits it as a tracing event. This acts as a compatibility layer
51+
/// to easily get Spin app logs as events in our OTel traces.
52+
fn app_log_to_tracing_event(buf: &[u8]) {
53+
static CELL: OnceLock<bool> = OnceLock::new();
54+
if *CELL.get_or_init(env::spin_disable_log_to_tracing) {
55+
return;
56+
}
57+
58+
if let Ok(s) = std::str::from_utf8(buf) {
59+
tracing::info!(app_log = s);
60+
} else {
61+
tracing::info!(app_log_non_utf8 = escape_non_utf8_buf(buf));
62+
}
63+
}
64+
65+
fn escape_non_utf8_buf(buf: &[u8]) -> String {
66+
buf.iter()
67+
.take(50)
68+
.map(|&x| escape_default(x).to_string())
69+
.collect::<String>()
70+
}
71+
72+
/// Initialize the OTel logging backend.
73+
pub(crate) fn init_otel_logging_backend(spin_version: String) -> anyhow::Result<()> {
74+
let resource = Resource::from_detectors(
75+
Duration::from_secs(5),
76+
vec![
77+
// Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin
78+
// Set service.version from Spin metadata
79+
Box::new(SpinResourceDetector::new(spin_version)),
80+
// Sets fields from env OTEL_RESOURCE_ATTRIBUTES
81+
Box::new(EnvResourceDetector::new()),
82+
// Sets telemetry.sdk{name, language, version}
83+
Box::new(TelemetryResourceDetector),
84+
],
85+
);
86+
87+
// This will configure the exporter based on the OTEL_EXPORTER_* environment variables. We
88+
// currently default to using the HTTP exporter but in the future we could select off of the
89+
// combination of OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_EXPORTER_OTLP_LOGS_PROTOCOL to
90+
// determine whether we should use http/protobuf or grpc.
91+
let exporter_builder: LogExporterBuilder = match OtlpProtocol::logs_protocol_from_env() {
92+
OtlpProtocol::Grpc => opentelemetry_otlp::new_exporter().tonic().into(),
93+
OtlpProtocol::HttpProtobuf => opentelemetry_otlp::new_exporter().http().into(),
94+
OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
95+
};
96+
97+
let provider = opentelemetry_sdk::logs::LoggerProvider::builder()
98+
.with_config(opentelemetry_sdk::logs::config().with_resource(resource))
99+
.with_log_processor(
100+
BatchLogProcessor::builder(
101+
exporter_builder.build_log_exporter()?,
102+
opentelemetry_sdk::runtime::Tokio,
103+
)
104+
.with_batch_config(BatchConfigBuilder::default().build())
105+
.build(),
106+
)
107+
.build();
108+
109+
global::set_logger_provider(provider);
110+
111+
Ok(())
112+
}

crates/trigger/src/stdio.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl AsyncWrite for ComponentStdioWriter {
292292

293293
impl std::io::Write for ComponentStdioWriter {
294294
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
295-
spin_telemetry::log::app_log_to_tracing_event(buf);
295+
spin_telemetry::logs::handle_app_log(buf);
296296

297297
match &mut self.inner {
298298
ComponentStdioWriterInner::Inherit => {

examples/spin-timer/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hack/o11y-stack/docker-compose.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ services:
1616
- jaeger
1717
- prometheus
1818
- tempo
19+
- loki
1920

2021
jaeger:
2122
image: jaegertracing/all-in-one:latest
@@ -59,3 +60,12 @@ services:
5960
depends_on:
6061
- prometheus
6162
- tempo
63+
64+
loki:
65+
image: grafana/loki:2.9.8
66+
command:
67+
- '-config.file=/etc/loki/config.yaml'
68+
volumes:
69+
- ./loki.yaml:/etc/loki/config.yaml
70+
ports:
71+
- 3100:3100

hack/o11y-stack/grafana.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ datasources:
1313
access: proxy
1414
version: 1
1515
editable: false
16-
uid: tempo
16+
uid: tempo
17+
- name: Loki
18+
type: loki
19+
url: http://loki:3100
20+
access: proxy
21+
version: 1
22+
editable: false
23+
uid: loki
24+
basicAuth: false

0 commit comments

Comments
 (0)