Skip to content

Commit

Permalink
formatter: add templated formatter and specialization for HTTP (envoy…
Browse files Browse the repository at this point in the history
…proxy#29201)

Commit Message: formatter: add templated formatter and specialization for HTTP
Additional Description:

Second sub PR that splited out from envoyproxy#29021

This PR make most important class be templated and implement specialization for the HTTP to aovid unnecessary code change.

Waiting envoyproxy#29183 to be merged first. Done.

Risk Level: low.
Testing: n/a.
Docs Changes: n/a.
Release Notes: n/a.
Platform Specific Features: n/a.

Signed-off-by: wbpcode <[email protected]>
  • Loading branch information
code authored Aug 25, 2023
1 parent 8bdc349 commit 8c3f3bf
Show file tree
Hide file tree
Showing 15 changed files with 949 additions and 678 deletions.
5 changes: 4 additions & 1 deletion envoy/formatter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ envoy_package()

envoy_cc_library(
name = "substitution_formatter_interface",
hdrs = ["substitution_formatter.h"],
hdrs = [
"substitution_formatter.h",
"substitution_formatter_base.h",
],
deps = [
"//envoy/access_log:access_log_interface",
"//envoy/config:typed_config_interface",
Expand Down
186 changes: 145 additions & 41 deletions envoy/formatter/substitution_formatter.h
Original file line number Diff line number Diff line change
@@ -1,31 +1,124 @@
#pragma once

#include <memory>
#include <string>

#include "envoy/access_log/access_log.h"
#include "envoy/common/pure.h"
#include "envoy/config/typed_config.h"
#include "envoy/formatter/substitution_formatter_base.h"
#include "envoy/http/header_map.h"
#include "envoy/server/factory_context.h"
#include "envoy/stream_info/stream_info.h"

namespace Envoy {
namespace Formatter {

/**
* HTTP specific substitution formatter context for HTTP access logs or formatters.
*/
class HttpFormatterContext {
public:
/**
* Constructor that uses the provided request/response headers, response trailers, local reply
* body, and access log type. Any of the parameters can be nullptr/empty.
*
* @param request_headers supplies the request headers.
* @param response_headers supplies the response headers.
* @param response_trailers supplies the response trailers.
* @param local_reply_body supplies the local reply body.
* @param log_type supplies the access log type.
*/
HttpFormatterContext(const Http::RequestHeaderMap* request_headers = nullptr,
const Http::ResponseHeaderMap* response_headers = nullptr,
const Http::ResponseTrailerMap* response_trailers = nullptr,
absl::string_view local_reply_body = {},
AccessLog::AccessLogType log_type = AccessLog::AccessLogType::NotSet);
/**
* Set or overwrite the request headers.
* @param request_headers supplies the request headers.
*/
HttpFormatterContext& setRequestHeaders(const Http::RequestHeaderMap& request_headers) {
request_headers_ = &request_headers;
return *this;
}
/**
* Set or overwrite the response headers.
* @param response_headers supplies the response headers.
*/
HttpFormatterContext& setResponseHeaders(const Http::ResponseHeaderMap& response_headers) {
response_headers_ = &response_headers;
return *this;
}

/**
* Set or overwrite the response trailers.
* @param response_trailers supplies the response trailers.
*/
HttpFormatterContext& setResponseTrailers(const Http::ResponseTrailerMap& response_trailers) {
response_trailers_ = &response_trailers;
return *this;
}

/**
* Set or overwrite the local reply body.
* @param local_reply_body supplies the local reply body.
*/
HttpFormatterContext& setLocalReplyBody(absl::string_view local_reply_body) {
local_reply_body_ = local_reply_body;
return *this;
}

/**
* Set or overwrite the access log type.
* @param log_type supplies the access log type.
*/
HttpFormatterContext& setAccessLogType(AccessLog::AccessLogType log_type) {
log_type_ = log_type;
return *this;
}

/**
* @return const Http::RequestHeaderMap& the request headers. Empty request header map if no
* request headers are available.
*/
const Http::RequestHeaderMap& requestHeaders() const;

/**
* @return const Http::ResponseHeaderMap& the response headers. Empty respnose header map if
* no response headers are available.
*/
const Http::ResponseHeaderMap& responseHeaders() const;

/**
* @return const Http::ResponseTrailerMap& the response trailers. Empty response trailer map
* if no response trailers are available.
*/
const Http::ResponseTrailerMap& responseTrailers() const;

/**
* @return absl::string_view the local reply body. Empty if no local reply body.
*/
absl::string_view localReplyBody() const;

/**
* @return AccessLog::AccessLogType the type of access log. NotSet if this is not used for
* access logging.
*/
AccessLog::AccessLogType accessLogType() const;

private:
const Http::RequestHeaderMap* request_headers_{};
const Http::ResponseHeaderMap* response_headers_{};
const Http::ResponseTrailerMap* response_trailers_{};
absl::string_view local_reply_body_{};
AccessLog::AccessLogType log_type_{AccessLog::AccessLogType::NotSet};
};

/**
* Interface for substitution formatter.
* Formatters provide a complete substitution output line for the given headers/trailers/stream.
* This is specilization of FormatterBase for HTTP and backwards compatibliity.
*/
class Formatter {
template <> class FormatterBase<HttpFormatterContext> {
public:
virtual ~Formatter() = default;
virtual ~FormatterBase() = default;

/**
* Return a formatted substitution line.
* @param request_headers supplies the request headers.
* @param response_headers supplies the response headers.
* @param response_trailers supplies the response trailers.
* @param context supplies the formatter context.
* @param stream_info supplies the stream info.
* @param local_reply_body supplies the local reply body.
* @return std::string string containing the complete formatted substitution line.
Expand All @@ -36,18 +129,32 @@ class Formatter {
const StreamInfo::StreamInfo& stream_info,
absl::string_view local_reply_body,
AccessLog::AccessLogType access_log_type) const PURE;

/**
* Return a formatted substitution line.
* @param context supplies the formatter context.
* @param stream_info supplies the stream info.
* @return std::string string containing the complete formatted substitution line.
*/
virtual std::string formatWithContext(const HttpFormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const {
return format(context.requestHeaders(), context.responseHeaders(), context.responseTrailers(),
stream_info, context.localReplyBody(), context.accessLogType());
}
};

using Formatter = FormatterBase<HttpFormatterContext>;
using FormatterPtr = std::unique_ptr<Formatter>;
using FormatterConstSharedPtr = std::shared_ptr<const Formatter>;

/**
* Interface for substitution provider.
* FormatterProviders extract information from the given headers/trailers/stream.
* This is specilization of FormatterBase for HTTP and backwards compatibliity.
*/
class FormatterProvider {
template <> class FormatterProviderBase<HttpFormatterContext> {
public:
virtual ~FormatterProvider() = default;
virtual ~FormatterProviderBase() = default;

/**
* Extract a value from the provided headers/trailers/stream.
Expand Down Expand Up @@ -81,43 +188,38 @@ class FormatterProvider {
const StreamInfo::StreamInfo& stream_info,
absl::string_view local_reply_body,
AccessLog::AccessLogType access_log_type) const PURE;

// TODO(wbpcode): this two methods are used to bridge the old and new formatter interface.
// We can defer the code change in the old formatter interface to future by this way.
virtual absl::optional<std::string>
formatWithContext(const HttpFormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const {
return format(context.requestHeaders(), context.responseHeaders(), context.responseTrailers(),
stream_info, context.localReplyBody(), context.accessLogType());
}
virtual ProtobufWkt::Value
formatValueWithContext(const HttpFormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const {
return formatValue(context.requestHeaders(), context.responseHeaders(),
context.responseTrailers(), stream_info, context.localReplyBody(),
context.accessLogType());
}
};

using FormatterProvider = FormatterProviderBase<HttpFormatterContext>;
using FormatterProviderPtr = std::unique_ptr<FormatterProvider>;

/**
* Interface for command parser.
* CommandParser returns a FormatterProviderPtr after successfully parsing an access log format
* token, nullptr otherwise.
*/
class CommandParser {
public:
virtual ~CommandParser() = default;

/**
* Return a FormatterProviderPtr if subcommand and max_length
* are correct for the formatter provider associated
* with command.
* @param command - name of the FormatterProvider
* @param subcommand - command specific data. (optional)
* @param max_length - length to which the output produced by FormatterProvider
* should be truncated to (optional)
*
* @return FormattterProviderPtr substitution provider for the parsed command
*/
virtual FormatterProviderPtr parse(const std::string& command, const std::string& subcommand,
absl::optional<size_t>& max_length) const PURE;
};

using CommandParser = CommandParserBase<HttpFormatterContext>;
using CommandParserPtr = std::unique_ptr<CommandParser>;

/**
* Implemented by each custom CommandParser and registered via Registry::registerFactory()
* or the convenience class RegisterFactory.
* Specialization of CommandParserFactoryBase for HTTP and backwards compatibliity.
*/
class CommandParserFactory : public Config::TypedFactory {
template <> class CommandParserFactoryBase<HttpFormatterContext> : public Config::TypedFactory {
public:
~CommandParserFactory() override = default;
~CommandParserFactoryBase() override = default;

/**
* Creates a particular CommandParser implementation.
Expand All @@ -134,5 +236,7 @@ class CommandParserFactory : public Config::TypedFactory {
std::string category() const override { return "envoy.formatter"; }
};

using CommandParserFactory = CommandParserFactoryBase<HttpFormatterContext>;

} // namespace Formatter
} // namespace Envoy
139 changes: 139 additions & 0 deletions envoy/formatter/substitution_formatter_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#pragma once

#include <memory>
#include <string>

#include "envoy/access_log/access_log.h"
#include "envoy/common/pure.h"
#include "envoy/config/typed_config.h"
#include "envoy/server/factory_context.h"
#include "envoy/stream_info/stream_info.h"

namespace Envoy {
namespace Formatter {

/**
* Template interface for multiple protocols/modules formatters.
*/
template <class FormatterContext> class FormatterBase {
public:
virtual ~FormatterBase() = default;

/**
* Return a formatted substitution line.
* @param context supplies the formatter context.
* @param stream_info supplies the stream info.
* @return std::string string containing the complete formatted substitution line.
*/
virtual std::string formatWithContext(const FormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const PURE;
};

template <class FormatterContext>
using FormatterBasePtr = std::unique_ptr<FormatterBase<FormatterContext>>;

/**
* Template interface for multiple protocols/modules formatter providers.
*/
template <class FormatterContext> class FormatterProviderBase {
public:
virtual ~FormatterProviderBase() = default;

/**
* Format the value with the given context and stream info.
* @param context supplies the formatter context.
* @param stream_info supplies the stream info.
* @return absl::optional<std::string> optional string containing a single value extracted from
* the given context and stream info.
*/
virtual absl::optional<std::string>
formatWithContext(const FormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const PURE;

/**
* Format the value with the given context and stream info.
* @param context supplies the formatter context.
* @param stream_info supplies the stream info.
* @return ProtobufWkt::Value containing a single value extracted from the given
* context and stream info.
*/
virtual ProtobufWkt::Value
formatValueWithContext(const FormatterContext& context,
const StreamInfo::StreamInfo& stream_info) const PURE;
};

template <class FormatterContext>
using FormatterProviderBasePtr = std::unique_ptr<FormatterProviderBase<FormatterContext>>;

template <class FormatterContext> class CommandParserBase {
public:
virtual ~CommandParserBase() = default;

/**
* Return a FormatterProviderBasePtr if command arg and max_length are correct for the formatter
* provider associated with command.
* @param command command name.
* @param command_arg command specific argument. Empty if no argument is provided.
* @param max_length length to which the output produced by FormatterProvider
* should be truncated to (optional).
*
* @return FormattterProviderPtr substitution provider for the parsed command.
*/
virtual FormatterProviderBasePtr<FormatterContext>
parse(const std::string& command, const std::string& command_arg,
absl::optional<size_t>& max_length) const PURE;
};

template <class FormatterContext>
using CommandParserBasePtr = std::unique_ptr<CommandParserBase<FormatterContext>>;

template <class FormatterContext>
using CommandParsersBase = std::vector<CommandParserBasePtr<FormatterContext>>;

template <class FormatterContext> class CommandParserFactoryBase : public Config::TypedFactory {
public:
/**
* Creates a particular CommandParser implementation.
*
* @param config supplies the configuration for the command parser.
* @param context supplies the factory context.
* @return CommandParserPtr the CommandParser which will be used in
* SubstitutionFormatParser::parse() when evaluating an access log format string.
*/
virtual CommandParserBasePtr<FormatterContext>
createCommandParserFromProto(const Protobuf::Message& config,
Server::Configuration::CommonFactoryContext& context) PURE;

std::string category() const override {
return fmt::format("envoy.{}.formatters", FormatterContext::category());
}
};

template <class FormatterContext> class BuiltInCommandParsersBase {
public:
static void addCommandParser(CommandParserBasePtr<FormatterContext> parser) {
mutableCommandParsers().push_back(std::move(parser));
}

static const CommandParsersBase<FormatterContext>& commandParsers() {
return mutableCommandParsers();
}

private:
static CommandParsersBase<FormatterContext>& mutableCommandParsers() {
MUTABLE_CONSTRUCT_ON_FIRST_USE(CommandParsersBase<FormatterContext>);
}
};

template <class FormatterContext> struct BuiltInCommandPaserRegister {
BuiltInCommandPaserRegister(CommandParserBasePtr<FormatterContext> parser) {
BuiltInCommandParsersBase<FormatterContext>::addCommandParser(std::move(parser));
}
};

#define REGISTER_BUILT_IN_COMMAND_PARSER(context, parser) \
static BuiltInCommandPaserRegister<context> register_##context##_##parser{ \
std::make_unique<parser>()};

} // namespace Formatter
} // namespace Envoy
Loading

0 comments on commit 8c3f3bf

Please sign in to comment.