@@ -3,14 +3,14 @@ use http::{Request, Response};
33use http_body_util:: BodyExt ;
44use hyper:: body:: Incoming ;
55use lambda_runtime_api_client:: body:: Body ;
6- use serde:: Deserialize ;
6+ use serde:: { Deserialize , Serialize } ;
77use std:: { boxed:: Box , fmt, sync:: Arc } ;
88use tokio:: sync:: Mutex ;
99use tower:: Service ;
1010use tracing:: { error, trace} ;
1111
1212/// Payload received from the Telemetry API
13- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
13+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
1414pub 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" ) ]
2525pub 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" ) ]
152163pub 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" ) ]
164175pub 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" ) ]
174185pub 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" ) ]
188199pub 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" ) ]
200211pub 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 ) ]
211222pub 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" ) ]
220231pub 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" ) ]
228239pub 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" ) ]
250261pub 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