-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add access logging as requested in linkerd/linkerd2#1913
Will write space-delimited access logs to a file specified by the LINKERD2_PROXY_ACCESS_LOG_FILE environment variable in a best-effort fashion Signed-off-by: Raphael Taylor-Davies <[email protected]>
- Loading branch information
Showing
13 changed files
with
498 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "linkerd2-access-log" | ||
version = "0.1.0" | ||
authors = ["Linkerd Developers <[email protected]>"] | ||
edition = "2018" | ||
publish = false | ||
|
||
[dependencies] | ||
base64 = "0.10.1" | ||
bytes = "0.5" | ||
futures = "0.3" | ||
hex = "0.3.2" | ||
http = "0.2" | ||
linkerd2-error = { path = "../error" } | ||
tower = { version = "0.3", default-features = false } | ||
tracing = "0.1.2" | ||
tokio = {version = "0.2", features = ["sync"]} | ||
pin-project = "0.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use crate::AccessLog; | ||
use futures::{ready, TryFuture}; | ||
use pin_project::pin_project; | ||
use std::future::Future; | ||
use std::pin::Pin; | ||
use std::task::{Context, Poll}; | ||
use std::time::SystemTime; | ||
use tokio::sync::mpsc; | ||
use tracing::warn; | ||
|
||
/// A layer that adds access logging | ||
#[derive(Clone)] | ||
pub struct AccessLogLayer { | ||
sink: Option<mpsc::Sender<AccessLog>>, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct AccessLogContext<Svc> { | ||
inner: Svc, | ||
sink: Option<mpsc::Sender<AccessLog>>, | ||
} | ||
|
||
#[pin_project] | ||
pub struct ResponseFuture<F> { | ||
state: Option<(AccessLog, mpsc::Sender<AccessLog>)>, | ||
|
||
#[pin] | ||
inner: F, | ||
} | ||
|
||
impl<Svc> tower::layer::Layer<Svc> for AccessLogLayer { | ||
type Service = AccessLogContext<Svc>; | ||
|
||
fn layer(&self, inner: Svc) -> Self::Service { | ||
Self::Service { | ||
inner, | ||
sink: self.sink.clone(), | ||
} | ||
} | ||
} | ||
|
||
impl<Svc, B1, B2> tower::Service<http::Request<B1>> for AccessLogContext<Svc> | ||
where | ||
Svc: tower::Service<http::Request<B1>, Response = http::Response<B2>>, | ||
{ | ||
type Response = Svc::Response; | ||
type Error = Svc::Error; | ||
type Future = ResponseFuture<Svc::Future>; | ||
|
||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Svc::Error>> { | ||
self.inner.poll_ready(cx) | ||
} | ||
|
||
fn call(&mut self, request: http::Request<B1>) -> Self::Future { | ||
let sink = match &self.sink { | ||
Some(sink) => sink, | ||
None => { | ||
return ResponseFuture { | ||
state: None, | ||
inner: self.inner.call(request), | ||
} | ||
} | ||
}; | ||
|
||
let t0 = SystemTime::now(); | ||
|
||
let host = request.headers().get("Host").map(|x| x.clone()); | ||
|
||
let trace_id = request | ||
.headers() | ||
.get("X-Amzn-Trace-Id") | ||
.or_else(|| request.headers().get("X-Request-ID")) | ||
.map(|x| x.clone()); | ||
|
||
let user_agent = request.headers().get("User-Agent").map(|x| x.clone()); | ||
|
||
let log = AccessLog { | ||
uri: request.uri().clone(), | ||
method: request.method().clone(), | ||
status: Default::default(), | ||
host, | ||
user_agent, | ||
trace_id, | ||
start_time: t0, | ||
end_time: t0, | ||
}; | ||
|
||
ResponseFuture { | ||
state: Some((log, sink.clone())), | ||
inner: self.inner.call(request), | ||
} | ||
} | ||
} | ||
|
||
impl AccessLogLayer { | ||
pub fn new(sink: Option<mpsc::Sender<AccessLog>>) -> Self { | ||
Self { sink } | ||
} | ||
} | ||
|
||
impl<F, B2> Future for ResponseFuture<F> | ||
where | ||
F: TryFuture<Ok = http::Response<B2>>, | ||
{ | ||
type Output = Result<F::Ok, F::Error>; | ||
|
||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||
let this = self.project(); | ||
let response: http::Response<B2> = ready!(this.inner.try_poll(cx))?; | ||
|
||
if let Some((mut log, mut sink)) = this.state.take() { | ||
log.end_time = SystemTime::now(); | ||
log.status = response.status().clone(); | ||
|
||
if let Err(error) = sink.try_send(log) { | ||
warn!(message = "access log dropped", %error); | ||
} | ||
} | ||
|
||
Poll::Ready(Ok(response)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#![deny(warnings, rust_2018_idioms)] | ||
|
||
use linkerd2_error::Error; | ||
use tokio::sync::mpsc; | ||
|
||
pub mod layer; | ||
|
||
use http::{HeaderValue, Method, StatusCode, Uri}; | ||
pub use layer::{AccessLogContext, AccessLogLayer}; | ||
use std::time::SystemTime; | ||
|
||
#[derive(Debug)] | ||
pub struct AccessLog { | ||
pub uri: Uri, | ||
pub method: Method, | ||
pub status: StatusCode, | ||
pub host: Option<HeaderValue>, | ||
pub user_agent: Option<HeaderValue>, | ||
pub trace_id: Option<HeaderValue>, | ||
pub start_time: SystemTime, | ||
pub end_time: SystemTime, | ||
} | ||
|
||
pub trait AccessLogSink { | ||
fn try_send(&mut self, log: AccessLog) -> Result<(), Error>; | ||
} | ||
|
||
impl AccessLogSink for mpsc::Sender<AccessLog> { | ||
fn try_send(&mut self, span: AccessLog) -> Result<(), Error> { | ||
self.try_send(span).map_err(Into::into) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.