Skip to content

[RFC] Lambda Logs API Integration #396

@nmoutschen

Description

@nmoutschen

The purpose of this issue is to define and agree on an library implementation for the Lambda Logs API.

Before going into proposed user experiences, there are a few characteristics of the Logs API that are important to consider:

  1. Only extensions can subscribe to logs. You need to first register the extension, then subscribe for the logs using the same extension identifier.
  2. Upon subscribing, Lambda will deliver logs to a local endpoint (HTTP or TCP). Meaning we only send a single API call on initialization.

Challenges with current implementation

Right now, all libraries (lambda_runtime and lambda_extension) abstract the registration process under the hood. That means developers don't have control between the registration process and the first invocation. However, to work correctly with the Lambda Logs API, a developer needs to first register the extension, then send a call to subscribe for logs.

That means that to implement this feature, we either need to give more flexibility, or create a new abstraction just for the logs API. With the former, this would make it harder to move all handler traits to tower::Service (see #374) unless we implement a factory pattern. With the latter, we would remove the possibility of having an extension serving multiple purposes.

Proposition 1: High-level library

The user experience would resemble the ones from other crates in this project. It would also take care of running a TCP server, and send log entries to a function or trait implementation provided.

Pros:

  • High-level library that hides the Logs API complexity under the hood
  • Works out of the box with migrating to tower::Service

Cons:

  • Less flexible for those that want to react to events or do something else while the extension is running
use lambda_logs::{log_fn, Error, LogEvent};

async fn log_processor(log_events: &[LogEvent]) -> Result<(), Error> {
    // Process a batch of log entries here
    todo!();
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let func = log_fn(log_processor);
    lambda_logs::run(func).await
}

Proposition 2: Low-level library with init function

This would provide a low-level library only. The developers would be responsible for starting an HTTP or TCP server, but would still benefit from a function that will subscribe for logs, and structs for log entries. Developers would still use lambda_extension to actually register the extension, and then subscribe to the logs.

Pros:

  • Give more flexibility to the developers

Cons:

  • Does not support migrating to tower::Service (extra init function)
  • Developers need to start their own TCP/HTTP server
use lambda_extension::{Extension, Error, LambdaEvent};
use lambda_logs::{
    // Function to make the API call
    subscribe_logs,
    // Schema for log events
    LogEvent.
};
use std::{
    future::{ready, Future},
    pin::Pin,
};

#[derive(Default)]
struct LogExtension;

impl Extension for LogExtension {
    type Fut = Pin<Box<dyn Future<Output = Result<(), Error>>>>;

    fn init(&mut self, extension_id: &str) -> Result<(), Error> {
        // Start an HTTP server
       let endpoint: String = my_function_to_make_a_server();
        subscribe_logs(extension_id, &endpoint);
    }

    fn call(&mut self, event: LambdaEvent) -> Self::Fut {
        // Process events from the extension
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    run(LogExtension::default()).await
}

Proposition 3: Low-level library with factory pattern

TODO

Pros:

  • Give more flexibility to the developers
  • Supports migrating to tower::Service

Cons:

  • Developers need to start their own TCP/HTTP server

Proposition 4: your idea here

If you have an idea that gives flexibility while providing a high-level interface, that'd be awesome, but I'm unsure what it would look like.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions