Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion bottlecap/src/lifecycle/invocation/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,21 @@ impl Processor {

/// Given trace context information, set it to the current span.
///
pub fn on_invocation_end(&mut self, trace_id: u64, span_id: u64, parent_id: u64) {
pub fn on_invocation_end(
&mut self,
trace_id: u64,
span_id: u64,
parent_id: u64,
status_code: Option<String>,
) {
self.span.trace_id = trace_id;
self.span.span_id = span_id;

if self.inferrer.get_inferred_span().is_some() {
self.inferrer.set_parent_id(parent_id);
if let Some(status_code) = status_code {
self.inferrer.set_status_code(status_code);
}
} else {
self.span.parent_id = parent_id;
}
Expand Down
34 changes: 32 additions & 2 deletions bottlecap/src/lifecycle/invocation/span_inferrer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use datadog_trace_protobuf::pb::Span;
use log::debug;
use rand::Rng;
use serde_json::Value;
use tracing::debug;

use crate::config::AwsConfig;

use crate::lifecycle::invocation::triggers::{
api_gateway_http_event::APIGatewayHttpEvent, Trigger,
api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent,
Trigger,
};

const FUNCTION_TRIGGER_EVENT_SOURCE_TAG: &str = "function_trigger.event_source";
Expand Down Expand Up @@ -58,6 +59,28 @@ impl SpanInferrer {
),
]);

self.is_async_span = t.is_async();
self.inferred_span = Some(span);
}
} else if APIGatewayRestEvent::is_match(&payload_value) {
if let Some(t) = APIGatewayRestEvent::new(payload_value) {
let mut span = Span {
span_id: Self::generate_span_id(),
..Default::default()
};

t.enrich_span(&mut span);
span.meta.extend([
(
FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(),
"api_gateway".to_string(),
),
(
FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(),
t.get_arn(&aws_config.region),
),
]);

self.is_async_span = t.is_async();
self.inferred_span = Some(span);
}
Expand All @@ -78,6 +101,13 @@ impl SpanInferrer {
}
}

pub fn set_status_code(&mut self, status_code: String) {
if let Some(s) = &mut self.inferred_span {
s.meta.insert("http.status_code".to_string(), status_code);
}
}

// TODO add status tag and other info from response
pub fn complete_inferred_span(&mut self, invocation_span: &Span) {
if let Some(s) = &mut self.inferred_span {
if self.is_async_span {
Expand Down
119 changes: 91 additions & 28 deletions bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,18 @@ impl Trigger for APIGatewayHttpEvent {
#[allow(clippy::cast_possible_truncation)]
fn enrich_span(&self, span: &mut Span) {
debug!("Enriching an Inferred Span for an API Gateway HTTP Event");
let resource = format!(
"{http_method} {path}",
http_method = self.request_context.http.method,
path = self.request_context.http.path
);
let resource = if self.route_key.is_empty() {
format!(
"{http_method} {route_key}",
http_method = self.request_context.http.method,
route_key = self.route_key
)
} else {
self.route_key.clone()
};

let http_url = format!(
"{domain_name}{path}",
"https://{domain_name}{path}",
domain_name = self.request_context.domain_name,
path = self.request_context.http.path
);
Expand Down Expand Up @@ -119,20 +124,35 @@ impl Trigger for APIGatewayHttpEvent {
let mut tags = HashMap::from([
(
"http.url".to_string(),
self.request_context.domain_name.clone(),
format!(
"https://{domain_name}{path}",
domain_name = self.request_context.domain_name.clone(),
path = self.request_context.http.path.clone()
),
),
// path and URL are full
// /users/12345/profile
(
"http_url_details.path".to_string(),
"http.url_details.path".to_string(),
self.request_context.http.path.clone(),
),
(
"http.method".to_string(),
self.request_context.http.method.clone(),
),
]);

// route is parameterized
// /users/{id}/profile
if !self.route_key.is_empty() {
tags.insert("http.route".to_string(), self.route_key.clone());
tags.insert(
"http.route".to_string(),
self.route_key
.clone()
.split_whitespace()
.last()
.unwrap_or(&self.route_key.clone())
.to_string(),
);
}

if let Some(referer) = self.headers.get("referer") {
Expand Down Expand Up @@ -202,7 +222,7 @@ mod tests {
request_id: "FaHnXjKCGjQEJ7A=".to_string(),
api_id: "x02yirxc7a".to_string(),
domain_name: "x02yirxc7a.execute-api.sa-east-1.amazonaws.com".to_string(),
time_epoch: 1631212283738,
time_epoch: 1_631_212_283_738,
http: RequestContextHTTP {
method: "GET".to_string(),
path: "/httpapi/get".to_string(),
Expand Down Expand Up @@ -254,7 +274,8 @@ mod tests {
("endpoint".to_string(), "/httpapi/get".to_string()),
(
"http.url".to_string(),
"x02yirxc7a.execute-api.sa-east-1.amazonaws.com/httpapi/get".to_string()
"https://x02yirxc7a.execute-api.sa-east-1.amazonaws.com/httpapi/get"
.to_string()
),
("http.method".to_string(), "GET".to_string()),
("http.protocol".to_string(), "HTTP/1.1".to_string()),
Expand All @@ -274,35 +295,77 @@ mod tests {
let event =
APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent");
let tags = event.get_tags();
let sorted_tags_array = tags
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect::<Vec<String>>()
.sort();

let expected = HashMap::from([
(
"http.url".to_string(),
"x02yirxc7a.execute-api.sa-east-1.amazonaws.com".to_string(),
"https://x02yirxc7a.execute-api.sa-east-1.amazonaws.com/httpapi/get".to_string(),
),
(
"http_url_details.path".to_string(),
"http.url_details.path".to_string(),
"/httpapi/get".to_string(),
),
("http.method".to_string(), "GET".to_string()),
("http.route".to_string(), "GET /httpapi/get".to_string()),
("http.route".to_string(), "/httpapi/get".to_string()),
("http.user_agent".to_string(), "curl/7.64.1".to_string()),
("http.referer".to_string(), "".to_string()),
]);
let expected_sorted_array = expected
.iter()
.map(|(k, v)| format!("{}:{}", k, v))
.collect::<Vec<String>>()
.sort();

assert_eq!(sorted_tags_array, expected_sorted_array);
assert_eq!(tags, expected);
}

#[test]
fn test_enrich_span_parameterized() {
let json = read_json_file("api_gateway_http_event_parameterized.json");
let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value");
let event =
APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent");
let mut span = Span::default();
event.enrich_span(&mut span);
assert_eq!(span.name, "aws.httpapi");
assert_eq!(
span.service,
"9vj54we5ih.execute-api.sa-east-1.amazonaws.com"
);
assert_eq!(span.resource, "GET /user/{id}");
assert_eq!(span.r#type, "http");
assert_eq!(
span.meta,
HashMap::from([
("endpoint".to_string(), "/user/42".to_string()),
(
"http.url".to_string(),
"https://9vj54we5ih.execute-api.sa-east-1.amazonaws.com/user/42".to_string()
),
("http.method".to_string(), "GET".to_string()),
("http.protocol".to_string(), "HTTP/1.1".to_string()),
("http.source_ip".to_string(), "76.115.124.192".to_string()),
("http.user_agent".to_string(), "curl/8.1.2".to_string()),
("operation_name".to_string(), "aws.httpapi".to_string()),
("request_id".to_string(), "Ur2JtjEfGjQEPOg=".to_string()),
("resource_names".to_string(), "GET /user/{id}".to_string()),
])
);
}

#[test]
fn test_get_tags_parameterized() {
let json = read_json_file("api_gateway_http_event_parameterized.json");
let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value");
let event =
APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent");
let tags = event.get_tags();

let expected = HashMap::from([
(
"http.url".to_string(),
"https://9vj54we5ih.execute-api.sa-east-1.amazonaws.com/user/42".to_string(),
),
("http.url_details.path".to_string(), "/user/42".to_string()),
("http.method".to_string(), "GET".to_string()),
("http.route".to_string(), "/user/{id}".to_string()),
("http.user_agent".to_string(), "curl/8.1.2".to_string()),
]);
assert_eq!(tags, expected);
}
#[test]
fn test_get_arn() {
let json = read_json_file("api_gateway_http_event.json");
Expand Down
Loading