1use std::io;
2use std::io::Write;
3use std::time::UNIX_EPOCH;
4
5use crossbeam_channel::unbounded;
6use rand::random;
7
8use super::super::ImplContextBox;
9use super::super::Result;
10
11use super::super::FinishedSpan;
12use super::super::LogValue;
13use super::super::Span;
14use super::super::SpanContext;
15use super::super::SpanReceiver;
16use super::super::SpanReference;
17use super::super::SpanReferenceAware;
18use super::super::SpanSender;
19use super::super::StartOptions;
20use super::super::TagValue;
21
22use super::super::ExtractFormat;
23use super::super::InjectFormat;
24use super::super::Tracer;
25use super::super::TracerInterface;
26
27
28const BAGGAGE_KEY_PREFIX: &str = "Baggage-";
29const SPAN_ID_KEY: &str = "SpanID";
30const TRACE_ID_KEY: &str = "TraceID";
31
32
33pub struct FileTracer {
69 sender: SpanSender
70}
71
72impl TracerInterface for FileTracer {
73 fn extract(&self, fmt: ExtractFormat) -> Result<Option<SpanContext>> {
77 match fmt {
78 ExtractFormat::HttpHeaders(carrier) => {
79 let trace_id = carrier.get(TRACE_ID_KEY);
81 if trace_id.is_none() {
82 return Ok(None);
83 }
84 let trace_id = trace_id.unwrap().parse::<u64>()?;
85
86 let span_id = carrier.get(SPAN_ID_KEY);
87 if span_id.is_none() {
88 return Ok(None);
89 }
90 let span_id = span_id.unwrap().parse::<u64>()?;
91
92 let mut context = SpanContext::new(ImplContextBox::new(
94 FileTracerContext {
95 trace_id,
96 span_id
97 }
98 ));
99
100 for (key, value) in carrier.items() {
102 if key.starts_with(BAGGAGE_KEY_PREFIX) {
103 context.set_baggage_item(key.clone(), value.clone());
104 }
105 }
106 Ok(Some(context))
107 },
108 _ => panic!("Unsupported extraction format")
109 }
110 }
111
112 fn inject(&self, context: &SpanContext, fmt: InjectFormat) -> Result<()> {
116 let span_context = context;
117 let context = span_context.impl_context::<FileTracerContext>();
118 let context = context.expect(
119 "Unsupported span, was it created by FileTracer?"
120 );
121 match fmt {
122 InjectFormat::HttpHeaders(carrier) |
123 InjectFormat::TextMap(carrier) => {
124 carrier.set(TRACE_ID_KEY, &context.trace_id.to_string());
125 carrier.set(SPAN_ID_KEY, &context.span_id.to_string());
126 for (key, value) in span_context.baggage_items() {
127 let key = format!("{}{}", BAGGAGE_KEY_PREFIX, key);
128 carrier.set(&key, value);
129 }
130 Ok(())
131 },
132 _ => panic!("Unsupported injection format")
133 }
134 }
135
136 fn span(&self, name: &str, options: StartOptions) -> Span {
137 let trace_id = random::<u64>();
138 let span_id = random::<u64>();
139 let context = SpanContext::new(ImplContextBox::new(FileTracerContext {
140 trace_id,
141 span_id
142 }));
143 Span::new(name, context, options, self.sender.clone())
144 }
145}
146
147impl FileTracer {
148 pub fn new() -> (Tracer, SpanReceiver) {
150 let (sender, receiver) = unbounded();
151 let tracer = FileTracer { sender };
152 (Tracer::new(tracer), receiver)
153 }
154
155 pub fn write_trace<W: Write>(
159 span: FinishedSpan, file: &mut W
160 ) -> io::Result<()> {
161 let context = span.context().impl_context::<FileTracerContext>();
162 let context = context.expect(
163 "Unsupported span, was it created by FileTracer?"
164 );
165 let mut buffer = String::new();
166 buffer.push_str(&format!("==>> Trace ID: {}\n", context.trace_id));
167 buffer.push_str(&format!("===> Span ID: {}\n", context.span_id));
168
169 let finish = span.finish_time();
170 let start = span.start_time();
171 let duration = finish.duration_since(*start).unwrap();
172 let secs = duration.as_secs() as f64;
173 let delta = secs + duration.subsec_nanos() as f64 * 1e-9;
174 buffer.push_str(&format!("===> Span Duration: {}\n", delta));
175
176 buffer.push_str("===> References: [\n");
177 for reference in span.references() {
178 let ref_id = match reference {
179 &SpanReference::ChildOf(ref parent) |
180 &SpanReference::FollowsFrom(ref parent) => {
181 let context = parent.impl_context::<FileTracerContext>();
182 let context = context.expect(
183 "Unsupported span context, was it created by FileTracer?"
184 );
185 context.span_id
186 }
187 };
188 let ref_type = match reference {
189 SpanReference::ChildOf(_) => "Child of span ID",
190 SpanReference::FollowsFrom(_) => "Follows from span ID"
191 };
192 buffer.push_str(&format!("===> * {}: {}\n", ref_type, ref_id));
193 }
194 buffer.push_str("===> ]\n");
195
196 buffer.push_str("===> Baggage items: [\n");
197 for (key, value) in span.context().baggage_items() {
198 buffer.push_str(&format!("===> * {}: {}\n", key, value));
199 }
200 buffer.push_str("===> ]\n");
201
202 let mut tags: Vec<(&String, &TagValue)> = span.tags().iter().collect();
203 tags.sort_by_key(|&(k, _)| k);
204 buffer.push_str("===> Tags: [\n");
205 for (tag, value) in tags {
206 let value = match value {
207 TagValue::Boolean(v) => v.to_string(),
208 TagValue::Float(v) => v.to_string(),
209 TagValue::Integer(v) => v.to_string(),
210 TagValue::String(ref v) => v.clone(),
211 };
212 buffer.push_str(&format!("===> * {}: {}\n", tag, value));
213 }
214 buffer.push_str("===> ]\n");
215
216 buffer.push_str("===> Logs: [\n");
217 for log in span.logs().iter() {
218 let timestamp = log.timestamp().unwrap()
219 .duration_since(UNIX_EPOCH).unwrap()
220 .as_secs();
221 buffer.push_str(&format!("===> - {}:\n", timestamp));
222
223 let mut fields: Vec<(&String, &LogValue)> = log.iter().collect();
224 fields.sort_by_key(|&(k, _)| k);
225 for (key, value) in fields {
226 let value = match value {
227 LogValue::Boolean(v) => v.to_string(),
228 LogValue::Float(v) => v.to_string(),
229 LogValue::Integer(v) => v.to_string(),
230 LogValue::String(ref v) => v.clone(),
231 };
232 buffer.push_str(&format!("===> * {}: {}\n", key, value));
233 }
234 }
235 buffer.push_str("===> ]\n");
236 file.write_all(buffer.as_bytes())
237 }
238}
239
240
241#[derive(Clone, Debug)]
243struct FileTracerContext {
244 trace_id: u64,
245 span_id: u64
246}
247
248impl SpanReferenceAware for FileTracerContext {
249 fn reference_span(&mut self, reference: &SpanReference) {
250 match reference {
251 &SpanReference::ChildOf(ref parent) |
252 &SpanReference::FollowsFrom(ref parent) => {
253 let context = parent.impl_context::<FileTracerContext>();
254 let context = context.expect(
255 "Unsupported span context, was it created by FileTracer?"
256 );
257 self.trace_id = context.trace_id;
258 }
259 }
260 }
261}
262
263
264#[cfg(test)]
265mod tests {
266 use super::super::super::ImplContextBox;
267 use super::super::super::SpanContext;
268 use super::super::super::SpanReceiver;
269 use super::super::super::Tracer;
270
271 use super::FileTracer;
272 use super::FileTracerContext;
273
274 fn make_context(trace_id: u64, span_id: u64) -> SpanContext {
275 SpanContext::new(ImplContextBox::new(FileTracerContext {
276 trace_id,
277 span_id
278 }))
279 }
280
281 fn make_tracer() -> (Tracer, SpanReceiver) {
282 FileTracer::new()
283 }
284
285
286 mod span {
287 use std::time::UNIX_EPOCH;
288 use std::time::Duration;
289
290 use super::super::super::super::Log;
291
292 use super::super::FileTracer;
293 use super::super::FileTracerContext;
294 use super::make_context;
295 use super::make_tracer;
296
297
298 mod extract {
299 use std::collections::HashMap;
300 use std::io;
301
302 use super::super::super::super::super::Error;
303 use super::super::super::super::super::ExtractFormat;
304
305 use super::FileTracerContext;
306 use super::make_tracer;
307
308
309 mod invalid {
310 use std::collections::HashMap;
311
312 use super::Error;
313 use super::ExtractFormat;
314 use super::make_tracer;
315
316 #[test]
317 fn fails_if_invalid_span_id() {
318 let (tracer, _) = make_tracer();
319 let mut map: HashMap<String, String> = HashMap::new();
320 map.insert(String::from("TraceID"), String::from("123"));
321 map.insert(String::from("SpanID"), String::from("abc"));
322 let context = tracer.extract(
323 ExtractFormat::HttpHeaders(Box::new(&map))
324 );
325 match context {
326 Err(Error::ParseIntError(_)) => {},
327 Err(err) => panic!("Unexpected error: {:?}", err),
328 Ok(success) => panic!("Unexpected ok: {:?}", success)
329 }
330 }
331
332 #[test]
333 fn fails_if_invalid_trace_id() {
334 let (tracer, _) = make_tracer();
335 let mut map: HashMap<String, String> = HashMap::new();
336 map.insert(String::from("TraceID"), String::from("abc"));
337 let context = tracer.extract(
338 ExtractFormat::HttpHeaders(Box::new(&map))
339 );
340 match context {
341 Err(Error::ParseIntError(_)) => {},
342 Err(err) => panic!("Unexpected error: {:?}", err),
343 Ok(success) => panic!("Unexpected ok: {:?}", success)
344 }
345 }
346
347 #[test]
348 fn returns_none_without_trace_id() {
349 let (tracer, _) = make_tracer();
350 let map: HashMap<String, String> = HashMap::new();
351 let context = tracer.extract(
352 ExtractFormat::HttpHeaders(Box::new(&map))
353 );
354 match context {
355 Err(err) => panic!("Unexpected error: {:?}", err),
356 Ok(Some(success)) => panic!(
357 "Unexpected some: {:?}", success
358 ),
359 Ok(None) => {}
360 }
361 }
362
363 #[test]
364 fn returns_none_without_span_id() {
365 let (tracer, _) = make_tracer();
366 let mut map: HashMap<String, String> = HashMap::new();
367 map.insert(String::from("TraceID"), String::from("123"));
368 let context = tracer.extract(
369 ExtractFormat::HttpHeaders(Box::new(&map))
370 );
371 match context {
372 Err(err) => panic!("Unexpected error: {:?}", err),
373 Ok(Some(success)) => panic!(
374 "Unexpected some: {:?}", success
375 ),
376 Ok(None) => {}
377 }
378 }
379 }
380
381 #[test]
382 #[should_panic(expected = "Unsupported extraction format")]
383 fn binary_not_supported() {
384 let (tracer, _) = make_tracer();
385 let mut stdin = io::stdin();
386 tracer.extract(
387 ExtractFormat::Binary(Box::new(&mut stdin))
388 ).unwrap();
389 }
390
391 #[test]
392 fn http_headers() {
393 let (tracer, _) = make_tracer();
394 let mut map: HashMap<String, String> = HashMap::new();
395 map.insert(String::from("TraceID"), String::from("1234"));
396 map.insert(String::from("SpanID"), String::from("5678"));
397 map.insert(String::from("Baggage-Item1"), String::from("ab"));
398 map.insert(String::from("Baggage-Item2"), String::from("cd"));
399
400 let context = tracer.extract(
401 ExtractFormat::HttpHeaders(Box::new(&map))
402 ).unwrap().unwrap();
403 let inner = context.impl_context::<FileTracerContext>();
404 let inner = inner.unwrap();
405
406 assert_eq!(1234, inner.trace_id);
407 assert_eq!(5678, inner.span_id);
408 assert_eq!(
409 "ab",
410 context.get_baggage_item("Baggage-Item1").unwrap()
411 );
412 assert_eq!(
413 "cd",
414 context.get_baggage_item("Baggage-Item2").unwrap()
415 );
416 }
417 }
418
419
420 mod inject {
421 use std::collections::HashMap;
422 use std::io;
423
424 use super::super::super::super::super::InjectFormat;
425 use super::make_context;
426 use super::make_tracer;
427
428
429 #[test]
430 #[should_panic(expected = "Unsupported injection format")]
431 fn binary_not_supported() {
432 let (tracer, _) = make_tracer();
433 let context = make_context(1234, 1234);
434 let mut stdout = io::stdout();
435 tracer.inject(
436 &context,
437 InjectFormat::Binary(Box::new(&mut stdout))
438 ).unwrap();
439 }
440
441 #[test]
442 fn http_headers() {
443 let (tracer, _) = make_tracer();
444 let mut context = make_context(1234, 5678);
445 let mut map: HashMap<String, String> = HashMap::new();
446 context.set_baggage_item(String::from("Item1"), String::from("ab"));
447 context.set_baggage_item(String::from("Item2"), String::from("cd"));
448 tracer.inject(
449 &context,
450 InjectFormat::HttpHeaders(Box::new(&mut map))
451 ).unwrap();
452
453 assert_eq!("1234", map.get("TraceID").unwrap());
454 assert_eq!("5678", map.get("SpanID").unwrap());
455 assert_eq!("ab", map.get("Baggage-Item1").unwrap());
456 assert_eq!("cd", map.get("Baggage-Item2").unwrap());
457 }
458
459 #[test]
460 fn text_map() {
461 let (tracer, _) = make_tracer();
462 let mut context = make_context(1234, 5678);
463 let mut map: HashMap<String, String> = HashMap::new();
464 context.set_baggage_item(String::from("Item1"), String::from("ab"));
465 context.set_baggage_item(String::from("Item2"), String::from("cd"));
466 tracer.inject(
467 &context,
468 InjectFormat::TextMap(Box::new(&mut map))
469 ).unwrap();
470
471 assert_eq!("1234", map.get("TraceID").unwrap());
472 assert_eq!("5678", map.get("SpanID").unwrap());
473 assert_eq!("ab", map.get("Baggage-Item1").unwrap());
474 assert_eq!("cd", map.get("Baggage-Item2").unwrap());
475 }
476 }
477
478
479 #[test]
480 fn create() {
481 let (tracer, _) = make_tracer();
482 let span = tracer.span("test1");
483 let context = span.context().impl_context::<FileTracerContext>();
484 context.unwrap();
485 }
486
487 #[test]
488 fn write() {
489 let (tracer, receiver) = make_tracer();
490 let mut span = tracer.span("test1");
491 span.child_of(make_context(123456, 123));
492 span.follows(make_context(123456, 456));
493 span.set_baggage_item("TestKey", "Test Value");
494 span.tag("test.bool", true);
495 span.tag("test.float", 0.5);
496 span.tag("test.int", 5);
497 span.tag("test.string", "hello");
498
499 span.log(Log::new()
500 .log("bool", false)
501 .log("float", 0.66)
502 .at(UNIX_EPOCH + Duration::from_secs(123456))
503 );
504 span.log(Log::new()
505 .log("int", 66)
506 .log("string", "message")
507 .at(UNIX_EPOCH + Duration::from_secs(654321))
508 );
509 span.finish().unwrap();
510
511 let mut buffer = Vec::new();
512 let span = receiver.recv().unwrap();
513 FileTracer::write_trace::<Vec<u8>>(span, &mut buffer).unwrap();
514
515 let buffer = String::from_utf8(buffer).unwrap();
516 let mut buffer = buffer.split('\n');
517 assert_eq!(buffer.next().unwrap(), "==>> Trace ID: 123456");
518
519 let buffer: Vec<&str> = buffer.skip(2).collect();
520 assert_eq!(buffer, [
521 "===> References: [",
522 "===> * Child of span ID: 123",
523 "===> * Follows from span ID: 456",
524 "===> ]",
525 "===> Baggage items: [",
526 "===> * TestKey: Test Value",
527 "===> ]",
528 "===> Tags: [",
529 "===> * test.bool: true",
530 "===> * test.float: 0.5",
531 "===> * test.int: 5",
532 "===> * test.string: hello",
533 "===> ]",
534 "===> Logs: [",
535 "===> - 123456:",
536 "===> * bool: false",
537 "===> * float: 0.66",
538 "===> - 654321:",
539 "===> * int: 66",
540 "===> * string: message",
541 "===> ]",
542 ""
543 ]);
544 }
545 }
546}