1use chrono::{DateTime, Utc};
2use http::{Request, Response};
3use http_body_util::BodyExt;
4use hyper::body::Incoming;
5use lambda_runtime_api_client::body::Body;
6use serde::{Deserialize, Serialize};
7use std::{boxed::Box, fmt, sync::Arc};
8use tokio::sync::Mutex;
9use tower::Service;
10use tracing::{error, trace};
11
12#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
14pub struct LambdaTelemetry {
15 pub time: DateTime<Utc>,
17 #[serde(flatten)]
19 pub record: LambdaTelemetryRecord,
20}
21
22#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
24#[serde(tag = "type", content = "record", rename_all = "lowercase")]
25pub enum LambdaTelemetryRecord {
26 Function(String),
28
29 Extension(String),
31
32 #[serde(rename = "platform.initStart", rename_all = "camelCase")]
34 PlatformInitStart {
35 initialization_type: InitType,
37 phase: InitPhase,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 runtime_version: Option<String>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 runtime_version_arn: Option<String>,
45 },
46 #[serde(rename = "platform.initRuntimeDone", rename_all = "camelCase")]
48 PlatformInitRuntimeDone {
49 initialization_type: InitType,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 phase: Option<InitPhase>,
54 status: Status,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 error_type: Option<String>,
59 #[serde(default)]
61 spans: Vec<Span>,
62 },
63 #[serde(rename = "platform.initReport", rename_all = "camelCase")]
65 PlatformInitReport {
66 initialization_type: InitType,
68 phase: InitPhase,
70 metrics: InitReportMetrics,
72 #[serde(default)]
74 spans: Vec<Span>,
75 },
76 #[serde(rename = "platform.start", rename_all = "camelCase")]
78 PlatformStart {
79 request_id: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 version: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 tracing: Option<TraceContext>,
87 },
88 #[serde(rename = "platform.runtimeDone", rename_all = "camelCase")]
90 PlatformRuntimeDone {
91 request_id: String,
93 status: Status,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 error_type: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 metrics: Option<RuntimeDoneMetrics>,
101 #[serde(default)]
103 spans: Vec<Span>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 tracing: Option<TraceContext>,
107 },
108 #[serde(rename = "platform.report", rename_all = "camelCase")]
110 PlatformReport {
111 request_id: String,
113 status: Status,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 error_type: Option<String>,
118 metrics: ReportMetrics,
120 #[serde(default)]
122 spans: Vec<Span>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 tracing: Option<TraceContext>,
126 },
127
128 #[serde(rename = "platform.extension", rename_all = "camelCase")]
130 PlatformExtension {
131 name: String,
133 state: String,
135 events: Vec<String>,
137 },
138 #[serde(rename = "platform.telemetrySubscription", rename_all = "camelCase")]
140 PlatformTelemetrySubscription {
141 name: String,
143 state: String,
145 types: Vec<String>,
147 },
148 #[serde(rename = "platform.logsDropped", rename_all = "camelCase")]
150 PlatformLogsDropped {
151 reason: String,
153 dropped_records: u64,
155 dropped_bytes: u64,
157 },
158}
159
160#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
162#[serde(rename_all = "kebab-case")]
163pub enum InitType {
164 OnDemand,
166 ProvisionedConcurrency,
168 SnapStart,
170}
171
172#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
174#[serde(rename_all = "kebab-case")]
175pub enum InitPhase {
176 Init,
178 Invoke,
180}
181
182#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
184#[serde(rename_all = "kebab-case")]
185pub enum Status {
186 Success,
188 Error,
190 Failure,
192 Timeout,
194}
195
196#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
198#[serde(rename_all = "camelCase")]
199pub struct Span {
200 pub duration_ms: f64,
202 pub name: String,
204 pub start: DateTime<Utc>,
206}
207
208#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
210#[serde(rename_all = "camelCase")]
211pub struct TraceContext {
212 pub span_id: Option<String>,
214 pub r#type: TracingType,
216 pub value: String,
218}
219
220#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
222pub enum TracingType {
223 #[serde(rename = "X-Amzn-Trace-Id")]
225 AmznTraceId,
226}
227
228#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
230#[serde(rename_all = "camelCase")]
231pub struct InitReportMetrics {
232 pub duration_ms: f64,
234}
235
236#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
238#[serde(rename_all = "camelCase")]
239pub struct ReportMetrics {
240 pub duration_ms: f64,
242 pub billed_duration_ms: u64,
244 #[serde(rename = "memorySizeMB")]
246 pub memory_size_mb: u64,
247 #[serde(rename = "maxMemoryUsedMB")]
249 pub max_memory_used_mb: u64,
250 #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")]
252 pub init_duration_ms: Option<f64>,
253 #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")]
255 pub restore_duration_ms: Option<f64>,
256}
257
258#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
260#[serde(rename_all = "camelCase")]
261pub struct RuntimeDoneMetrics {
262 pub duration_ms: f64,
264 pub produced_bytes: Option<u64>,
266}
267
268pub(crate) async fn telemetry_wrapper<S>(
273 service: Arc<Mutex<S>>,
274 req: Request<Incoming>,
275) -> Result<Response<Body>, Box<dyn std::error::Error + Send + Sync>>
276where
277 S: Service<Vec<LambdaTelemetry>, Response = ()>,
278 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
279 S::Future: Send,
280{
281 trace!("Received telemetry request");
282 let body = match req.into_body().collect().await {
284 Ok(body) => body,
285 Err(e) => {
286 error!("Error reading telemetry request body: {}", e);
287 return Ok(hyper::Response::builder()
288 .status(hyper::StatusCode::BAD_REQUEST)
289 .body(Body::empty())
290 .unwrap());
291 }
292 };
293
294 let telemetry: Vec<LambdaTelemetry> = match serde_json::from_slice(&body.to_bytes()) {
295 Ok(telemetry) => telemetry,
296 Err(e) => {
297 error!("Error parsing telemetry: {}", e);
298 return Ok(hyper::Response::builder()
299 .status(hyper::StatusCode::BAD_REQUEST)
300 .body(Body::empty())
301 .unwrap());
302 }
303 };
304
305 {
306 let mut service = service.lock().await;
307 match service.call(telemetry).await {
308 Ok(_) => (),
309 Err(err) => println!("{err:?}"),
310 }
311 }
312
313 Ok(hyper::Response::new(Body::empty()))
314}
315
316#[cfg(test)]
317mod deserialization_tests {
318 use super::*;
319 use chrono::{TimeDelta, TimeZone};
320
321 macro_rules! deserialize_tests {
322 ($($name:ident: $value:expr,)*) => {
323 $(
324 #[test]
325 fn $name() {
326 let (input, expected) = $value;
327 let actual = serde_json::from_str::<LambdaTelemetry>(&input).expect("unable to deserialize");
328
329 assert!(actual.record == expected);
330 }
331 )*
332 }
333 }
334
335 deserialize_tests! {
336 function: (
338 r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#,
339 LambdaTelemetryRecord::Function("hello world".to_string()),
340 ),
341
342 extension: (
344 r#"{"time": "2020-08-20T12:31:32.123Z","type": "extension", "record": "hello world"}"#,
345 LambdaTelemetryRecord::Extension("hello world".to_string()),
346 ),
347
348 platform_start: (
350 r#"{"time":"2022-10-21T14:05:03.165Z","type":"platform.start","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","version":"$LATEST","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#,
351 LambdaTelemetryRecord::PlatformStart {
352 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
353 version: Some("$LATEST".to_string()),
354 tracing: Some(TraceContext{
355 span_id: Some("24cd7d670fa455f0".to_string()),
356 r#type: TracingType::AmznTraceId,
357 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
358 }),
359 },
360 ),
361 platform_init_start: (
363 r#"{"time":"2022-10-19T13:52:15.636Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#,
364 LambdaTelemetryRecord::PlatformInitStart {
365 initialization_type: InitType::OnDemand,
366 phase: InitPhase::Init,
367 runtime_version: None,
368 runtime_version_arn: None,
369 },
370 ),
371 platform_runtime_done: (
373 r#"{"time":"2022-10-21T14:05:05.764Z","type":"platform.runtimeDone","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"},"spans":[{"name":"responseLatency","start":"2022-10-21T14:05:03.165Z","durationMs":2598.0},{"name":"responseDuration","start":"2022-10-21T14:05:05.763Z","durationMs":0.0}],"metrics":{"durationMs":2599.0,"producedBytes":8}}}"#,
374 LambdaTelemetryRecord::PlatformRuntimeDone {
375 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
376 status: Status::Success,
377 error_type: None,
378 metrics: Some(RuntimeDoneMetrics {
379 duration_ms: 2599.0,
380 produced_bytes: Some(8),
381 }),
382 spans: vec!(
383 Span {
384 name:"responseLatency".to_string(),
385 start: Utc
386 .with_ymd_and_hms(2022, 10, 21, 14, 5, 3)
387 .unwrap()
388 .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap())
389 .unwrap(),
390 duration_ms: 2598.0
391 },
392 Span {
393 name:"responseDuration".to_string(),
394 start: Utc
395 .with_ymd_and_hms(2022, 10, 21, 14, 5, 5)
396 .unwrap()
397 .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap())
398 .unwrap(),
399 duration_ms: 0.0
400 },
401 ),
402 tracing: Some(TraceContext{
403 span_id: Some("24cd7d670fa455f0".to_string()),
404 r#type: TracingType::AmznTraceId,
405 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
406 }),
407 },
408 ),
409 platform_report: (
411 r#"{"time":"2022-10-21T14:05:05.766Z","type":"platform.report","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","metrics":{"durationMs":2599.4,"billedDurationMs":2600,"memorySizeMB":128,"maxMemoryUsedMB":94,"initDurationMs":549.04},"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"},"status":"success"}}"#,
412 LambdaTelemetryRecord::PlatformReport {
413 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
414 status: Status::Success,
415 error_type: None,
416 metrics: ReportMetrics {
417 duration_ms: 2599.4,
418 billed_duration_ms: 2600,
419 memory_size_mb:128,
420 max_memory_used_mb:94,
421 init_duration_ms: Some(549.04),
422 restore_duration_ms: None,
423 },
424 spans: Vec::new(),
425 tracing: Some(TraceContext {
426 span_id: Some("24cd7d670fa455f0".to_string()),
427 r#type: TracingType::AmznTraceId,
428 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
429 }),
430 },
431 ),
432 platform_telemetry_subscription: (
434 r#"{"time":"2022-10-19T13:52:15.667Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#,
435 LambdaTelemetryRecord::PlatformTelemetrySubscription {
436 name: "my-extension".to_string(),
437 state: "Subscribed".to_string(),
438 types: vec!("platform".to_string(), "function".to_string()),
439 },
440 ),
441 platform_init_runtime_done: (
443 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success"}}"#,
444 LambdaTelemetryRecord::PlatformInitRuntimeDone {
445 initialization_type: InitType::OnDemand,
446 status: Status::Success,
447 phase: None,
448 error_type: None,
449 spans: Vec::new(),
450 },
451 ),
452 platform_extension: (
454 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#,
455 LambdaTelemetryRecord::PlatformExtension {
456 name: "my-extension".to_string(),
457 state: "Ready".to_string(),
458 events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()),
459 },
460 ),
461 platform_init_report: (
463 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initReport","record":{"initializationType":"on-demand","metrics":{"durationMs":500.0},"phase":"init"}}"#,
464 LambdaTelemetryRecord::PlatformInitReport {
465 initialization_type: InitType::OnDemand,
466 phase: InitPhase::Init,
467 metrics: InitReportMetrics { duration_ms: 500.0 },
468 spans: Vec::new(),
469 }
470 ),
471 }
472}
473
474#[cfg(test)]
475mod serialization_tests {
476 use chrono::{TimeDelta, TimeZone};
477
478 use super::*;
479 macro_rules! serialize_tests {
480 ($($name:ident: $value:expr,)*) => {
481 $(
482 #[test]
483 fn $name() {
484 let (input, expected) = $value;
485 let actual = serde_json::to_string(&input).expect("unable to serialize");
486 println!("Input: {:?}\n", input);
487 println!("Expected:\n {:?}\n", expected);
488 println!("Actual:\n {:?}\n", actual);
489
490 assert!(actual == expected);
491 }
492 )*
493 }
494 }
495
496 serialize_tests! {
497 function: (
499 LambdaTelemetry {
500 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
501 record: LambdaTelemetryRecord::Function("hello world".to_string()),
502 },
503 r#"{"time":"2023-11-28T12:00:09Z","type":"function","record":"hello world"}"#,
504 ),
505 extension: (
507 LambdaTelemetry {
508 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
509 record: LambdaTelemetryRecord::Extension("hello world".to_string()),
510 },
511 r#"{"time":"2023-11-28T12:00:09Z","type":"extension","record":"hello world"}"#,
512 ),
513 platform_start: (
515 LambdaTelemetry{
516 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
517 record: LambdaTelemetryRecord::PlatformStart {
518 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
519 version: Some("$LATEST".to_string()),
520 tracing: Some(TraceContext{
521 span_id: Some("24cd7d670fa455f0".to_string()),
522 r#type: TracingType::AmznTraceId,
523 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
524 }),
525 }
526 },
527 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.start","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","version":"$LATEST","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#,
528 ),
529 platform_init_start: (
531 LambdaTelemetry{
532 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
533 record: LambdaTelemetryRecord::PlatformInitStart {
534 initialization_type: InitType::OnDemand,
535 phase: InitPhase::Init,
536 runtime_version: None,
537 runtime_version_arn: None,
538 },
539 },
540 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#,
541 ),
542 platform_runtime_done: (
544 LambdaTelemetry{
545 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
546 record: LambdaTelemetryRecord::PlatformRuntimeDone {
547 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
548 status: Status::Success,
549 error_type: None,
550 metrics: Some(RuntimeDoneMetrics {
551 duration_ms: 2599.0,
552 produced_bytes: Some(8),
553 }),
554 spans: vec!(
555 Span {
556 name:"responseLatency".to_string(),
557 start: Utc
558 .with_ymd_and_hms(2022, 10, 21, 14, 5, 3)
559 .unwrap()
560 .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap())
561 .unwrap(),
562 duration_ms: 2598.0
563 },
564 Span {
565 name:"responseDuration".to_string(),
566 start: Utc
567 .with_ymd_and_hms(2022, 10, 21, 14, 5, 5)
568 .unwrap()
569 .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap())
570 .unwrap(),
571 duration_ms: 0.0
572 },
573 ),
574 tracing: Some(TraceContext{
575 span_id: Some("24cd7d670fa455f0".to_string()),
576 r#type: TracingType::AmznTraceId,
577 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
578 }),
579 },
580 },
581 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.runtimeDone","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.0,"producedBytes":8},"spans":[{"durationMs":2598.0,"name":"responseLatency","start":"2022-10-21T14:05:03.165Z"},{"durationMs":0.0,"name":"responseDuration","start":"2022-10-21T14:05:05.763Z"}],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#,
582 ),
583 platform_report: (
585 LambdaTelemetry{
586 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
587 record: LambdaTelemetryRecord::PlatformReport {
588 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
589 status: Status::Success,
590 error_type: None,
591 metrics: ReportMetrics {
592 duration_ms: 2599.4,
593 billed_duration_ms: 2600,
594 memory_size_mb:128,
595 max_memory_used_mb:94,
596 init_duration_ms: Some(549.04),
597 restore_duration_ms: None,
598 },
599 spans: Vec::new(),
600 tracing: Some(TraceContext {
601 span_id: Some("24cd7d670fa455f0".to_string()),
602 r#type: TracingType::AmznTraceId,
603 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
604 }),
605 },
606 },
607 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.report","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.4,"billedDurationMs":2600,"memorySizeMB":128,"maxMemoryUsedMB":94,"initDurationMs":549.04},"spans":[],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#,
608 ),
609 platform_telemetry_subscription: (
611 LambdaTelemetry{
612 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
613 record: LambdaTelemetryRecord::PlatformTelemetrySubscription {
614 name: "my-extension".to_string(),
615 state: "Subscribed".to_string(),
616 types: vec!("platform".to_string(), "function".to_string()),
617 },
618 },
619 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#,
620 ),
621 platform_init_runtime_done: (
623 LambdaTelemetry{
624 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
625 record: LambdaTelemetryRecord::PlatformInitRuntimeDone {
626 initialization_type: InitType::OnDemand,
627 status: Status::Success,
628 phase: None,
629 error_type: None,
630 spans: Vec::new(),
631 },
632 },
633 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success","spans":[]}}"#,
634 ),
635 platform_extension: (
637 LambdaTelemetry {
638 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
639 record: LambdaTelemetryRecord::PlatformExtension {
640 name: "my-extension".to_string(),
641 state: "Ready".to_string(),
642 events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()),
643 },
644 },
645 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#,
646 ),
647 platform_init_report: (
649 LambdaTelemetry {
650 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
651 record: LambdaTelemetryRecord::PlatformInitReport {
652 initialization_type: InitType::OnDemand,
653 phase: InitPhase::Init,
654 metrics: InitReportMetrics { duration_ms: 500.0 },
655 spans: Vec::new(),
656 },
657 },
658 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initReport","record":{"initializationType":"on-demand","phase":"init","metrics":{"durationMs":500.0},"spans":[]}}"#,
659 ),
660
661 }
662}