envoy_sdk/extension/access_logger/mod.rs
1// Copyright 2020 Tetrate
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `Envoy` `Access Logger` extension.
16//!
17//! Creating a new `Access Logger` extension using `Envoy SDK` consists of the following steps:
18//!
19//! 1. Implement [`AccessLogger`] trait to define core logic of your extension
20//! 2. [`Register`] your extension on WebAssembly module start up
21//!
22//! # Examples
23//!
24//! #### Basic [`AccessLogger`]:
25//!
26//! ```
27//! # use envoy_sdk as envoy;
28//! use envoy::extension::AccessLogger;
29//!
30//! /// My very own `AccessLogger`.
31//! struct MyAccessLogger;
32//!
33//! impl AccessLogger for MyAccessLogger {
34//! fn name() -> &'static str { "my_access_logger" }
35//! }
36//! ```
37//!
38//! #### Registration of `MyAccessLogger` on start up:
39//!
40//! ```
41//! # use envoy_sdk as envoy;
42//! # use envoy::extension::AccessLogger;
43//! #
44//! # /// My very own `AccessLogger`.
45//! # struct MyAccessLogger;
46//! #
47//! # impl AccessLogger for MyAccessLogger {
48//! # fn name() -> &'static str { "my_access_logger" }
49//! # }
50//! #
51//! use envoy::extension::{entrypoint, Module, Result};
52//!
53//! entrypoint! { initialize } // put initialization logic into a function to make it unit testable
54//!
55//! fn initialize() -> Result<Module> {
56//! Module::new()
57//! .add_access_logger(|_instance_id| Ok(MyAccessLogger))
58//! }
59//! ```
60//!
61//! [`AccessLogger`]: trait.AccessLogger.html
62//! [`Register`]: ../../macro.entrypoint.html
63
64use crate::extension::{ConfigStatus, DrainStatus, Result};
65use crate::host::http::client::{HttpClientRequestHandle, HttpClientResponseOps};
66use crate::host::{self, ByteString, HeaderMap, StreamInfo};
67
68pub(crate) use self::context::AccessLoggerContext;
69
70mod context;
71mod ops;
72
73/// An interface of the `Envoy` `Access Logger` extension.
74///
75/// In contrast to [`HttpFilter`] and [`NetworkFilter`] that only operate on a single
76/// HTTP stream and TCP connection respectively, `Access Logger` operates on multiple
77/// HTTP streams or TCP connections.
78///
79/// # Examples
80///
81/// #### Basic `AccessLogger`:
82///
83/// ```
84/// # use envoy_sdk as envoy;
85/// use envoy::extension::{AccessLogger, Result};
86/// use envoy::extension::access_logger::LogOps;
87/// use envoy::host::{ByteString, log};
88///
89/// /// My very own `AccessLogger`.
90/// struct MyAccessLogger;
91///
92/// impl AccessLogger for MyAccessLogger {
93/// fn name() -> &'static str { "my_access_logger" }
94///
95/// fn on_log(&mut self, ops: &dyn LogOps) -> Result<()> {
96/// let upstream_address = ops.stream_info().upstream().address()?
97/// .unwrap_or_else(|| "<unknown>".into());
98/// log::info!("upstream.address : {}", upstream_address);
99/// Ok(())
100/// }
101/// }
102/// ```
103///
104/// # NOTE
105///
106/// **This trait MUST NOT panic!**
107///
108/// If a logger invocation cannot proceed normally, it should return [`Result::Err(x)`].
109/// In that case, `Envoy SDK` will be able to handle the error gracefully.
110///
111/// For comparison, if the extension chooses to panic, this will, at best, affect all ongoing HTTP requests
112/// / TCP connections handled by that extension, and, at worst, will crash `Envoy` entirely (as of July 2020).
113///
114/// [`HttpFilter`]: ../filter/http/trait.HttpFilter.html
115/// [`NetworkFilter`]: ../filter/network/trait.NetworkFilter.html
116/// [`Result::Err(x)`]: https://doc.rust-lang.org/core/result/enum.Result.html#variant.Err
117pub trait AccessLogger {
118 /// Returns a name the extension should be referred to in `Envoy` configuration.
119 fn name() -> &'static str
120 where
121 Self: Sized;
122
123 /// Called when `Access Logger` is being (re-)configured.
124 ///
125 /// # Arguments
126 ///
127 /// * `_config` - configuration.
128 /// * `_ops` - a [`trait object`][`ConfigureOps`] through which `Access Logger` can access
129 /// its configuration.
130 ///
131 /// # Return value
132 ///
133 /// [`ConfigStatus`] telling `Envoy` whether configuration has been successfully applied.
134 ///
135 /// [`ConfigStatus`]: ../factory/enum.ConfigStatus.html
136 /// [`ConfigureOps`]: trait.ConfigureOps.html
137 fn on_configure(
138 &mut self,
139 _config: ByteString,
140 _ops: &dyn ConfigureOps,
141 ) -> Result<ConfigStatus> {
142 Ok(ConfigStatus::Accepted)
143 }
144
145 /// Called when HTTP request or TCP connection is complete.
146 ///
147 /// # Arguments
148 ///
149 /// * `ops` - a [`trait object`][`LogOps`] through which `Access Logger` can access
150 /// data of the HTTP stream or TCP connection that is being logged.
151 ///
152 /// [`LogOps`]: trait.LogOps.html
153 fn on_log(&mut self, _ops: &dyn LogOps) -> Result<()> {
154 Ok(())
155 }
156
157 /// Called when `Access Logger` is about to be destroyed.
158 ///
159 /// # Return value
160 ///
161 /// [`DrainStatus`] telling `Envoy` whether `Access Logger` has already been drained
162 /// and can be now removed safely.
163 ///
164 /// [`DrainStatus`]: ../factory/enum.DrainStatus.html
165 fn on_drain(&mut self) -> Result<DrainStatus> {
166 Ok(DrainStatus::Complete)
167 }
168
169 // Http Client callbacks
170
171 /// Called when the async HTTP request made through [`Envoy HTTP Client API`][`HttpClient`] is complete.
172 ///
173 /// # Arguments
174 ///
175 /// * `request_id` - opaque identifier of the request that is now complete.
176 /// * `num_headers` - number of headers in the response.
177 /// * `body_size` - size of the response body.
178 /// * `num_trailers` - number of tarilers in the response.
179 /// * `http_client_ops` - a [`trait object`][`HttpClientResponseOps`] through which `Access Logger` can access
180 /// data of the response received by [`HttpClient`], including headers, body and trailers.
181 ///
182 /// [`HttpClient`]: ../../host/http/client/trait.HttpClient.html
183 /// [`HttpClientResponseOps`]: ../../host/http/client/trait.HttpClientResponseOps.html
184 /// [`Ops`]: trait.Ops.html
185 fn on_http_call_response(
186 &mut self,
187 _request_id: HttpClientRequestHandle,
188 _num_headers: usize,
189 _body_size: usize,
190 _num_trailers: usize,
191 _http_client_ops: &dyn HttpClientResponseOps,
192 ) -> Result<()> {
193 Ok(())
194 }
195}
196
197/// An interface for accessing extension config.
198pub(crate) trait ContextOps {
199 /// Returns extension config.
200 fn configuration(&self, start: usize, max_size: usize) -> host::Result<ByteString>;
201}
202
203impl dyn ContextOps {
204 /// Returns the default implementation that interacts with `Envoy`
205 /// through its [`ABI`].
206 ///
207 /// [`ABI`]: https://github.com/proxy-wasm/spec
208 pub fn default() -> &'static dyn ContextOps {
209 &ops::Host
210 }
211}
212
213/// An interface for operations available in the context of [`on_configure`]
214/// invocation.
215///
216/// [`on_configure`]: trait.AccessLogger.html#method.on_configure
217pub trait ConfigureOps {}
218
219/// An interface for acknowledging `Envoy` that `AccessLogger` has been drained.
220///
221/// [`AccessLogger`]: trait.AccessLogger.html
222pub trait DrainOps {
223 /// Acknowledges `Envoy` that extension has been drained and can be safely removed now.
224 fn done(&self) -> host::Result<()>;
225}
226
227/// An interface for accessing data of the HTTP stream or TCP connection that is being logged.
228pub trait LogOps {
229 /// Returns request headers.
230 fn request_headers(&self) -> host::Result<HeaderMap>;
231
232 /// Returns request header by name.
233 fn request_header(&self, name: &str) -> host::Result<Option<ByteString>>;
234
235 /// Returns response headers.
236 fn response_headers(&self) -> host::Result<HeaderMap>;
237
238 /// Returns response header by name.
239 fn response_header(&self, name: &str) -> host::Result<Option<ByteString>>;
240
241 /// Returns response trailers.
242 fn response_trailers(&self) -> host::Result<HeaderMap>;
243
244 /// Returns response trailer by name.
245 fn response_trailer(&self, name: &str) -> host::Result<Option<ByteString>>;
246
247 /// Provides access to properties of the stream.
248 fn stream_info(&self) -> &dyn StreamInfo;
249}
250
251#[doc(hidden)]
252pub trait Ops: ConfigureOps + LogOps {
253 fn as_configure_ops(&self) -> &dyn ConfigureOps;
254
255 fn as_log_ops(&self) -> &dyn LogOps;
256}
257
258impl<T> Ops for T
259where
260 T: ConfigureOps + LogOps,
261{
262 fn as_configure_ops(&self) -> &dyn ConfigureOps {
263 self
264 }
265
266 fn as_log_ops(&self) -> &dyn LogOps {
267 self
268 }
269}
270
271impl dyn Ops {
272 /// Returns the default implementation that interacts with `Envoy`
273 /// through its [`ABI`].
274 ///
275 /// [`ABI`]: https://github.com/proxy-wasm/spec
276 pub fn default() -> &'static dyn Ops {
277 &ops::Host
278 }
279}