Skip to content

Commit 6b1120d

Browse files
committed
Add access logging as requested in linkerd/linkerd2#1913
Signed-off-by: Raphael Taylor-Davies <[email protected]>
1 parent 7cda303 commit 6b1120d

File tree

12 files changed

+358
-27
lines changed

12 files changed

+358
-27
lines changed

Cargo.lock

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
189189

190190
[[package]]
191191
name = "chrono"
192-
version = "0.4.10"
192+
version = "0.4.15"
193193
source = "registry+https://github.com/rust-lang/crates.io-index"
194-
checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
194+
checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
195195
dependencies = [
196196
"num-integer",
197197
"num-traits 0.2.6",
@@ -225,6 +225,16 @@ dependencies = [
225225
"build_const",
226226
]
227227

228+
[[package]]
229+
name = "crossbeam-channel"
230+
version = "0.4.4"
231+
source = "registry+https://github.com/rust-lang/crates.io-index"
232+
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
233+
dependencies = [
234+
"crossbeam-utils",
235+
"maybe-uninit",
236+
]
237+
228238
[[package]]
229239
name = "crossbeam-utils"
230240
version = "0.7.2"
@@ -450,7 +460,7 @@ checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
450460
dependencies = [
451461
"cfg-if",
452462
"libc",
453-
"wasi",
463+
"wasi 0.7.0",
454464
]
455465

456466
[[package]]
@@ -736,6 +746,20 @@ version = "0.5.3"
736746
source = "registry+https://github.com/rust-lang/crates.io-index"
737747
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
738748

749+
[[package]]
750+
name = "linkerd2-access-log"
751+
version = "0.1.0"
752+
dependencies = [
753+
"base64 0.10.1",
754+
"chrono",
755+
"futures 0.3.5",
756+
"http 0.2.1",
757+
"pin-project",
758+
"tower",
759+
"tracing",
760+
"tracing-subscriber",
761+
]
762+
739763
[[package]]
740764
name = "linkerd2-addr"
741765
version = "0.1.0"
@@ -803,6 +827,7 @@ dependencies = [
803827
"hyper",
804828
"indexmap",
805829
"libc",
830+
"linkerd2-access-log",
806831
"linkerd2-addr",
807832
"linkerd2-admit",
808833
"linkerd2-buffer",
@@ -856,6 +881,7 @@ dependencies = [
856881
"tower",
857882
"tower-request-modifier",
858883
"tracing",
884+
"tracing-appender",
859885
"tracing-futures",
860886
"tracing-log",
861887
"tracing-subscriber",
@@ -1628,6 +1654,12 @@ version = "0.1.6"
16281654
source = "registry+https://github.com/rust-lang/crates.io-index"
16291655
checksum = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
16301656

1657+
[[package]]
1658+
name = "maybe-uninit"
1659+
version = "2.0.0"
1660+
source = "registry+https://github.com/rust-lang/crates.io-index"
1661+
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
1662+
16311663
[[package]]
16321664
name = "memchr"
16331665
version = "0.1.11"
@@ -2531,12 +2563,12 @@ dependencies = [
25312563

25322564
[[package]]
25332565
name = "time"
2534-
version = "0.1.39"
2566+
version = "0.1.44"
25352567
source = "registry+https://github.com/rust-lang/crates.io-index"
2536-
checksum = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
2568+
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
25372569
dependencies = [
25382570
"libc",
2539-
"redox_syscall",
2571+
"wasi 0.10.0+wasi-snapshot-preview1",
25402572
"winapi 0.3.8",
25412573
]
25422574

@@ -2794,6 +2826,17 @@ dependencies = [
27942826
"tracing-core",
27952827
]
27962828

2829+
[[package]]
2830+
name = "tracing-appender"
2831+
version = "0.1.1"
2832+
source = "registry+https://github.com/rust-lang/crates.io-index"
2833+
checksum = "7aa52d56cc0d79ab604e8a022a1cebc4de33cf09dc9933c94353bea2e00d6e88"
2834+
dependencies = [
2835+
"chrono",
2836+
"crossbeam-channel",
2837+
"tracing-subscriber",
2838+
]
2839+
27972840
[[package]]
27982841
name = "tracing-attributes"
27992842
version = "0.1.10"
@@ -2807,9 +2850,9 @@ dependencies = [
28072850

28082851
[[package]]
28092852
name = "tracing-core"
2810-
version = "0.1.14"
2853+
version = "0.1.16"
28112854
source = "registry+https://github.com/rust-lang/crates.io-index"
2812-
checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545"
2855+
checksum = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a"
28132856
dependencies = [
28142857
"lazy_static",
28152858
]
@@ -2837,19 +2880,19 @@ dependencies = [
28372880

28382881
[[package]]
28392882
name = "tracing-serde"
2840-
version = "0.1.1"
2883+
version = "0.1.2"
28412884
source = "registry+https://github.com/rust-lang/crates.io-index"
2842-
checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79"
2885+
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
28432886
dependencies = [
28442887
"serde",
28452888
"tracing-core",
28462889
]
28472890

28482891
[[package]]
28492892
name = "tracing-subscriber"
2850-
version = "0.2.11"
2893+
version = "0.2.12"
28512894
source = "registry+https://github.com/rust-lang/crates.io-index"
2852-
checksum = "abd165311cc4d7a555ad11cc77a37756df836182db0d81aac908c8184c584f40"
2895+
checksum = "82bb5079aa76438620837198db8a5c529fb9878c730bc2b28179b0241cf04c10"
28532896
dependencies = [
28542897
"ansi_term",
28552898
"chrono",
@@ -3003,6 +3046,12 @@ version = "0.7.0"
30033046
source = "registry+https://github.com/rust-lang/crates.io-index"
30043047
checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
30053048

3049+
[[package]]
3050+
name = "wasi"
3051+
version = "0.10.0+wasi-snapshot-preview1"
3052+
source = "registry+https://github.com/rust-lang/crates.io-index"
3053+
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
3054+
30063055
[[package]]
30073056
name = "wasm-bindgen"
30083057
version = "0.2.50"

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
members = [
33
"hyper-balance",
4+
"linkerd/access-log",
45
"linkerd/addr",
56
"linkerd/admit",
67
"linkerd/app/core",

linkerd/access-log/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "linkerd2-access-log"
3+
version = "0.1.0"
4+
authors = ["Linkerd Developers <[email protected]>"]
5+
edition = "2018"
6+
publish = false
7+
8+
[dependencies]
9+
base64 = "0.10.1"
10+
chrono = "0.4.15"
11+
futures = "0.3"
12+
http = "0.2"
13+
tower = { version = "0.3", default-features = false }
14+
tracing = "0.1.19"
15+
tracing-subscriber = "0.2.12"
16+
pin-project = "0.4"

linkerd/access-log/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#![deny(warnings, rust_2018_idioms)]
2+
3+
pub mod tower;
4+
pub mod tracing;

linkerd/access-log/src/tower.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use futures::TryFuture;
2+
use pin_project::pin_project;
3+
use std::future::Future;
4+
use std::pin::Pin;
5+
use std::task::{Context, Poll};
6+
use std::time::{Duration, Instant};
7+
use tracing::{field, span, Level, Span};
8+
9+
/// A tower layer that associates a tokio-tracing Span with each request
10+
#[derive(Clone)]
11+
pub struct AccessLogLayer {}
12+
13+
#[derive(Clone)]
14+
pub struct AccessLogContext<Svc> {
15+
inner: Svc,
16+
}
17+
18+
struct ResponseFutureInner {
19+
span: Span,
20+
start: Instant,
21+
processing: Duration,
22+
}
23+
24+
#[pin_project]
25+
pub struct AccessLogFuture<F> {
26+
data: Option<ResponseFutureInner>,
27+
28+
#[pin]
29+
inner: F,
30+
}
31+
32+
impl<Svc> tower::layer::Layer<Svc> for AccessLogLayer {
33+
type Service = AccessLogContext<Svc>;
34+
35+
fn layer(&self, inner: Svc) -> Self::Service {
36+
Self::Service { inner }
37+
}
38+
}
39+
40+
impl<Svc, B1, B2> tower::Service<http::Request<B1>> for AccessLogContext<Svc>
41+
where
42+
Svc: tower::Service<http::Request<B1>, Response = http::Response<B2>>,
43+
{
44+
type Response = Svc::Response;
45+
type Error = Svc::Error;
46+
type Future = AccessLogFuture<Svc::Future>;
47+
48+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Svc::Error>> {
49+
self.inner.poll_ready(cx)
50+
}
51+
52+
fn call(&mut self, request: http::Request<B1>) -> Self::Future {
53+
let span: Span = span!(target: "access_log", Level::TRACE, "http",
54+
timestamp=field::Empty, processing_ns=field::Empty, total_ns=field::Empty,
55+
method=field::Empty, uri=field::Empty, version=field::Empty, user_agent=field::Empty,
56+
host=field::Empty, trace_id=field::Empty, status=field::Empty,
57+
request_bytes=field::Empty, response_bytes=field::Empty);
58+
59+
if span.is_disabled() {
60+
return AccessLogFuture {
61+
data: None,
62+
inner: self.inner.call(request),
63+
};
64+
}
65+
66+
// Delay formatting to avoid an intermediate `String`
67+
let delayed_format = chrono::Utc::now().format_with_items(
68+
[chrono::format::Item::Fixed(chrono::format::Fixed::RFC3339)].iter(),
69+
);
70+
71+
span.record("timestamp", &field::display(&delayed_format));
72+
span.record("method", &request.method().as_str());
73+
span.record("uri", &field::display(&request.uri()));
74+
span.record("version", &field::debug(&request.version()));
75+
76+
request
77+
.headers()
78+
.get("Host")
79+
.and_then(|x| x.to_str().ok())
80+
.map(|x| span.record("host", &x));
81+
82+
request
83+
.headers()
84+
.get("User-Agent")
85+
.and_then(|x| x.to_str().ok())
86+
.map(|x| span.record("user_agent", &x));
87+
88+
request
89+
.headers()
90+
.get("Content-Length")
91+
.and_then(|x| x.to_str().ok())
92+
.map(|x| span.record("request_bytes", &x));
93+
94+
request
95+
.headers()
96+
.get("x-b3-traceid")
97+
.or_else(|| request.headers().get("X-Request-ID"))
98+
.or_else(|| request.headers().get("X-Amzn-Trace-Id"))
99+
.and_then(|x| x.to_str().ok())
100+
.map(|x| span.record("trace_id", &x));
101+
102+
AccessLogFuture {
103+
data: Some(ResponseFutureInner {
104+
span,
105+
start: Instant::now(),
106+
processing: Duration::from_secs(0),
107+
}),
108+
inner: self.inner.call(request),
109+
}
110+
}
111+
}
112+
113+
impl AccessLogLayer {
114+
pub fn new() -> Self {
115+
Self {}
116+
}
117+
}
118+
119+
impl<F, B2> Future for AccessLogFuture<F>
120+
where
121+
F: TryFuture<Ok = http::Response<B2>>,
122+
{
123+
type Output = Result<F::Ok, F::Error>;
124+
125+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
126+
let mut this = self.project();
127+
128+
let data: &mut ResponseFutureInner = match &mut this.data {
129+
Some(data) => data,
130+
None => return this.inner.try_poll(cx),
131+
};
132+
133+
let _enter = data.span.enter();
134+
let poll_start = Instant::now();
135+
136+
let response: http::Response<B2> = match this.inner.try_poll(cx) {
137+
Poll::Pending => {
138+
data.processing += Instant::now().duration_since(poll_start);
139+
return Poll::Pending;
140+
}
141+
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
142+
Poll::Ready(Ok(response)) => response,
143+
};
144+
145+
let now = Instant::now();
146+
let total_ns = now.duration_since(data.start).as_nanos();
147+
let processing_ns = (now.duration_since(poll_start) + data.processing).as_nanos();
148+
149+
let span = &data.span;
150+
151+
response
152+
.headers()
153+
.get("Content-Length")
154+
.and_then(|x| x.to_str().ok())
155+
.map(|x| span.record("response_bytes", &x));
156+
157+
span.record("status", &response.status().as_u16());
158+
span.record("total_ns", &field::display(total_ns));
159+
span.record("processing_ns", &field::display(processing_ns));
160+
161+
Poll::Ready(Ok(response))
162+
}
163+
}

0 commit comments

Comments
 (0)