Skip to content

Commit 95687e1

Browse files
committed
Add access logging as requested in linkerd/linkerd2#1913
Signed-off-by: Raphael Taylor-Davies <r.taylordavies@googlemail.com>
1 parent df5a4fb commit 95687e1

File tree

12 files changed

+353
-24
lines changed

12 files changed

+353
-24
lines changed

Cargo.lock

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

196196
[[package]]
197197
name = "chrono"
198-
version = "0.4.10"
198+
version = "0.4.15"
199199
source = "registry+https://github.com/rust-lang/crates.io-index"
200-
checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
200+
checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
201201
dependencies = [
202202
"num-integer",
203203
"num-traits 0.2.6",
@@ -231,6 +231,16 @@ dependencies = [
231231
"build_const",
232232
]
233233

234+
[[package]]
235+
name = "crossbeam-channel"
236+
version = "0.4.4"
237+
source = "registry+https://github.com/rust-lang/crates.io-index"
238+
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
239+
dependencies = [
240+
"crossbeam-utils",
241+
"maybe-uninit",
242+
]
243+
234244
[[package]]
235245
name = "crossbeam-utils"
236246
version = "0.7.2"
@@ -456,7 +466,7 @@ checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
456466
dependencies = [
457467
"cfg-if",
458468
"libc",
459-
"wasi",
469+
"wasi 0.7.0",
460470
]
461471

462472
[[package]]
@@ -742,6 +752,20 @@ version = "0.5.3"
742752
source = "registry+https://github.com/rust-lang/crates.io-index"
743753
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
744754

755+
[[package]]
756+
name = "linkerd2-access-log"
757+
version = "0.1.0"
758+
dependencies = [
759+
"base64 0.10.1",
760+
"chrono",
761+
"futures 0.3.5",
762+
"http 0.2.1",
763+
"pin-project",
764+
"tower",
765+
"tracing",
766+
"tracing-subscriber",
767+
]
768+
745769
[[package]]
746770
name = "linkerd2-addr"
747771
version = "0.1.0"
@@ -798,6 +822,7 @@ dependencies = [
798822
"indexmap",
799823
"ipnet 1.0.0",
800824
"libc",
825+
"linkerd2-access-log",
801826
"linkerd2-addr",
802827
"linkerd2-buffer",
803828
"linkerd2-cache",
@@ -848,6 +873,7 @@ dependencies = [
848873
"tower",
849874
"tower-request-modifier",
850875
"tracing",
876+
"tracing-appender",
851877
"tracing-futures",
852878
"tracing-log",
853879
"tracing-subscriber",
@@ -1597,6 +1623,12 @@ version = "0.1.6"
15971623
source = "registry+https://github.com/rust-lang/crates.io-index"
15981624
checksum = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
15991625

1626+
[[package]]
1627+
name = "maybe-uninit"
1628+
version = "2.0.0"
1629+
source = "registry+https://github.com/rust-lang/crates.io-index"
1630+
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
1631+
16001632
[[package]]
16011633
name = "memchr"
16021634
version = "0.1.11"
@@ -2500,12 +2532,12 @@ dependencies = [
25002532

25012533
[[package]]
25022534
name = "time"
2503-
version = "0.1.39"
2535+
version = "0.1.44"
25042536
source = "registry+https://github.com/rust-lang/crates.io-index"
2505-
checksum = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
2537+
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
25062538
dependencies = [
25072539
"libc",
2508-
"redox_syscall",
2540+
"wasi 0.10.0+wasi-snapshot-preview1",
25092541
"winapi 0.3.8",
25102542
]
25112543

@@ -2753,6 +2785,17 @@ dependencies = [
27532785
"tracing-core",
27542786
]
27552787

2788+
[[package]]
2789+
name = "tracing-appender"
2790+
version = "0.1.1"
2791+
source = "registry+https://github.com/rust-lang/crates.io-index"
2792+
checksum = "7aa52d56cc0d79ab604e8a022a1cebc4de33cf09dc9933c94353bea2e00d6e88"
2793+
dependencies = [
2794+
"chrono",
2795+
"crossbeam-channel",
2796+
"tracing-subscriber",
2797+
]
2798+
27562799
[[package]]
27572800
name = "tracing-attributes"
27582801
version = "0.1.10"
@@ -2766,9 +2809,9 @@ dependencies = [
27662809

27672810
[[package]]
27682811
name = "tracing-core"
2769-
version = "0.1.14"
2812+
version = "0.1.16"
27702813
source = "registry+https://github.com/rust-lang/crates.io-index"
2771-
checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545"
2814+
checksum = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a"
27722815
dependencies = [
27732816
"lazy_static",
27742817
]
@@ -2796,19 +2839,19 @@ dependencies = [
27962839

27972840
[[package]]
27982841
name = "tracing-serde"
2799-
version = "0.1.1"
2842+
version = "0.1.2"
28002843
source = "registry+https://github.com/rust-lang/crates.io-index"
2801-
checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79"
2844+
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
28022845
dependencies = [
28032846
"serde",
28042847
"tracing-core",
28052848
]
28062849

28072850
[[package]]
28082851
name = "tracing-subscriber"
2809-
version = "0.2.11"
2852+
version = "0.2.12"
28102853
source = "registry+https://github.com/rust-lang/crates.io-index"
2811-
checksum = "abd165311cc4d7a555ad11cc77a37756df836182db0d81aac908c8184c584f40"
2854+
checksum = "82bb5079aa76438620837198db8a5c529fb9878c730bc2b28179b0241cf04c10"
28122855
dependencies = [
28132856
"ansi_term",
28142857
"chrono",
@@ -2962,6 +3005,12 @@ version = "0.7.0"
29623005
source = "registry+https://github.com/rust-lang/crates.io-index"
29633006
checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
29643007

3008+
[[package]]
3009+
name = "wasi"
3010+
version = "0.10.0+wasi-snapshot-preview1"
3011+
source = "registry+https://github.com/rust-lang/crates.io-index"
3012+
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
3013+
29653014
[[package]]
29663015
name = "wasm-bindgen"
29673016
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/app/core",
67
"linkerd/app/gateway",

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 <cncf-linkerd-dev@lists.cncf.io>"]
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)