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::{de::DeserializeOwned, 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<L = String> {
15 pub time: DateTime<Utc>,
17 #[serde(flatten)]
19 pub record: LambdaTelemetryRecord<L>,
20}
21
22#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
24#[serde(tag = "type", content = "record", rename_all = "lowercase")]
25pub enum LambdaTelemetryRecord<L = String> {
26 Function(L),
28
29 Extension(L),
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, L>(
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<L>>, Response = ()>,
278 S::Error: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
279 S::Future: Send,
280 L: DeserializeOwned,
281{
282 trace!("Received telemetry request");
283 let body = match req.into_body().collect().await {
285 Ok(body) => body,
286 Err(e) => {
287 error!("Error reading telemetry request body: {}", e);
288 return Ok(hyper::Response::builder()
289 .status(hyper::StatusCode::BAD_REQUEST)
290 .body(Body::empty())
291 .unwrap());
292 }
293 };
294
295 let telemetry: Vec<LambdaTelemetry<L>> = match serde_json::from_slice(&body.to_bytes()) {
296 Ok(telemetry) => telemetry,
297 Err(e) => {
298 error!("Error parsing telemetry: {}", e);
299 return Ok(hyper::Response::builder()
300 .status(hyper::StatusCode::BAD_REQUEST)
301 .body(Body::empty())
302 .unwrap());
303 }
304 };
305
306 {
307 let mut service = service.lock().await;
308 match service.call(telemetry).await {
309 Ok(_) => (),
310 Err(err) => println!("{err:?}"),
311 }
312 }
313
314 Ok(hyper::Response::new(Body::empty()))
315}
316
317#[cfg(test)]
318mod deserialization_tests {
319 use super::*;
320 use chrono::{TimeDelta, TimeZone};
321
322 macro_rules! deserialize_tests {
323 ($($name:ident$(<$log:ty>)?: $value:expr,)*) => {
324 $(
325 #[test]
326 fn $name() {
327 let (input, expected) = $value;
328 let actual = serde_json::from_str::<LambdaTelemetry$(<$log>)?>(&input).expect("unable to deserialize");
329
330 assert!(actual.record == expected);
331 }
332 )*
333 };
334 }
335
336 deserialize_tests! {
337 function: (
339 r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": "hello world"}"#,
340 LambdaTelemetryRecord::Function("hello world".to_string()),
341 ),
342
343 function_generic<bool>: (
345 r#"{"time": "2020-08-20T12:31:32.123Z","type": "function", "record": true}"#,
346 LambdaTelemetryRecord::Function(true),
347 ),
348
349 extension: (
351 r#"{"time": "2020-08-20T12:31:32.123Z","type": "extension", "record": "hello world"}"#,
352 LambdaTelemetryRecord::Extension("hello world".to_string()),
353 ),
354
355 extension_generic<bool>: (
357 r#"{"time": "2020-08-20T12:31:32.123Z","type": "extension", "record": true}"#,
358 LambdaTelemetryRecord::Extension(true),
359 ),
360
361 platform_start: (
363 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"}}}"#,
364 LambdaTelemetryRecord::PlatformStart {
365 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
366 version: Some("$LATEST".to_string()),
367 tracing: Some(TraceContext{
368 span_id: Some("24cd7d670fa455f0".to_string()),
369 r#type: TracingType::AmznTraceId,
370 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
371 }),
372 },
373 ),
374 platform_init_start: (
376 r#"{"time":"2022-10-19T13:52:15.636Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#,
377 LambdaTelemetryRecord::PlatformInitStart {
378 initialization_type: InitType::OnDemand,
379 phase: InitPhase::Init,
380 runtime_version: None,
381 runtime_version_arn: None,
382 },
383 ),
384 platform_runtime_done: (
386 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}}}"#,
387 LambdaTelemetryRecord::PlatformRuntimeDone {
388 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
389 status: Status::Success,
390 error_type: None,
391 metrics: Some(RuntimeDoneMetrics {
392 duration_ms: 2599.0,
393 produced_bytes: Some(8),
394 }),
395 spans: vec!(
396 Span {
397 name:"responseLatency".to_string(),
398 start: Utc
399 .with_ymd_and_hms(2022, 10, 21, 14, 5, 3)
400 .unwrap()
401 .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap())
402 .unwrap(),
403 duration_ms: 2598.0
404 },
405 Span {
406 name:"responseDuration".to_string(),
407 start: Utc
408 .with_ymd_and_hms(2022, 10, 21, 14, 5, 5)
409 .unwrap()
410 .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap())
411 .unwrap(),
412 duration_ms: 0.0
413 },
414 ),
415 tracing: Some(TraceContext{
416 span_id: Some("24cd7d670fa455f0".to_string()),
417 r#type: TracingType::AmznTraceId,
418 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
419 }),
420 },
421 ),
422 platform_report: (
424 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"}}"#,
425 LambdaTelemetryRecord::PlatformReport {
426 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
427 status: Status::Success,
428 error_type: None,
429 metrics: ReportMetrics {
430 duration_ms: 2599.4,
431 billed_duration_ms: 2600,
432 memory_size_mb:128,
433 max_memory_used_mb:94,
434 init_duration_ms: Some(549.04),
435 restore_duration_ms: None,
436 },
437 spans: Vec::new(),
438 tracing: Some(TraceContext {
439 span_id: Some("24cd7d670fa455f0".to_string()),
440 r#type: TracingType::AmznTraceId,
441 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
442 }),
443 },
444 ),
445 platform_telemetry_subscription: (
447 r#"{"time":"2022-10-19T13:52:15.667Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#,
448 LambdaTelemetryRecord::PlatformTelemetrySubscription {
449 name: "my-extension".to_string(),
450 state: "Subscribed".to_string(),
451 types: vec!("platform".to_string(), "function".to_string()),
452 },
453 ),
454 platform_init_runtime_done: (
456 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success"}}"#,
457 LambdaTelemetryRecord::PlatformInitRuntimeDone {
458 initialization_type: InitType::OnDemand,
459 status: Status::Success,
460 phase: None,
461 error_type: None,
462 spans: Vec::new(),
463 },
464 ),
465 platform_extension: (
467 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#,
468 LambdaTelemetryRecord::PlatformExtension {
469 name: "my-extension".to_string(),
470 state: "Ready".to_string(),
471 events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()),
472 },
473 ),
474 platform_init_report: (
476 r#"{"time":"2022-10-19T13:52:16.136Z","type":"platform.initReport","record":{"initializationType":"on-demand","metrics":{"durationMs":500.0},"phase":"init"}}"#,
477 LambdaTelemetryRecord::PlatformInitReport {
478 initialization_type: InitType::OnDemand,
479 phase: InitPhase::Init,
480 metrics: InitReportMetrics { duration_ms: 500.0 },
481 spans: Vec::new(),
482 }
483 ),
484 }
485}
486
487#[cfg(test)]
488mod serialization_tests {
489 use chrono::{TimeDelta, TimeZone};
490
491 use super::*;
492 macro_rules! serialize_tests {
493 ($($name:ident$(<$log:ty>)?: $value:expr,)*) => {
494 $(
495 #[test]
496 fn $name() {
497 let (input, expected): (LambdaTelemetry$(<$log>)?, &str) = $value;
498 let actual = serde_json::to_string(&input).expect("unable to serialize");
499 println!("Input: {:?}\n", input);
500 println!("Expected:\n {:?}\n", expected);
501 println!("Actual:\n {:?}\n", actual);
502
503 assert!(actual == expected);
504 }
505 )*
506 };
507 }
508
509 serialize_tests! {
510 function: (
512 LambdaTelemetry {
513 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
514 record: LambdaTelemetryRecord::Function("hello world".to_string()),
515 },
516 r#"{"time":"2023-11-28T12:00:09Z","type":"function","record":"hello world"}"#,
517 ),
518 function_generic<bool>: (
520 LambdaTelemetry {
521 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
522 record: LambdaTelemetryRecord::Function(true),
523 },
524 r#"{"time":"2023-11-28T12:00:09Z","type":"function","record":true}"#,
525 ),
526 extension: (
528 LambdaTelemetry {
529 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
530 record: LambdaTelemetryRecord::Extension("hello world".to_string()),
531 },
532 r#"{"time":"2023-11-28T12:00:09Z","type":"extension","record":"hello world"}"#,
533 ),
534 extension_generic<bool>: (
536 LambdaTelemetry {
537 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
538 record: LambdaTelemetryRecord::Extension(true),
539 },
540 r#"{"time":"2023-11-28T12:00:09Z","type":"extension","record":true}"#,
541 ),
542 platform_start: (
544 LambdaTelemetry{
545 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
546 record: LambdaTelemetryRecord::PlatformStart {
547 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
548 version: Some("$LATEST".to_string()),
549 tracing: Some(TraceContext{
550 span_id: Some("24cd7d670fa455f0".to_string()),
551 r#type: TracingType::AmznTraceId,
552 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
553 }),
554 }
555 },
556 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"}}}"#,
557 ),
558 platform_init_start: (
560 LambdaTelemetry{
561 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
562 record: LambdaTelemetryRecord::PlatformInitStart {
563 initialization_type: InitType::OnDemand,
564 phase: InitPhase::Init,
565 runtime_version: None,
566 runtime_version_arn: None,
567 },
568 },
569 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#,
570 ),
571 platform_runtime_done: (
573 LambdaTelemetry{
574 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
575 record: LambdaTelemetryRecord::PlatformRuntimeDone {
576 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
577 status: Status::Success,
578 error_type: None,
579 metrics: Some(RuntimeDoneMetrics {
580 duration_ms: 2599.0,
581 produced_bytes: Some(8),
582 }),
583 spans: vec!(
584 Span {
585 name:"responseLatency".to_string(),
586 start: Utc
587 .with_ymd_and_hms(2022, 10, 21, 14, 5, 3)
588 .unwrap()
589 .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap())
590 .unwrap(),
591 duration_ms: 2598.0
592 },
593 Span {
594 name:"responseDuration".to_string(),
595 start: Utc
596 .with_ymd_and_hms(2022, 10, 21, 14, 5, 5)
597 .unwrap()
598 .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap())
599 .unwrap(),
600 duration_ms: 0.0
601 },
602 ),
603 tracing: Some(TraceContext{
604 span_id: Some("24cd7d670fa455f0".to_string()),
605 r#type: TracingType::AmznTraceId,
606 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
607 }),
608 },
609 },
610 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"}}}"#,
611 ),
612 platform_report: (
614 LambdaTelemetry{
615 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
616 record: LambdaTelemetryRecord::PlatformReport {
617 request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(),
618 status: Status::Success,
619 error_type: None,
620 metrics: ReportMetrics {
621 duration_ms: 2599.4,
622 billed_duration_ms: 2600,
623 memory_size_mb:128,
624 max_memory_used_mb:94,
625 init_duration_ms: Some(549.04),
626 restore_duration_ms: None,
627 },
628 spans: Vec::new(),
629 tracing: Some(TraceContext {
630 span_id: Some("24cd7d670fa455f0".to_string()),
631 r#type: TracingType::AmznTraceId,
632 value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(),
633 }),
634 },
635 },
636 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"}}}"#,
637 ),
638 platform_telemetry_subscription: (
640 LambdaTelemetry{
641 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
642 record: LambdaTelemetryRecord::PlatformTelemetrySubscription {
643 name: "my-extension".to_string(),
644 state: "Subscribed".to_string(),
645 types: vec!("platform".to_string(), "function".to_string()),
646 },
647 },
648 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#,
649 ),
650 platform_init_runtime_done: (
652 LambdaTelemetry{
653 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
654 record: LambdaTelemetryRecord::PlatformInitRuntimeDone {
655 initialization_type: InitType::OnDemand,
656 status: Status::Success,
657 phase: None,
658 error_type: None,
659 spans: Vec::new(),
660 },
661 },
662 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success","spans":[]}}"#,
663 ),
664 platform_extension: (
666 LambdaTelemetry {
667 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
668 record: LambdaTelemetryRecord::PlatformExtension {
669 name: "my-extension".to_string(),
670 state: "Ready".to_string(),
671 events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()),
672 },
673 },
674 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#,
675 ),
676 platform_init_report: (
678 LambdaTelemetry {
679 time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(),
680 record: LambdaTelemetryRecord::PlatformInitReport {
681 initialization_type: InitType::OnDemand,
682 phase: InitPhase::Init,
683 metrics: InitReportMetrics { duration_ms: 500.0 },
684 spans: Vec::new(),
685 },
686 },
687 r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initReport","record":{"initializationType":"on-demand","phase":"init","metrics":{"durationMs":500.0},"spans":[]}}"#,
688 ),
689
690 }
691}