@@ -3,14 +3,14 @@ use http::{Request, Response};
3
3
use http_body_util:: BodyExt ;
4
4
use hyper:: body:: Incoming ;
5
5
use lambda_runtime_api_client:: body:: Body ;
6
- use serde:: Deserialize ;
6
+ use serde:: { Deserialize , Serialize } ;
7
7
use std:: { boxed:: Box , fmt, sync:: Arc } ;
8
8
use tokio:: sync:: Mutex ;
9
9
use tower:: Service ;
10
10
use tracing:: { error, trace} ;
11
11
12
12
/// Payload received from the Telemetry API
13
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
13
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
14
14
pub struct LambdaTelemetry {
15
15
/// Time when the telemetry was generated
16
16
pub time : DateTime < Utc > ,
@@ -20,7 +20,7 @@ pub struct LambdaTelemetry {
20
20
}
21
21
22
22
/// Record in a LambdaTelemetry entry
23
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
23
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
24
24
#[ serde( tag = "type" , content = "record" , rename_all = "lowercase" ) ]
25
25
pub enum LambdaTelemetryRecord {
26
26
/// Function log records
@@ -37,8 +37,10 @@ pub enum LambdaTelemetryRecord {
37
37
/// Phase of initialisation
38
38
phase : InitPhase ,
39
39
/// Lambda runtime version
40
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
40
41
runtime_version : Option < String > ,
41
42
/// Lambda runtime version ARN
43
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
42
44
runtime_version_arn : Option < String > ,
43
45
} ,
44
46
/// Platform init runtime done record
@@ -47,10 +49,12 @@ pub enum LambdaTelemetryRecord {
47
49
/// Type of initialization
48
50
initialization_type : InitType ,
49
51
/// Phase of initialisation
52
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
50
53
phase : Option < InitPhase > ,
51
54
/// Status of initalization
52
55
status : Status ,
53
56
/// When the status = failure, the error_type describes what kind of error occurred
57
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
54
58
error_type : Option < String > ,
55
59
/// Spans
56
60
#[ serde( default ) ]
@@ -75,8 +79,10 @@ pub enum LambdaTelemetryRecord {
75
79
/// Request identifier
76
80
request_id : String ,
77
81
/// Version of the Lambda function
82
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
78
83
version : Option < String > ,
79
84
/// Trace Context
85
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
80
86
tracing : Option < TraceContext > ,
81
87
} ,
82
88
/// Record marking the completion of an invocation
@@ -87,13 +93,16 @@ pub enum LambdaTelemetryRecord {
87
93
/// Status of the invocation
88
94
status : Status ,
89
95
/// When unsuccessful, the error_type describes what kind of error occurred
96
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
90
97
error_type : Option < String > ,
91
98
/// Metrics corresponding to the runtime
99
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
92
100
metrics : Option < RuntimeDoneMetrics > ,
93
101
/// Spans
94
102
#[ serde( default ) ]
95
103
spans : Vec < Span > ,
96
104
/// Trace Context
105
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
97
106
tracing : Option < TraceContext > ,
98
107
} ,
99
108
/// Platfor report record
@@ -104,13 +113,15 @@ pub enum LambdaTelemetryRecord {
104
113
/// Status of the invocation
105
114
status : Status ,
106
115
/// When unsuccessful, the error_type describes what kind of error occurred
116
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
107
117
error_type : Option < String > ,
108
118
/// Metrics
109
119
metrics : ReportMetrics ,
110
120
/// Spans
111
121
#[ serde( default ) ]
112
122
spans : Vec < Span > ,
113
123
/// Trace Context
124
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
114
125
tracing : Option < TraceContext > ,
115
126
} ,
116
127
@@ -147,7 +158,7 @@ pub enum LambdaTelemetryRecord {
147
158
}
148
159
149
160
/// Type of Initialization
150
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq ) ]
161
+ #[ derive( Clone , Debug , Deserialize , Serialize , Eq , PartialEq ) ]
151
162
#[ serde( rename_all = "kebab-case" ) ]
152
163
pub enum InitType {
153
164
/// Initialised on demand
@@ -159,7 +170,7 @@ pub enum InitType {
159
170
}
160
171
161
172
/// Phase in which initialization occurs
162
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq ) ]
173
+ #[ derive( Clone , Debug , Deserialize , Serialize , Eq , PartialEq ) ]
163
174
#[ serde( rename_all = "kebab-case" ) ]
164
175
pub enum InitPhase {
165
176
/// Initialization phase
@@ -169,7 +180,7 @@ pub enum InitPhase {
169
180
}
170
181
171
182
/// Status of invocation/initialization
172
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq ) ]
183
+ #[ derive( Clone , Debug , Deserialize , Serialize , Eq , PartialEq ) ]
173
184
#[ serde( rename_all = "kebab-case" ) ]
174
185
pub enum Status {
175
186
/// Success
@@ -183,7 +194,7 @@ pub enum Status {
183
194
}
184
195
185
196
/// Span
186
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
197
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
187
198
#[ serde( rename_all = "camelCase" ) ]
188
199
pub struct Span {
189
200
/// Duration of the span
@@ -195,7 +206,7 @@ pub struct Span {
195
206
}
196
207
197
208
/// Tracing Context
198
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq ) ]
209
+ #[ derive( Clone , Debug , Deserialize , Serialize , Eq , PartialEq ) ]
199
210
#[ serde( rename_all = "camelCase" ) ]
200
211
pub struct TraceContext {
201
212
/// Span ID
@@ -207,23 +218,23 @@ pub struct TraceContext {
207
218
}
208
219
209
220
/// Type of tracing
210
- #[ derive( Clone , Debug , Deserialize , Eq , PartialEq ) ]
221
+ #[ derive( Clone , Debug , Deserialize , Serialize , Eq , PartialEq ) ]
211
222
pub enum TracingType {
212
223
/// Amazon trace type
213
224
#[ serde( rename = "X-Amzn-Trace-Id" ) ]
214
225
AmznTraceId ,
215
226
}
216
227
217
228
///Init report metrics
218
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
229
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
219
230
#[ serde( rename_all = "camelCase" ) ]
220
231
pub struct InitReportMetrics {
221
232
/// Duration of initialization
222
233
pub duration_ms : f64 ,
223
234
}
224
235
225
236
/// Report metrics
226
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
237
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
227
238
#[ serde( rename_all = "camelCase" ) ]
228
239
pub struct ReportMetrics {
229
240
/// Duration in milliseconds
@@ -237,15 +248,15 @@ pub struct ReportMetrics {
237
248
#[ serde( rename = "maxMemoryUsedMB" ) ]
238
249
pub max_memory_used_mb : u64 ,
239
250
/// Init duration in case of a cold start
240
- #[ serde( default = "Option::default" ) ]
251
+ #[ serde( default = "Option::default" , skip_serializing_if = "Option::is_none" ) ]
241
252
pub init_duration_ms : Option < f64 > ,
242
253
/// Restore duration in milliseconds
243
- #[ serde( default = "Option::default" ) ]
254
+ #[ serde( default = "Option::default" , skip_serializing_if = "Option::is_none" ) ]
244
255
pub restore_duration_ms : Option < f64 > ,
245
256
}
246
257
247
258
/// Runtime done metrics
248
- #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
259
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
249
260
#[ serde( rename_all = "camelCase" ) ]
250
261
pub struct RuntimeDoneMetrics {
251
262
/// Duration in milliseconds
@@ -303,7 +314,7 @@ where
303
314
}
304
315
305
316
#[ cfg( test) ]
306
- mod tests {
317
+ mod deserialization_tests {
307
318
use super :: * ;
308
319
use chrono:: { Duration , TimeZone } ;
309
320
@@ -459,3 +470,193 @@ mod tests {
459
470
) ,
460
471
}
461
472
}
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