Skip to content

Commit ead7f37

Browse files
authored
Implement Serialize for lambda telemetry (#759)
* Add serialize testing mod and macro * Use and derive serialize * Skip serialize if None on Options * Add unit tests for serialization
1 parent 953b2d2 commit ead7f37

File tree

1 file changed

+216
-15
lines changed

1 file changed

+216
-15
lines changed

lambda-extension/src/telemetry.rs

+216-15
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use http::{Request, Response};
33
use http_body_util::BodyExt;
44
use hyper::body::Incoming;
55
use lambda_runtime_api_client::body::Body;
6-
use serde::Deserialize;
6+
use serde::{Deserialize, Serialize};
77
use std::{boxed::Box, fmt, sync::Arc};
88
use tokio::sync::Mutex;
99
use tower::Service;
1010
use tracing::{error, trace};
1111

1212
/// Payload received from the Telemetry API
13-
#[derive(Clone, Debug, Deserialize, PartialEq)]
13+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1414
pub struct LambdaTelemetry {
1515
/// Time when the telemetry was generated
1616
pub time: DateTime<Utc>,
@@ -20,7 +20,7 @@ pub struct LambdaTelemetry {
2020
}
2121

2222
/// Record in a LambdaTelemetry entry
23-
#[derive(Clone, Debug, Deserialize, PartialEq)]
23+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
2424
#[serde(tag = "type", content = "record", rename_all = "lowercase")]
2525
pub enum LambdaTelemetryRecord {
2626
/// Function log records
@@ -37,8 +37,10 @@ pub enum LambdaTelemetryRecord {
3737
/// Phase of initialisation
3838
phase: InitPhase,
3939
/// Lambda runtime version
40+
#[serde(skip_serializing_if = "Option::is_none")]
4041
runtime_version: Option<String>,
4142
/// Lambda runtime version ARN
43+
#[serde(skip_serializing_if = "Option::is_none")]
4244
runtime_version_arn: Option<String>,
4345
},
4446
/// Platform init runtime done record
@@ -47,10 +49,12 @@ pub enum LambdaTelemetryRecord {
4749
/// Type of initialization
4850
initialization_type: InitType,
4951
/// Phase of initialisation
52+
#[serde(skip_serializing_if = "Option::is_none")]
5053
phase: Option<InitPhase>,
5154
/// Status of initalization
5255
status: Status,
5356
/// When the status = failure, the error_type describes what kind of error occurred
57+
#[serde(skip_serializing_if = "Option::is_none")]
5458
error_type: Option<String>,
5559
/// Spans
5660
#[serde(default)]
@@ -75,8 +79,10 @@ pub enum LambdaTelemetryRecord {
7579
/// Request identifier
7680
request_id: String,
7781
/// Version of the Lambda function
82+
#[serde(skip_serializing_if = "Option::is_none")]
7883
version: Option<String>,
7984
/// Trace Context
85+
#[serde(skip_serializing_if = "Option::is_none")]
8086
tracing: Option<TraceContext>,
8187
},
8288
/// Record marking the completion of an invocation
@@ -87,13 +93,16 @@ pub enum LambdaTelemetryRecord {
8793
/// Status of the invocation
8894
status: Status,
8995
/// When unsuccessful, the error_type describes what kind of error occurred
96+
#[serde(skip_serializing_if = "Option::is_none")]
9097
error_type: Option<String>,
9198
/// Metrics corresponding to the runtime
99+
#[serde(skip_serializing_if = "Option::is_none")]
92100
metrics: Option<RuntimeDoneMetrics>,
93101
/// Spans
94102
#[serde(default)]
95103
spans: Vec<Span>,
96104
/// Trace Context
105+
#[serde(skip_serializing_if = "Option::is_none")]
97106
tracing: Option<TraceContext>,
98107
},
99108
/// Platfor report record
@@ -104,13 +113,15 @@ pub enum LambdaTelemetryRecord {
104113
/// Status of the invocation
105114
status: Status,
106115
/// When unsuccessful, the error_type describes what kind of error occurred
116+
#[serde(skip_serializing_if = "Option::is_none")]
107117
error_type: Option<String>,
108118
/// Metrics
109119
metrics: ReportMetrics,
110120
/// Spans
111121
#[serde(default)]
112122
spans: Vec<Span>,
113123
/// Trace Context
124+
#[serde(skip_serializing_if = "Option::is_none")]
114125
tracing: Option<TraceContext>,
115126
},
116127

@@ -147,7 +158,7 @@ pub enum LambdaTelemetryRecord {
147158
}
148159

149160
/// Type of Initialization
150-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
161+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
151162
#[serde(rename_all = "kebab-case")]
152163
pub enum InitType {
153164
/// Initialised on demand
@@ -159,7 +170,7 @@ pub enum InitType {
159170
}
160171

161172
/// Phase in which initialization occurs
162-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
173+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
163174
#[serde(rename_all = "kebab-case")]
164175
pub enum InitPhase {
165176
/// Initialization phase
@@ -169,7 +180,7 @@ pub enum InitPhase {
169180
}
170181

171182
/// Status of invocation/initialization
172-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
183+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
173184
#[serde(rename_all = "kebab-case")]
174185
pub enum Status {
175186
/// Success
@@ -183,7 +194,7 @@ pub enum Status {
183194
}
184195

185196
/// Span
186-
#[derive(Clone, Debug, Deserialize, PartialEq)]
197+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
187198
#[serde(rename_all = "camelCase")]
188199
pub struct Span {
189200
/// Duration of the span
@@ -195,7 +206,7 @@ pub struct Span {
195206
}
196207

197208
/// Tracing Context
198-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
209+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
199210
#[serde(rename_all = "camelCase")]
200211
pub struct TraceContext {
201212
/// Span ID
@@ -207,23 +218,23 @@ pub struct TraceContext {
207218
}
208219

209220
/// Type of tracing
210-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
221+
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
211222
pub enum TracingType {
212223
/// Amazon trace type
213224
#[serde(rename = "X-Amzn-Trace-Id")]
214225
AmznTraceId,
215226
}
216227

217228
///Init report metrics
218-
#[derive(Clone, Debug, Deserialize, PartialEq)]
229+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
219230
#[serde(rename_all = "camelCase")]
220231
pub struct InitReportMetrics {
221232
/// Duration of initialization
222233
pub duration_ms: f64,
223234
}
224235

225236
/// Report metrics
226-
#[derive(Clone, Debug, Deserialize, PartialEq)]
237+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
227238
#[serde(rename_all = "camelCase")]
228239
pub struct ReportMetrics {
229240
/// Duration in milliseconds
@@ -237,15 +248,15 @@ pub struct ReportMetrics {
237248
#[serde(rename = "maxMemoryUsedMB")]
238249
pub max_memory_used_mb: u64,
239250
/// Init duration in case of a cold start
240-
#[serde(default = "Option::default")]
251+
#[serde(default = "Option::default", skip_serializing_if = "Option::is_none")]
241252
pub init_duration_ms: Option<f64>,
242253
/// Restore duration in milliseconds
243-
#[serde(default = "Option::default")]
254+
#[serde(default = "Option::default", skip_serializing_if = "Option::is_none")]
244255
pub restore_duration_ms: Option<f64>,
245256
}
246257

247258
/// Runtime done metrics
248-
#[derive(Clone, Debug, Deserialize, PartialEq)]
259+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
249260
#[serde(rename_all = "camelCase")]
250261
pub struct RuntimeDoneMetrics {
251262
/// Duration in milliseconds
@@ -303,7 +314,7 @@ where
303314
}
304315

305316
#[cfg(test)]
306-
mod tests {
317+
mod deserialization_tests {
307318
use super::*;
308319
use chrono::{Duration, TimeZone};
309320

@@ -459,3 +470,193 @@ mod tests {
459470
),
460471
}
461472
}
473+
474+
#[cfg(test)]
475+
mod serialization_tests {
476+
use chrono::{Duration, 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
498+
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
506+
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
514+
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.initStart
530+
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.runtimeDone
543+
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(Duration::milliseconds(165))
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(Duration::milliseconds(763))
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
584+
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.telemetrySubscription
610+
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.initRuntimeDone
622+
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
636+
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.initReport
648+
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+
}

0 commit comments

Comments
 (0)