Skip to content

Commit 0839125

Browse files
Add support for Telemetry API (#563)
* Add support for Telemetry API * Update lambda-extension/src/extension.rs Co-authored-by: David Calavera <david.calavera@gmail.com> * derive Eq where possible * Add simple examples of using telemetry API Co-authored-by: David Calavera <david.calavera@gmail.com>
1 parent 826d4e6 commit 0839125

File tree

8 files changed

+732
-13
lines changed

8 files changed

+732
-13
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "extension-telemetry-basic"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
7+
# Use cargo-edit(https://github.com/killercup/cargo-edit#installation)
8+
# to manage dependencies.
9+
# Running `cargo add DEPENDENCY_NAME` will
10+
# add the latest version of a dependency to the list,
11+
# and it will keep the alphabetic ordering for you.
12+
13+
[dependencies]
14+
lambda-extension = { path = "../../lambda-extension" }
15+
serde = "1.0.136"
16+
tokio = { version = "1", features = ["macros", "rt"] }
17+
tracing = { version = "0.1", features = ["log"] }
18+
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
19+
20+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# AWS Lambda Telemetry extension example
2+
3+
## Build & Deploy
4+
5+
1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation)
6+
2. Build the extension with `cargo lambda build --release --extension`
7+
3. Deploy the extension as a layer with `cargo lambda deploy --extension`
8+
9+
The last command will give you an ARN for the extension layer that you can use in your functions.
10+
11+
12+
## Build for ARM 64
13+
14+
Build the extension with `cargo lambda build --release --extension --arm64`
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService};
2+
use tracing::info;
3+
4+
async fn handler(events: Vec<LambdaTelemetry>) -> Result<(), Error> {
5+
for event in events {
6+
match event.record {
7+
LambdaTelemetryRecord::Function(record) => info!("[logs] [function] {}", record),
8+
LambdaTelemetryRecord::PlatformInitStart {
9+
initialization_type: _,
10+
phase: _,
11+
runtime_version: _,
12+
runtime_version_arn: _,
13+
} => info!("[platform] Initialization started"),
14+
LambdaTelemetryRecord::PlatformInitRuntimeDone {
15+
initialization_type: _,
16+
phase: _,
17+
status: _,
18+
error_type: _,
19+
spans: _,
20+
} => info!("[platform] Initialization finished"),
21+
LambdaTelemetryRecord::PlatformStart {
22+
request_id,
23+
version: _,
24+
tracing: _,
25+
} => info!("[platform] Handling of request {} started", request_id),
26+
LambdaTelemetryRecord::PlatformRuntimeDone {
27+
request_id,
28+
status: _,
29+
error_type: _,
30+
metrics: _,
31+
spans: _,
32+
tracing: _,
33+
} => info!("[platform] Handling of request {} finished", request_id),
34+
_ => (),
35+
}
36+
}
37+
38+
Ok(())
39+
}
40+
41+
#[tokio::main]
42+
async fn main() -> Result<(), Error> {
43+
// The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber`
44+
// While `tracing` is used internally, `log` can be used as well if preferred.
45+
tracing_subscriber::fmt()
46+
.with_max_level(tracing::Level::INFO)
47+
// disabling time is handy because CloudWatch will add the ingestion time.
48+
.without_time()
49+
.init();
50+
51+
let telemetry_processor = SharedService::new(service_fn(handler));
52+
53+
Extension::new()
54+
.with_telemetry_processor(telemetry_processor)
55+
.run()
56+
.await?;
57+
58+
Ok(())
59+
}

lambda-extension/README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
### Simple extension
1010

11-
The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events, and logs them in CloudWatch.
11+
The code below creates a simple extension that's registered to every `INVOKE` and `SHUTDOWN` events.
1212

1313
```rust,no_run
1414
use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent};
@@ -75,6 +75,45 @@ async fn main() -> Result<(), Error> {
7575
7676
```
7777

78+
### Telemetry processor extension
79+
80+
```rust,no_run
81+
use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService};
82+
use tracing::info;
83+
84+
async fn handler(events: Vec<LambdaTelemetry>) -> Result<(), Error> {
85+
for event in events {
86+
match event.record {
87+
LambdaTelemetryRecord::Function(record) => {
88+
// do something with the function log record
89+
},
90+
LambdaTelemetryRecord::PlatformInitStart {
91+
initialization_type: _,
92+
phase: _,
93+
runtime_version: _,
94+
runtime_version_arn: _,
95+
} => {
96+
// do something with the PlatformInitStart event
97+
},
98+
// more types of telemetry events are available
99+
_ => (),
100+
}
101+
}
102+
103+
Ok(())
104+
}
105+
106+
#[tokio::main]
107+
async fn main() -> Result<(), Error> {
108+
let telemetry_processor = SharedService::new(service_fn(handler));
109+
110+
Extension::new().with_telemetry_processor(telemetry_processor).run().await?;
111+
112+
Ok(())
113+
}
114+
115+
```
116+
78117
## Deployment
79118

80119
Lambda extensions can be added to your functions either using [Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#using-extensions-config), or adding them to [containers images](https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html#invocation-extensions-images).

lambda-extension/src/extension.rs

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,31 @@ use tokio_stream::StreamExt;
99
use tower::{service_fn, MakeService, Service, ServiceExt};
1010
use tracing::{error, trace};
1111

12-
use crate::{logs::*, requests, Error, ExtensionError, LambdaEvent, NextEvent};
12+
use crate::{
13+
logs::*,
14+
requests::{self, Api},
15+
telemetry_wrapper, Error, ExtensionError, LambdaEvent, LambdaTelemetry, NextEvent,
16+
};
1317

1418
const DEFAULT_LOG_PORT_NUMBER: u16 = 9002;
19+
const DEFAULT_TELEMETRY_PORT_NUMBER: u16 = 9003;
1520

16-
/// An Extension that runs event and log processors
17-
pub struct Extension<'a, E, L> {
21+
/// An Extension that runs event, log and telemetry processors
22+
pub struct Extension<'a, E, L, T> {
1823
extension_name: Option<&'a str>,
1924
events: Option<&'a [&'a str]>,
2025
events_processor: E,
2126
log_types: Option<&'a [&'a str]>,
2227
logs_processor: Option<L>,
2328
log_buffering: Option<LogBuffering>,
2429
log_port_number: u16,
30+
telemetry_types: Option<&'a [&'a str]>,
31+
telemetry_processor: Option<T>,
32+
telemetry_buffering: Option<LogBuffering>,
33+
telemetry_port_number: u16,
2534
}
2635

27-
impl<'a> Extension<'a, Identity<LambdaEvent>, MakeIdentity<Vec<LambdaLog>>> {
36+
impl<'a> Extension<'a, Identity<LambdaEvent>, MakeIdentity<Vec<LambdaLog>>, MakeIdentity<Vec<LambdaTelemetry>>> {
2837
/// Create a new base [`Extension`] with a no-op events processor
2938
pub fn new() -> Self {
3039
Extension {
@@ -35,17 +44,23 @@ impl<'a> Extension<'a, Identity<LambdaEvent>, MakeIdentity<Vec<LambdaLog>>> {
3544
log_buffering: None,
3645
logs_processor: None,
3746
log_port_number: DEFAULT_LOG_PORT_NUMBER,
47+
telemetry_types: None,
48+
telemetry_buffering: None,
49+
telemetry_processor: None,
50+
telemetry_port_number: DEFAULT_TELEMETRY_PORT_NUMBER,
3851
}
3952
}
4053
}
4154

42-
impl<'a> Default for Extension<'a, Identity<LambdaEvent>, MakeIdentity<Vec<LambdaLog>>> {
55+
impl<'a> Default
56+
for Extension<'a, Identity<LambdaEvent>, MakeIdentity<Vec<LambdaLog>>, MakeIdentity<Vec<LambdaTelemetry>>>
57+
{
4358
fn default() -> Self {
4459
Self::new()
4560
}
4661
}
4762

48-
impl<'a, E, L> Extension<'a, E, L>
63+
impl<'a, E, L, T> Extension<'a, E, L, T>
4964
where
5065
E: Service<LambdaEvent>,
5166
E::Future: Future<Output = Result<(), E::Error>>,
@@ -58,6 +73,14 @@ where
5873
L::Error: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
5974
L::MakeError: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
6075
L::Future: Send,
76+
77+
// Fixme: 'static bound might be too restrictive
78+
T: MakeService<(), Vec<LambdaTelemetry>, Response = ()> + Send + Sync + 'static,
79+
T::Service: Service<Vec<LambdaTelemetry>, Response = ()> + Send + Sync,
80+
<T::Service as Service<Vec<LambdaTelemetry>>>::Future: Send + 'a,
81+
T::Error: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
82+
T::MakeError: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Debug,
83+
T::Future: Send,
6184
{
6285
/// Create a new [`Extension`] with a given extension name
6386
pub fn with_extension_name(self, extension_name: &'a str) -> Self {
@@ -77,7 +100,7 @@ where
77100
}
78101

79102
/// Create a new [`Extension`] with a service that receives Lambda events.
80-
pub fn with_events_processor<N>(self, ep: N) -> Extension<'a, N, L>
103+
pub fn with_events_processor<N>(self, ep: N) -> Extension<'a, N, L, T>
81104
where
82105
N: Service<LambdaEvent>,
83106
N::Future: Future<Output = Result<(), N::Error>>,
@@ -91,11 +114,15 @@ where
91114
log_buffering: self.log_buffering,
92115
logs_processor: self.logs_processor,
93116
log_port_number: self.log_port_number,
117+
telemetry_types: self.telemetry_types,
118+
telemetry_buffering: self.telemetry_buffering,
119+
telemetry_processor: self.telemetry_processor,
120+
telemetry_port_number: self.telemetry_port_number,
94121
}
95122
}
96123

97124
/// Create a new [`Extension`] with a service that receives Lambda logs.
98-
pub fn with_logs_processor<N, NS>(self, lp: N) -> Extension<'a, E, N>
125+
pub fn with_logs_processor<N, NS>(self, lp: N) -> Extension<'a, E, N, T>
99126
where
100127
N: Service<()>,
101128
N::Future: Future<Output = Result<NS, N::Error>>,
@@ -109,6 +136,10 @@ where
109136
log_types: self.log_types,
110137
log_buffering: self.log_buffering,
111138
log_port_number: self.log_port_number,
139+
telemetry_types: self.telemetry_types,
140+
telemetry_buffering: self.telemetry_buffering,
141+
telemetry_processor: self.telemetry_processor,
142+
telemetry_port_number: self.telemetry_port_number,
112143
}
113144
}
114145

@@ -137,6 +168,53 @@ where
137168
}
138169
}
139170

171+
/// Create a new [`Extension`] with a service that receives Lambda telemetry data.
172+
pub fn with_telemetry_processor<N, NS>(self, lp: N) -> Extension<'a, E, L, N>
173+
where
174+
N: Service<()>,
175+
N::Future: Future<Output = Result<NS, N::Error>>,
176+
N::Error: Into<Box<dyn std::error::Error + Send + Sync>> + fmt::Display,
177+
{
178+
Extension {
179+
telemetry_processor: Some(lp),
180+
events_processor: self.events_processor,
181+
extension_name: self.extension_name,
182+
events: self.events,
183+
log_types: self.log_types,
184+
log_buffering: self.log_buffering,
185+
logs_processor: self.logs_processor,
186+
log_port_number: self.log_port_number,
187+
telemetry_types: self.telemetry_types,
188+
telemetry_buffering: self.telemetry_buffering,
189+
telemetry_port_number: self.telemetry_port_number,
190+
}
191+
}
192+
193+
/// Create a new [`Extension`] with a list of telemetry types to subscribe.
194+
/// The only accepted telemetry types are `function`, `platform`, and `extension`.
195+
pub fn with_telemetry_types(self, telemetry_types: &'a [&'a str]) -> Self {
196+
Extension {
197+
telemetry_types: Some(telemetry_types),
198+
..self
199+
}
200+
}
201+
202+
/// Create a new [`Extension`] with specific configuration to buffer telemetry.
203+
pub fn with_telemetry_buffering(self, lb: LogBuffering) -> Self {
204+
Extension {
205+
telemetry_buffering: Some(lb),
206+
..self
207+
}
208+
}
209+
210+
/// Create a new [`Extension`] with a different port number to listen to telemetry.
211+
pub fn with_telemetry_port_number(self, port_number: u16) -> Self {
212+
Extension {
213+
telemetry_port_number: port_number,
214+
..self
215+
}
216+
}
217+
140218
/// Execute the given extension
141219
pub async fn run(self) -> Result<(), Error> {
142220
let client = &Client::builder().build()?;
@@ -166,7 +244,8 @@ where
166244
trace!("Log processor started");
167245

168246
// Call Logs API to start receiving events
169-
let req = requests::subscribe_logs_request(
247+
let req = requests::subscribe_request(
248+
Api::LogsApi,
170249
extension_id,
171250
self.log_types,
172251
self.log_buffering,
@@ -179,6 +258,41 @@ where
179258
trace!("Registered extension with Logs API");
180259
}
181260

261+
if let Some(mut telemetry_processor) = self.telemetry_processor {
262+
trace!("Telemetry processor found");
263+
// Spawn task to run processor
264+
let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number));
265+
let make_service = service_fn(move |_socket: &AddrStream| {
266+
trace!("Creating new telemetry processor Service");
267+
let service = telemetry_processor.make_service(());
268+
async move {
269+
let service = Arc::new(Mutex::new(service.await?));
270+
Ok::<_, T::MakeError>(service_fn(move |req| telemetry_wrapper(service.clone(), req)))
271+
}
272+
});
273+
let server = Server::bind(&addr).serve(make_service);
274+
tokio::spawn(async move {
275+
if let Err(e) = server.await {
276+
error!("Error while running telemetry processor: {}", e);
277+
}
278+
});
279+
trace!("Telemetry processor started");
280+
281+
// Call Telemetry API to start receiving events
282+
let req = requests::subscribe_request(
283+
Api::TelemetryApi,
284+
extension_id,
285+
self.telemetry_types,
286+
self.telemetry_buffering,
287+
self.telemetry_port_number,
288+
)?;
289+
let res = client.call(req).await?;
290+
if res.status() != http::StatusCode::OK {
291+
return Err(ExtensionError::boxed("unable to initialize the telemetry api"));
292+
}
293+
trace!("Registered extension with Telemetry API");
294+
}
295+
182296
let incoming = async_stream::stream! {
183297
loop {
184298
trace!("Waiting for next event (incoming loop)");

lambda-extension/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ mod events;
1717
pub use events::*;
1818
mod logs;
1919
pub use logs::*;
20+
mod telemetry;
21+
pub use telemetry::*;
2022

2123
/// Include several request builders to interact with the Extension API.
2224
pub mod requests;

0 commit comments

Comments
 (0)