Skip to content

Commit 185d2a6

Browse files
committed
feat(logs): saving work (this is not even close to ready)
Signed-off-by: Andrew Steurer <[email protected]>
1 parent 927138a commit 185d2a6

File tree

14 files changed

+694
-28
lines changed

14 files changed

+694
-28
lines changed

rust/Cargo.lock

Lines changed: 261 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/examples/spin-logs/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
.spin/

rust/examples/spin-logs/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "spin-logs"
3+
authors = ["Andrew Steurer <[email protected]>"]
4+
description = ""
5+
version = "0.1.0"
6+
rust-version = "1.78"
7+
edition = "2021"
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[dependencies]
13+
anyhow = { workspace = true }
14+
opentelemetry = { workspace = true }
15+
opentelemetry_sdk = { workspace = true, features = ["logs", "rt-tokio"] }
16+
opentelemetry-wasi = { path = "../../" }
17+
spin-sdk = { workspace = true }
18+
opentelemetry-stdout = { version = "0.28.0", features = ["logs"] }
19+
tracing = "0.1.41"
20+
opentelemetry-appender-tracing = "0.28.1"
21+
tracing-subscriber = "0.3.19"

rust/examples/spin-logs/spin.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
spin_manifest_version = 2
2+
3+
[application]
4+
name = "spin-logs"
5+
version = "0.1.0"
6+
authors = ["Andrew Steurer <[email protected]>"]
7+
description = "A Spin application demonstrating the use of OpenTelemetry logs"
8+
9+
[[trigger.http]]
10+
route = "/..."
11+
component = "spin-logs"
12+
13+
[component.spin-logs]
14+
source = "../../target/wasm32-wasip2/release/spin_metrics.wasm"
15+
allowed_outbound_hosts = []
16+
[component.spin-logs.build]
17+
command = "cargo build --target wasm32-wasip2 --release"
18+
watch = ["src/**/*.rs", "Cargo.toml"]

rust/examples/spin-logs/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use opentelemetry_appender_tracing::layer;
2+
use opentelemetry_sdk::Resource;
3+
use spin_sdk::http::{IntoResponse, Request, Response};
4+
use spin_sdk::http_component;
5+
use tracing_subscriber::prelude::*;
6+
7+
#[http_component]
8+
fn handle_spin_logs(_req: Request) -> anyhow::Result<impl IntoResponse> {
9+
let processor = opentelemetry_wasi::logs::WasiLogProcessor::new();
10+
let provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder()
11+
.with_resource(Resource::builder().with_service_name("spin-logs").build())
12+
.with_log_processor(processor)
13+
.build();
14+
15+
let layer = layer::OpenTelemetryTracingBridge::new(&provider);
16+
tracing_subscriber::registry().with(layer).init();
17+
18+
tracing::info!(message = "Hello, world!");
19+
20+
let _ = provider.shutdown();
21+
Ok(Response::builder()
22+
.status(200)
23+
.header("content-type", "text/plain")
24+
.body("Hello World!")
25+
.build())
26+
}

rust/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
mod logs;
12
mod tracing;
3+
mod types;
24

5+
pub use logs::*;
36
pub use tracing::*;
47

58
#[doc(hidden)]

rust/src/logs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod conversion;
2+
mod processor;
3+
4+
pub use processor::WasiLogProcessor;

rust/src/logs/conversion.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use core::panic;
2+
use std::collections::HashMap;
3+
4+
use crate::wit::wasi::otel::logs::*;
5+
6+
impl From<&mut opentelemetry_sdk::logs::SdkLogRecord> for LogRecord {
7+
fn from(value: &mut opentelemetry_sdk::logs::SdkLogRecord) -> Self {
8+
Self {
9+
event_name: match value.event_name() {
10+
Some(v) => Some(v.to_string()),
11+
None => None,
12+
},
13+
timestamp: match value.timestamp() {
14+
Some(v) => Some(v.into()),
15+
None => None,
16+
},
17+
observed_timestamp: match value.observed_timestamp() {
18+
Some(v) => Some(v.into()),
19+
None => None,
20+
},
21+
severity: match value.severity_number() {
22+
Some(v) => Some(v.into()),
23+
None => None,
24+
},
25+
severity_text: match value.severity_text() {
26+
Some(v) => Some(v.to_string()),
27+
None => None,
28+
},
29+
body: match value.body() {
30+
Some(v) => Some(v.into()),
31+
None => None,
32+
},
33+
attributes: value.attributes_iter().map(Into::into).collect(),
34+
}
35+
}
36+
}
37+
38+
impl From<opentelemetry::logs::Severity> for Severity {
39+
fn from(value: opentelemetry::logs::Severity) -> Self {
40+
use opentelemetry::logs::Severity as S;
41+
match value {
42+
S::Trace => Severity::Trace,
43+
S::Trace2 => Severity::Trace2,
44+
S::Trace3 => Severity::Trace3,
45+
S::Trace4 => Severity::Trace4,
46+
S::Debug => Severity::Debug,
47+
S::Debug2 => Severity::Debug2,
48+
S::Debug3 => Severity::Debug3,
49+
S::Debug4 => Severity::Debug4,
50+
S::Info => Severity::Info,
51+
S::Info2 => Severity::Info2,
52+
S::Info3 => Severity::Info3,
53+
S::Info4 => Severity::Info4,
54+
S::Warn => Severity::Warn,
55+
S::Warn2 => Severity::Warn2,
56+
S::Warn3 => Severity::Warn3,
57+
S::Warn4 => Severity::Warn4,
58+
S::Error => Severity::Error,
59+
S::Error2 => Severity::Error2,
60+
S::Error3 => Severity::Error3,
61+
S::Error4 => Severity::Error4,
62+
S::Fatal => Severity::Fatal,
63+
S::Fatal2 => Severity::Fatal2,
64+
S::Fatal3 => Severity::Fatal3,
65+
S::Fatal4 => Severity::Fatal4,
66+
}
67+
}
68+
}
69+
70+
impl From<opentelemetry::logs::AnyValue> for LogAny {
71+
fn from(value: opentelemetry::logs::AnyValue) -> Self {
72+
use opentelemetry::logs::AnyValue as A;
73+
match value {
74+
A::Int(v) => LogAny::Value(v.into()),
75+
A::Double(v) => LogAny::Value(v.into()),
76+
A::String(v) => LogAny::Value(v.into()),
77+
A::Boolean(v) => LogAny::Value(v.into()),
78+
A::Bytes(v) => LogAny::List(v.into()),
79+
A::ListAny(v) => LogAny::List(v.into()),
80+
A::Map(v) => LogAny::Map(map_to_kv_list(v)),
81+
_ => panic!("unsupported data type"),
82+
}
83+
}
84+
}
85+
86+
impl From<&opentelemetry::logs::AnyValue> for LogAny {
87+
fn from(value: &opentelemetry::logs::AnyValue) -> Self {
88+
use opentelemetry::logs::AnyValue as A;
89+
match value {
90+
A::Int(v) => LogAny::Value(v.to_owned().into()),
91+
A::Double(v) => LogAny::Value(v.to_owned().into()),
92+
A::String(v) => LogAny::Value(v.to_owned().into()),
93+
A::Boolean(v) => LogAny::Value(v.to_owned().into()),
94+
A::Bytes(v) => LogAny::List(v.to_owned().into()),
95+
A::ListAny(v) => LogAny::List(v.to_owned().into()),
96+
A::Map(v) => LogAny::Map(map_to_kv_list(v.to_owned())),
97+
_ => panic!("unsupported data type"),
98+
}
99+
}
100+
}
101+
102+
impl From<i64> for LogValue {
103+
fn from(value: i64) -> Self {
104+
Self::Int(value)
105+
}
106+
}
107+
108+
impl From<f64> for LogValue {
109+
fn from(value: f64) -> Self {
110+
Self::Double(value)
111+
}
112+
}
113+
114+
impl From<opentelemetry::StringValue> for LogValue {
115+
fn from(value: opentelemetry::StringValue) -> Self {
116+
Self::String(value.to_string())
117+
}
118+
}
119+
120+
impl From<bool> for LogValue {
121+
fn from(value: bool) -> Self {
122+
Self::Boolean(value)
123+
}
124+
}
125+
126+
impl From<Box<Vec<u8>>> for LogList {
127+
fn from(value: Box<Vec<u8>>) -> Self {
128+
Self::Bytes(*value)
129+
}
130+
}
131+
132+
impl From<Box<Vec<opentelemetry::logs::AnyValue>>> for LogList {
133+
fn from(value: Box<Vec<opentelemetry::logs::AnyValue>>) -> Self {
134+
LogList::List(value.into_iter().map(Into::into).collect())
135+
}
136+
}
137+
138+
impl From<opentelemetry::logs::AnyValue> for LogValue {
139+
fn from(value: opentelemetry::logs::AnyValue) -> Self {
140+
use opentelemetry::logs::AnyValue as A;
141+
match value {
142+
A::Int(v) => LogValue::Int(v),
143+
A::Double(v) => LogValue::Double(v),
144+
A::String(v) => LogValue::String(v.to_string()),
145+
A::Boolean(v) => LogValue::Boolean(v),
146+
_ => panic!("unsupported data type"),
147+
}
148+
}
149+
}
150+
151+
impl From<&(opentelemetry::Key, opentelemetry::logs::AnyValue)> for LogRecordAttribute {
152+
fn from(value: &(opentelemetry::Key, opentelemetry::logs::AnyValue)) -> Self {
153+
Self {
154+
key: value.0.to_string(),
155+
value: value.1.to_owned().into(),
156+
}
157+
}
158+
}
159+
160+
fn map_to_kv_list(
161+
map: Box<HashMap<opentelemetry::Key, opentelemetry::logs::AnyValue>>,
162+
) -> Vec<LogMapKeyValue> {
163+
use opentelemetry::logs::AnyValue as A;
164+
map.into_iter()
165+
.map(|(k, val)| {
166+
let value = match val {
167+
A::Int(v) => LogMapValue::Value(v.into()),
168+
A::Double(v) => LogMapValue::Value(v.into()),
169+
A::String(v) => LogMapValue::Value(v.into()),
170+
A::Boolean(v) => LogMapValue::Value(v.into()),
171+
A::Bytes(l) => LogMapValue::List(l.into()),
172+
A::ListAny(l) => LogMapValue::List(l.into()),
173+
_ => panic!("unsupported data type"),
174+
};
175+
176+
return LogMapKeyValue {
177+
key: k.to_string(),
178+
value,
179+
};
180+
})
181+
.collect()
182+
}

rust/src/logs/processor.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::sync::atomic::{AtomicBool, Ordering};
2+
3+
use opentelemetry_sdk::error::OTelSdkResult;
4+
5+
use crate::wit::wasi::otel::logs::emit;
6+
7+
#[derive(Debug)]
8+
pub struct WasiLogProcessor {
9+
is_shutdown: AtomicBool,
10+
}
11+
12+
impl WasiLogProcessor {
13+
pub fn new() -> Self {
14+
Self {
15+
is_shutdown: AtomicBool::new(false),
16+
}
17+
}
18+
}
19+
20+
impl Default for WasiLogProcessor {
21+
fn default() -> Self {
22+
Self::new()
23+
}
24+
}
25+
26+
impl opentelemetry_sdk::logs::LogProcessor for WasiLogProcessor {
27+
fn emit(
28+
&self,
29+
data: &mut opentelemetry_sdk::logs::SdkLogRecord,
30+
_: &opentelemetry::InstrumentationScope,
31+
) {
32+
match emit(&data.into()) {
33+
Ok(_) => (),
34+
Err(v) => println!("ERROR: opentelemetry_wasi failed to emit log: {}", v),
35+
}
36+
}
37+
38+
fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult {
39+
if self.is_shutdown.load(Ordering::Relaxed) {
40+
return OTelSdkResult::Err(opentelemetry_sdk::error::OTelSdkError::AlreadyShutdown);
41+
}
42+
Ok(())
43+
}
44+
45+
fn shutdown(&self) -> opentelemetry_sdk::error::OTelSdkResult {
46+
let result = self.force_flush();
47+
if self.is_shutdown.swap(true, Ordering::Relaxed) {
48+
return OTelSdkResult::Err(opentelemetry_sdk::error::OTelSdkError::AlreadyShutdown);
49+
}
50+
result
51+
}
52+
}

rust/src/tracing/conversion.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::wit::wasi::otel::tracing::*;
22
use crate::wit::wasi::otel::types::{KeyValue, Value};
3-
use std::time::UNIX_EPOCH;
43

54
impl From<opentelemetry_sdk::trace::SpanData> for SpanData {
65
fn from(value: opentelemetry_sdk::trace::SpanData) -> Self {
@@ -92,18 +91,6 @@ impl From<opentelemetry::trace::SpanKind> for SpanKind {
9291
}
9392
}
9493

95-
impl From<std::time::SystemTime> for Datetime {
96-
fn from(value: std::time::SystemTime) -> Self {
97-
let duration_since_epoch = value
98-
.duration_since(UNIX_EPOCH)
99-
.expect("SystemTime should be after UNIX EPOCH");
100-
Self {
101-
seconds: duration_since_epoch.as_secs(),
102-
nanoseconds: duration_since_epoch.subsec_nanos(),
103-
}
104-
}
105-
}
106-
10794
impl From<opentelemetry::KeyValue> for KeyValue {
10895
fn from(value: opentelemetry::KeyValue) -> Self {
10996
Self {

0 commit comments

Comments
 (0)