opentelemetry_langfuse/
context.rs1use opentelemetry::KeyValue;
29use std::collections::HashMap;
30use std::sync::{Arc, RwLock};
31
32pub mod attributes {
37 pub const TRACE_SESSION_ID: &str = "langfuse.session.id";
39 pub const TRACE_USER_ID: &str = "langfuse.user.id";
41 pub const TRACE_TAGS: &str = "langfuse.trace.tags";
43 pub const TRACE_METADATA: &str = "langfuse.trace.metadata";
45 pub const TRACE_NAME: &str = "langfuse.trace.name";
47}
48
49#[derive(Clone)]
54pub struct LangfuseContext {
55 attributes: Arc<RwLock<HashMap<String, String>>>,
56}
57
58impl LangfuseContext {
59 #[must_use]
61 pub fn new() -> Self {
62 Self {
63 attributes: Arc::new(RwLock::new(HashMap::new())),
64 }
65 }
66
67 pub fn set_session_id(&self, session_id: impl Into<String>) -> &Self {
69 self.set_attribute(attributes::TRACE_SESSION_ID, session_id);
70 self
71 }
72
73 pub fn set_user_id(&self, user_id: impl Into<String>) -> &Self {
75 self.set_attribute(attributes::TRACE_USER_ID, user_id);
76 self
77 }
78
79 pub fn add_tags(&self, tags: Vec<String>) -> &Self {
83 let tags_json = serde_json::to_string(&tags).unwrap_or_else(|_| "[]".to_string());
84 self.set_attribute(attributes::TRACE_TAGS, tags_json);
85 self
86 }
87
88 pub fn add_tag(&self, tag: impl Into<String>) -> &Self {
90 let tag = tag.into();
91 let mut attrs = self.attributes.write().unwrap();
92
93 if let Some(existing) = attrs.get(attributes::TRACE_TAGS) {
95 if let Ok(mut tags_vec) = serde_json::from_str::<Vec<String>>(existing) {
97 tags_vec.push(tag);
98 let tags_json =
99 serde_json::to_string(&tags_vec).unwrap_or_else(|_| "[]".to_string());
100 attrs.insert(attributes::TRACE_TAGS.to_string(), tags_json);
101 } else {
102 attrs.insert(attributes::TRACE_TAGS.to_string(), format!("[\"{}\"]", tag));
104 }
105 } else {
106 attrs.insert(attributes::TRACE_TAGS.to_string(), format!("[\"{}\"]", tag));
107 }
108 drop(attrs);
109 self
110 }
111
112 pub fn set_metadata(&self, metadata: serde_json::Value) -> &Self {
114 let metadata_str = metadata.to_string();
115 self.set_attribute(attributes::TRACE_METADATA, metadata_str);
116 self
117 }
118
119 pub fn set_attribute(&self, key: impl Into<String>, value: impl Into<String>) -> &Self {
121 let mut attrs = self.attributes.write().unwrap();
122 attrs.insert(key.into(), value.into());
123 self
124 }
125
126 pub fn set_trace_name(&self, name: impl Into<String>) -> &Self {
128 self.set_attribute(attributes::TRACE_NAME, name);
129 self
130 }
131
132 pub fn clear(&self) {
134 let mut attrs = self.attributes.write().unwrap();
135 attrs.clear();
136 }
137
138 #[must_use]
140 pub fn get_attributes(&self) -> Vec<KeyValue> {
141 let attrs = self.attributes.read().unwrap();
142 attrs
143 .iter()
144 .map(|(k, v)| KeyValue::new(k.clone(), v.clone()))
145 .collect()
146 }
147
148 #[must_use]
150 pub fn has_attribute(&self, key: &str) -> bool {
151 let attrs = self.attributes.read().unwrap();
152 attrs.contains_key(key)
153 }
154
155 #[must_use]
157 pub fn get_attribute(&self, key: &str) -> Option<String> {
158 let attrs = self.attributes.read().unwrap();
159 attrs.get(key).cloned()
160 }
161}
162
163impl Default for LangfuseContext {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_context_attributes() {
175 let ctx = LangfuseContext::new();
176 ctx.set_session_id("session-123");
177 ctx.set_user_id("user-456");
178
179 assert_eq!(
180 ctx.get_attribute(attributes::TRACE_SESSION_ID),
181 Some("session-123".to_string())
182 );
183 assert_eq!(
184 ctx.get_attribute(attributes::TRACE_USER_ID),
185 Some("user-456".to_string())
186 );
187 }
188
189 #[test]
190 fn test_tags() {
191 let ctx = LangfuseContext::new();
192 ctx.add_tags(vec!["tag1".to_string(), "tag2".to_string()]);
193
194 let tags_json = ctx.get_attribute(attributes::TRACE_TAGS).unwrap();
195 let tags: Vec<String> = serde_json::from_str(&tags_json).unwrap();
196 assert_eq!(tags, vec!["tag1", "tag2"]);
197 }
198
199 #[test]
200 fn test_add_single_tag() {
201 let ctx = LangfuseContext::new();
202 ctx.add_tag("tag1");
203 ctx.add_tag("tag2");
204
205 let tags_json = ctx.get_attribute(attributes::TRACE_TAGS).unwrap();
206 let tags: Vec<String> = serde_json::from_str(&tags_json).unwrap();
207 assert_eq!(tags, vec!["tag1", "tag2"]);
208 }
209
210 #[test]
211 fn test_fluent_api() {
212 let ctx = LangfuseContext::new();
213 ctx.set_session_id("session-123")
214 .set_user_id("user-456")
215 .add_tags(vec!["tag1".to_string()])
216 .set_trace_name("my-trace");
217
218 assert!(ctx.has_attribute(attributes::TRACE_SESSION_ID));
219 assert!(ctx.has_attribute(attributes::TRACE_USER_ID));
220 assert!(ctx.has_attribute(attributes::TRACE_TAGS));
221 assert!(ctx.has_attribute(attributes::TRACE_NAME));
222 }
223
224 #[test]
225 fn test_clear() {
226 let ctx = LangfuseContext::new();
227 ctx.set_session_id("session-123");
228 assert!(ctx.has_attribute(attributes::TRACE_SESSION_ID));
229
230 ctx.clear();
231 assert!(!ctx.has_attribute(attributes::TRACE_SESSION_ID));
232 }
233}