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}