This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch 10.2.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 09415554bfe27608f78dfb8a9aba748d07c276ff Author: Masakazu Kitajo <[email protected]> AuthorDate: Tue Feb 24 14:40:31 2026 -0700 Add support for custom logging field (#12872) This adds TSLogFieldRegister to enable plugins to add or redefine access log fields. (cherry picked from commit 40b99c7b57eec10ff8e549a6d6d2b87663804eea) --- .../api/functions/TSLifecycleHookAdd.en.rst | 6 + .../api/functions/TSLogFieldRegister.en.rst | 99 +++++++++ doc/developer-guide/api/types/TSEvent.en.rst | 4 + example/plugins/c-api/CMakeLists.txt | 1 + .../c-api/custom_logfield/custom_logfield.cc | 230 +++++++++++++++++++++ include/proxy/logging/Log.h | 1 + include/proxy/logging/LogAccess.h | 4 + include/proxy/logging/LogField.h | 8 + include/ts/apidefs.h.in | 17 +- include/ts/ts.h | 44 ++++ src/api/InkAPI.cc | 141 +++++++++++++ src/proxy/logging/Log.cc | 19 +- src/proxy/logging/LogAccess.cc | 7 + src/proxy/logging/LogField.cc | 59 +++++- src/traffic_server/traffic_server.cc | 10 + 15 files changed, 638 insertions(+), 12 deletions(-) diff --git a/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst b/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst index e67ca32bc3..b9c3a70976 100644 --- a/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst +++ b/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst @@ -120,6 +120,12 @@ Types Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_SHUTDOWN` and ``nullptr`` data. + .. cpp:enumerator:: TS_LIFECYCLE_LOG_INITIALIZED_HOOK + + Called after |TS| logging system is initialized but before logging configuration is loaded. + + Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_LOG_INITIALIZED` and ``nullptr`` data. + .. struct:: TSPluginMsg The data for the plugin message event :enumerator:`TS_EVENT_LIFECYCLE_MSG`. diff --git a/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst new file mode 100644 index 0000000000..4acdf40c84 --- /dev/null +++ b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst @@ -0,0 +1,99 @@ +.. Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed + with this work for additional information regarding copyright + ownership. The ASF licenses this file to you under the Apache + License, Version 2.0 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + +.. include:: ../../../common.defs + +.. default-domain:: cpp + +TSLogFieldRegister +****************** + +Registers a custom log field, or modifies an existing log field with a new definition. + +Synopsis +======== + +.. code-block:: cpp + + #include <ts/ts.h> + +.. function:: TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false); + +.. enum:: TSLogType + + Specify the type of a log field + + .. enumerator:: TS_LOG_TYPE_INT + + Integer field. + + .. enumerator:: TS_LOG_TYPE_STRING + + String field. + + .. enumerator:: TS_LOG_TYPE_ADDR + + Address field. It supports IPv4 address, IPv6 address, and Unix Domain Socket address (path). + +.. type:: int (*TSLogMarshalCallback)(TSHttpTxn, char *); + + Callback signature for functions to marshal log fields. + +.. type:: std::tuple<int, int> (*TSLogUnmarshalCallback)(char **, char *, int); + + Callback signature for functions to unmarshal log fields. + +.. function:: int TSLogStringMarshal(char *buf, std::string_view str); +.. function:: int TSLogIntMarshal(char *buf, int64_t value); +.. function:: int TSLogAddrMarshal(char *buf, sockaddr *addr); +.. function:: std::tuple<int, int> TSLogStringUnmarshal(char **buf, char *dest, int len); +.. function:: std::tuple<int, int> TSLogIntUnmarshal(char **buf, char *dest, int len); +.. function:: std::tuple<int, int> TSLogAddrUnmarshal(char **buf, char *dest, int len); + + Predefined marshaling and unmarshaling functions. + +Description +=========== + +The function registers or modifies a log field for access log. This is useful if you want to log something that |TS| does not expose, +log plugin state, or redefine existing log fields. + +The `name` is a human friendly name, and only used for debugging. The `symbol` is the keyword you'd want to use on logging.yaml for +the log field. It needs to be unique unless you are replacing an existing field by passing `true` to the optional argument +`replace`, otherwise the API call fails. + +The `type` is the data type of a log field. You can log any data as a string value, but please note that aggregating functions such +as AVG and SUM are only available for integer log fields. + +In many cases, you don't need to write code for marshaling and unmarshaling from scratch. The predefined functions are provided for +your convenience, and you only needs to pass a value that you want to log, + +Example: + + .. code-block:: cpp + + TSLogFieldRegister("Example", "exmpl", TS_LOG_TYPE_INT, + [](TSHttpTxn txnp, char *buf) -> int { + return TSLogIntMarshal(buf, 123); + }, + TSLogIntUnmarshal); + +Return Values +============= + +:func:`TSLogFieldRegister` returns :enumerator:`TS_SUCCESS` if it successfully registeres a new field, or :enumerator:`TS_ERROR` if it +fails due to symbol conflict. If :arg:`replace` is set to `true`, the function resolve the conflict by replacing the existing +field definition with a new one, and returns :enumerator:`TS_SUCCESS`. diff --git a/doc/developer-guide/api/types/TSEvent.en.rst b/doc/developer-guide/api/types/TSEvent.en.rst index 1f06546358..9de2fe9d01 100644 --- a/doc/developer-guide/api/types/TSEvent.en.rst +++ b/doc/developer-guide/api/types/TSEvent.en.rst @@ -198,6 +198,10 @@ Enumeration Members The |TS| process has is shutting down. +.. enumerator:: TS_EVENT_LIFECYCLE_LOG_INITIALIZED + + The logging system is initialized. + .. enumerator:: TS_EVENT_INTERNAL_60200 .. enumerator:: TS_EVENT_INTERNAL_60201 diff --git a/example/plugins/c-api/CMakeLists.txt b/example/plugins/c-api/CMakeLists.txt index 678fe416dd..65594cd391 100644 --- a/example/plugins/c-api/CMakeLists.txt +++ b/example/plugins/c-api/CMakeLists.txt @@ -65,3 +65,4 @@ add_atsplugin(statistic ./statistic/statistic.cc) add_atsplugin(protocol_stack ./protocol_stack/protocol_stack.cc) add_atsplugin(client_context_dump ./client_context_dump/client_context_dump.cc) target_link_libraries(client_context_dump PRIVATE OpenSSL::SSL libswoc::libswoc) +add_atsplugin(custom_logfield ./custom_logfield/custom_logfield.cc) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc new file mode 100644 index 0000000000..975dd2900a --- /dev/null +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -0,0 +1,230 @@ +/** @file + + This plugin demonstrates custom log field registration and usage. + It populates custom log fields from per-transaction user arguments. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include <inttypes.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ts/ts.h> +#include <ts/remap.h> + +DbgCtl dbg_ctl{"custom_logfield"}; + +char PLUGIN_NAME[] = "custom_logfield"; +char VENDOR_NAME[] = "Apache Software Foundation"; +char SUPPORT_EMAIL[] = "[email protected]"; +char USER_ARG_CSTM[] = "cstm_field"; +char USER_ARG_CSTMI[] = "cstmi_field"; +char USER_ARG_CSSN[] = "cssn_field"; + +int +write_text_from_user_arg(TSHttpTxn txnp, char *buf, const char *user_arg_name) +{ + int len = 0; + int index; + + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, user_arg_name, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + if (char *value = static_cast<char *>(TSUserArgGet(txnp, index)); value) { + Dbg(dbg_ctl, "Value: %s", value); + len = strlen(value); + if (buf) { + TSstrlcpy(buf, value, len + 1); + } + } + } + return len + 1; +} + +int +marshal_function_cstm(TSHttpTxn txnp, char *buf) +{ + if (buf) { + Dbg(dbg_ctl, "Marshaling a custom field cstm"); + } else { + Dbg(dbg_ctl, "Marshaling a custom field cstm for size calculation"); + } + return write_text_from_user_arg(txnp, buf, USER_ARG_CSTM); +} + +int +marshal_function_cssn(TSHttpTxn txnp, char *buf) +{ + if (buf) { + Dbg(dbg_ctl, "Marshaling a built-in field cssn"); + } else { + Dbg(dbg_ctl, "Marshaling a built-in field cssn for size calculation"); + } + return write_text_from_user_arg(txnp, buf, USER_ARG_CSSN); +} + +int +marshal_function_cstmi(TSHttpTxn txnp, char *buf) +{ + // This implementation is just to demonstrate marshaling an integer value. + // Predefined marshal function, TSLogIntMarshal, works for simple integer values + + int index; + + if (buf) { + Dbg(dbg_ctl, "Marshaling a custom field cstmi"); + } else { + Dbg(dbg_ctl, "Marshaling a custom field cstmi for size calculation"); + } + + if (buf) { + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + if (int64_t value = reinterpret_cast<int64_t>(TSUserArgGet(txnp, index)); value) { + Dbg(dbg_ctl, "Value: %" PRId64, value); + *(reinterpret_cast<int64_t *>(buf)) = value; + } + } + } + return sizeof(int64_t); +} + +std::tuple<int, int> +unmarshal_function_string(char **buf, char *dest, int len) +{ + Dbg(dbg_ctl, "Unmarshaling a string field"); + + // This implementation is just to demonstrate unmarshaling a string value. + // Predefined unmarshal function, TSLogStringUnmarshal, works for simple string values + + int l = strlen(*buf); + Dbg(dbg_ctl, "Dest buf size: %d", len); + Dbg(dbg_ctl, "Unmarshaled value length: %d", l); + if (l < len) { + memcpy(dest, *buf, l); + Dbg(dbg_ctl, "Unmarshaled value: %.*s", l, dest); + return { + l, // The length of data read from buf + l // The length of data written to dest + }; + } else { + return {-1, -1}; + } +} + +int +lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) +{ + TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIALIZED); + + // This registers a custom log field "cstm". + Dbg(dbg_ctl, "Registering cstm log field"); + TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING, marshal_function_cstm, unmarshal_function_string); + + // This replaces marshaling and unmarshaling functions for a built-in log field "cssn". + Dbg(dbg_ctl, "Overriding cssn log field"); + TSLogFieldRegister("modified cssn", "cssn", TS_LOG_TYPE_STRING, marshal_function_cssn, TSLogStringUnmarshal, true); + + // This registers a custom log field "cstmi" + Dbg(dbg_ctl, "Registering cstmi log field"); + TSLogFieldRegister("custom integer log field", "cstmi", TS_LOG_TYPE_INT, marshal_function_cstmi, TSLogIntUnmarshal); + + // This replaces marshaling and unmarshaling functions for a built-in log field "chi". + Dbg(dbg_ctl, "Overriding chi log field"); + TSLogFieldRegister( + "modified cssn", "chi", TS_LOG_TYPE_ADDR, + [](TSHttpTxn /* txnp */, char *buf) -> int { + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(80); + addr.sin_addr.s_addr = inet_addr("192.168.0.1"); + return TSLogAddrMarshal(buf, reinterpret_cast<sockaddr *>(&addr)); + }, + TSLogAddrUnmarshal, true); + + return TS_SUCCESS; +} + +void +TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) +{ + Dbg(dbg_ctl, "Initializing plugin"); + + TSPluginRegistrationInfo info = {PLUGIN_NAME, VENDOR_NAME, SUPPORT_EMAIL}; + if (TSPluginRegister(&info) != TS_SUCCESS) { + TSError("[%s](%s) Plugin registration failed. \n", PLUGIN_NAME, __FUNCTION__); + } + + TSCont cont = TSContCreate(lifecycle_event_handler, nullptr); + TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIALIZED_HOOK, cont); + + int argIndex; + TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTM, "This is for cstm log field", &argIndex); + Dbg(dbg_ctl, "User Arg Index: %d", argIndex); + TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSSN, "This is for cssn log field", &argIndex); + Dbg(dbg_ctl, "User Arg Index: %d", argIndex); + TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTMI, "This is for cstmi log field", &argIndex); + Dbg(dbg_ctl, "User Arg Index: %d", argIndex); +} + +TSReturnCode +TSRemapInit(TSRemapInterface *, char *, int) +{ + return TS_SUCCESS; +} + +TSReturnCode +TSRemapNewInstance(int, char **, void **, char *, int) +{ + return TS_SUCCESS; +} + +void +TSRemapDeleteInstance(void *) +{ +} + +TSRemapStatus +TSRemapDoRemap(void *, TSHttpTxn txn, TSRemapRequestInfo *) +{ + Dbg(dbg_ctl, "Remapping"); + + int index; + + // Store a string value for cstm field + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTM, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + TSUserArgSet(txn, index, const_cast<char *>("abc")); + } + + // Store a string value for cssn field + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSSN, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + TSUserArgSet(txn, index, const_cast<char *>("xyz")); + } + + // Store an integer value for cstmi field + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + TSUserArgSet(txn, index, reinterpret_cast<void *>(43)); + } + + return TSREMAP_NO_REMAP; +} diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h index debde4d2de..fffc8adf2f 100644 --- a/include/proxy/logging/Log.h +++ b/include/proxy/logging/Log.h @@ -156,6 +156,7 @@ public: // main interface static void init(int configFlags = 0); static void init_fields(); + static void load_config(); static bool transaction_logging_enabled() diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 16237b0289..d56078ac68 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -323,6 +323,10 @@ public: bool has_http_header_field(LogField::Container container, const char *field) const; void set_http_header_field(LogField::Container container, char *field, char *buf, int len); + + // Plugin + int marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_marshal_func); + // // unmarshalling routines // diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h index 43cad97111..7fbaca429e 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -28,6 +28,7 @@ #include <string_view> #include <string> #include <variant> +#include <tuple> #include <vector> #include "tscore/ink_inet.h" @@ -87,6 +88,8 @@ public: using UnmarshalFuncWithSlice = int (*)(char **, char *, int, LogSlice *, LogEscapeType); using UnmarshalFuncWithMap = int (*)(char **, char *, int, const Ptr<LogFieldAliasMap> &); using SetFunc = void (LogAccess::*)(char *, int); + using CustomMarshalFunc = int (*)(void *, char *); + using CustomUnmarshalFunc = std::tuple<int, int> (*)(char **, char *, int); using VarUnmarshalFuncSliceOnly = std::variant<UnmarshalFunc, UnmarshalFuncWithSlice>; using VarUnmarshalFunc = std::variant<decltype(nullptr), UnmarshalFunc, UnmarshalFuncWithSlice, UnmarshalFuncWithMap>; @@ -141,6 +144,8 @@ public: LogField(const char *name, const char *symbol, Type type, MarshalFunc marshal, UnmarshalFuncWithMap unmarshal, const Ptr<LogFieldAliasMap> &map, SetFunc _setFunc = nullptr); + LogField(const char *name, const char *symbol, Type type, CustomMarshalFunc custom_marshal, CustomUnmarshalFunc custom_unmarshal); + LogField(const char *field, Container container); LogField(const char *symbol, std::vector<HeaderField> header_fields, std::unique_ptr<LogField> fallback_field = nullptr, std::optional<std::string> fallback_default = std::nullopt); @@ -219,6 +224,8 @@ private: SetFunc m_set_func; TSMilestonesType milestone_from_m_name(); int milestones_from_m_name(TSMilestonesType *m1, TSMilestonesType *m2); + CustomMarshalFunc m_custom_marshal_func = nullptr; + CustomUnmarshalFunc m_custom_unmarshal_func = nullptr; std::vector<HeaderField> m_fallback_header_fields; std::unique_ptr<LogField> m_fallback_field; @@ -257,6 +264,7 @@ public: void clear(); void add(LogField *field, bool copy = true); + void remove(LogField *field); LogField *find_by_name(const char *name) const; LogField *find_by_symbol(const char *symbol) const; unsigned marshal_len(LogAccess *lad); diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 8f1e8929b2..3641f98a13 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -365,6 +365,7 @@ enum TSEvent { TS_EVENT_LIFECYCLE_MSG = 60105, TS_EVENT_LIFECYCLE_TASK_THREADS_READY = 60106, TS_EVENT_LIFECYCLE_SHUTDOWN = 60107, + TS_EVENT_LIFECYCLE_LOG_INITIALIZED = 60108, TS_EVENT_INTERNAL_60200 = 60200, TS_EVENT_INTERNAL_60201 = 60201, @@ -582,6 +583,7 @@ enum TSLifecycleHookID { TS_LIFECYCLE_TASK_THREADS_READY_HOOK, TS_LIFECYCLE_SHUTDOWN_HOOK, TS_LIFECYCLE_SSL_SECRET_HOOK, + TS_LIFECYCLE_LOG_INITIALIZED_HOOK, TS_LIFECYCLE_LAST_HOOK, }; @@ -1157,9 +1159,11 @@ using TSRemapPluginInfo = struct tsapi_remap_plugin_info *; using TSFetchSM = struct tsapi_fetchsm *; -using TSThreadFunc = void *(*)(void *data); -using TSEventFunc = int (*)(TSCont contp, TSEvent event, void *edata); -using TSConfigDestroyFunc = void (*)(void *data); +using TSThreadFunc = void *(*)(void *data); +using TSEventFunc = int (*)(TSCont contp, TSEvent event, void *edata); +using TSConfigDestroyFunc = void (*)(void *data); +using TSLogMarshalCallback = int (*)(TSHttpTxn, char *); +using TSLogUnmarshalCallback = std::tuple<int, int> (*)(char **, char *, int); struct TSFetchEvent { int success_event_id; @@ -1644,6 +1648,13 @@ struct TSResponseAction { bool no_cache; }; +enum TSLogType { + TS_LOG_TYPE_INT, + // DINT is omitted from the public API for now, until we decide whether we keep the type + TS_LOG_TYPE_STRING = 2, + TS_LOG_TYPE_ADDR = 3, +}; + /* -------------------------------------------------------------------------- Init */ diff --git a/include/ts/ts.h b/include/ts/ts.h index 49ab23eb9d..ad266054cb 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3289,3 +3289,47 @@ TSReturnCode TSVConnPPInfoGet(TSVConn vconn, uint16_t key, const char **value, i */ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); + +/** + Registers a custom log field, or modifies an existing log field with a new definition. + + @param name a human friendly name + @param symbol a symbol to use on the config file + @param type a type of the new log field + @param marshal_cb a callback function to marshal log value + @param unmarshal_cb a callback function to unmarshal log value + @param replace a flag to allow replacing an existing log field + + @return @c TS_SCCESS if the registration successes, TS_ERROR otherwise +*/ +TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb, bool replace = false); +/** + Helper function to marshal a string +*/ +int TSLogStringMarshal(char *buf, std::string_view str); + +/** + Helper function to marshal an integer +*/ +int TSLogIntMarshal(char *buf, int64_t value); + +/** + Helper function to marshal an address +*/ +int TSLogAddrMarshal(char *buf, sockaddr *addr); + +/** + Helper function to unmarshal a string +*/ +std::tuple<int, int> TSLogStringUnmarshal(char **buf, char *dest, int len); + +/** + Helper function to unmarshal an integer +*/ +std::tuple<int, int> TSLogIntUnmarshal(char **buf, char *dest, int len); + +/** + Helper function to unmarshal an address +*/ +std::tuple<int, int> TSLogAddrUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 625358a40b..ebb5185792 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -26,6 +26,7 @@ #include <unordered_map> #include <string_view> #include <string> +#include <charconv> #include "iocore/net/NetVConnection.h" #include "iocore/net/NetHandler.h" @@ -9161,3 +9162,143 @@ TSClientHello::TSExtensionTypeList::Iterator::operator*() const { return *(*reinterpret_cast<const TLSSNISupport::ClientHello::ExtensionIdIterator *>(_real_iterator)); } + +TSReturnCode +TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb, bool replace) +{ + if (auto ite = Log::field_symbol_hash.find(symbol.data()); ite != Log::field_symbol_hash.end()) { + if (replace) { + // Symbol is registered and the plugin wants to replace it. + // Need to unregister the existing entry first. + Log::global_field_list.remove(ite->second); + Log::field_symbol_hash.erase(ite); + } else { + // Symbol conflict. + return TS_ERROR; + } + } + + LogField *field = new LogField(name.data(), symbol.data(), static_cast<LogField::Type>(type), + reinterpret_cast<LogField::CustomMarshalFunc>(marshal_cb), unmarshal_cb); + Log::global_field_list.add(field, false); + Log::field_symbol_hash.emplace(symbol.data(), field); + + return TS_SUCCESS; +} + +int +TSLogStringMarshal(char *buf, std::string_view str) +{ + if (buf) { + ink_strlcpy(buf, str.data(), str.length() + 1); + } + return str.length() + 1; +} + +std::tuple<int, int> +TSLogStringUnmarshal(char **buf, char *dest, int len) +{ + // We cannot use LogAccess::unmarshal_str, etc. here because those internal + // functions take care of log buffer alignment. This function needs to be + // implemented as if it's a piece of code in plugin code, which is unaware + // of the alignment. + if (int l = strlen(*buf); l < len) { + memcpy(dest, *buf, l); + return {l, l}; + } else { + return {-1, -1}; + } +} + +int +TSLogIntMarshal(char *buf, int64_t value) +{ + if (buf) { + *(reinterpret_cast<int64_t *>(buf)) = value; + } + return sizeof(int64_t); +} + +std::tuple<int, int> +TSLogIntUnmarshal(char **buf, char *dest, int len) +{ + int64_t val = *(reinterpret_cast<int64_t *>(*buf)); + auto [end, err] = std::to_chars(dest, dest + len, val); + if (err == std::errc()) { + *end = '\0'; + return {sizeof(uint64_t), end - dest}; + } + + return {-1, -1}; +} + +int +TSLogAddrMarshal(char *buf, sockaddr *addr) +{ + LogFieldIpStorage data; + int len = sizeof(data._ip); + + if (nullptr == addr) { + data._ip._family = AF_UNSPEC; + } else if (ats_is_ip4(addr)) { + if (buf) { + data._ip4._family = AF_INET; + data._ip4._addr = ats_ip4_addr_cast(addr); + } + len = sizeof(data._ip4); + } else if (ats_is_ip6(addr)) { + if (buf) { + data._ip6._family = AF_INET6; + data._ip6._addr = ats_ip6_addr_cast(addr); + } + len = sizeof(data._ip6); + } else if (ats_is_unix(addr)) { + if (buf) { + data._un._family = AF_UNIX; + strncpy(data._un._path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE); + } + len = sizeof(data._un); + } else { + data._ip._family = AF_UNSPEC; + } + + if (buf) { + memcpy(buf, &data, len); + } + return len; +} + +std::tuple<int, int> +TSLogAddrUnmarshal(char **buf, char *dest, int len) +{ + IpEndpoint endpoint; + int read_len = sizeof(LogFieldIp); + + LogFieldIp *raw = reinterpret_cast<LogFieldIp *>(*buf); + if (AF_INET == raw->_family) { + LogFieldIp4 *ip4 = static_cast<LogFieldIp4 *>(raw); + ats_ip4_set(&endpoint, ip4->_addr); + read_len = sizeof(*ip4); + } else if (AF_INET6 == raw->_family) { + LogFieldIp6 *ip6 = static_cast<LogFieldIp6 *>(raw); + ats_ip6_set(&endpoint, ip6->_addr); + read_len = sizeof(*ip6); + } else if (AF_UNIX == raw->_family) { + LogFieldUn *un = static_cast<LogFieldUn *>(raw); + ats_unix_set(&endpoint, un->_path, TS_UNIX_SIZE); + read_len = sizeof(*un); + } else { + ats_ip_invalidate(&endpoint); + } + + if (!ats_is_ip(&endpoint) && !ats_is_unix(&endpoint)) { + dest[0] = '0'; + dest[1] = '\0'; + return {-1, 1}; + } else if (ats_ip_ntop(&endpoint, dest, len)) { + return {read_len, static_cast<int>(::strlen(dest))}; + } + + return {-1, -1}; +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index f5f685d68a..cbad6829ae 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1188,13 +1188,6 @@ Log::init(int flags) } init_fields(); - if (!(config_flags & LOGCAT)) { - RecRegisterConfigUpdateCb("proxy.config.log.logging_enabled", &Log::handle_logging_mode_change, nullptr); - - Dbg(dbg_ctl_log_config, "Log::init(): logging_mode = %d init status = %d", logging_mode, init_status); - config->init(); - init_when_enabled(); - } } void @@ -1219,6 +1212,18 @@ Log::init_when_enabled() } } +void +Log::load_config() +{ + if (!(config_flags & LOGCAT)) { + RecRegisterConfigUpdateCb("proxy.config.log.logging_enabled", &Log::handle_logging_mode_change, nullptr); + + Dbg(dbg_ctl_log_config, "Log::init(): logging_mode = %d init status = %d", logging_mode, init_status); + config->init(); + init_when_enabled(); + } +} + void Log::create_threads() { diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index 67b07d9be4..b671da02e4 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -464,6 +464,13 @@ LogAccess::marshal_ip(char *dest, sockaddr const *ip) return INK_ALIGN_DEFAULT(len); } +int +LogAccess::marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_marshal_func) +{ + int len = plugin_marshal_func(m_data, buf); + return LogAccess::padded_length(len); +} + HTTPHdr * LogAccess::header_for_container(LogField::Container container) const { diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc index e3e03ea11a..2c8378ef48 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -295,6 +295,33 @@ LogField::LogField(const char *name, const char *symbol, Type type, MarshalFunc strcmp(m_symbol, "cqtn") == 0 || strcmp(m_symbol, "cqtd") == 0 || strcmp(m_symbol, "cqtt") == 0); } +LogField::LogField(const char *name, const char *symbol, Type type, CustomMarshalFunc custom_marshal, + CustomUnmarshalFunc custom_unmarshal) + : m_name(ats_strdup(name)), + m_symbol(ats_strdup(symbol)), + m_type(type), + m_container(NO_CONTAINER), + m_marshal_func(nullptr), + m_unmarshal_func(VarUnmarshalFunc(nullptr)), + m_agg_op(NO_AGGREGATE), + m_agg_cnt(0), + m_agg_val(0), + m_milestone1(TS_MILESTONE_LAST_ENTRY), + m_milestone2(TS_MILESTONE_LAST_ENTRY), + m_time_field(false), + m_alias_map(nullptr), + m_set_func(nullptr), + m_custom_marshal_func(custom_marshal), + m_custom_unmarshal_func(custom_unmarshal) +{ + ink_assert(m_name != nullptr); + ink_assert(m_symbol != nullptr); + ink_assert(m_type >= 0 && m_type < N_TYPES); + + m_time_field = (strcmp(m_symbol, "cqts") == 0 || strcmp(m_symbol, "cqth") == 0 || strcmp(m_symbol, "cqtq") == 0 || + strcmp(m_symbol, "cqtn") == 0 || strcmp(m_symbol, "cqtd") == 0 || strcmp(m_symbol, "cqtt") == 0); +} + TSMilestonesType LogField::milestone_from_m_name() { @@ -449,6 +476,8 @@ LogField::LogField(const LogField &rhs) m_time_field(rhs.m_time_field), m_alias_map(rhs.m_alias_map), m_set_func(rhs.m_set_func), + m_custom_marshal_func(rhs.m_custom_marshal_func), + m_custom_unmarshal_func(rhs.m_custom_unmarshal_func), m_fallback_header_fields(rhs.m_fallback_header_fields), m_fallback_field(rhs.m_fallback_field ? std::make_unique<LogField>(*rhs.m_fallback_field) : nullptr), m_fallback_default(rhs.m_fallback_default) @@ -492,7 +521,11 @@ LogField::marshal_len(LogAccess *lad) } if (m_container == NO_CONTAINER) { - return (lad->*m_marshal_func)(nullptr); + if (m_custom_marshal_func == nullptr) { + return (lad->*m_marshal_func)(nullptr); + } else { + return lad->marshal_custom_field(nullptr, m_custom_marshal_func); + } } switch (m_container) { @@ -597,7 +630,11 @@ LogField::marshal(LogAccess *lad, char *buf) } if (m_container == NO_CONTAINER) { - return (lad->*m_marshal_func)(buf); + if (m_custom_marshal_func == nullptr) { + return (lad->*m_marshal_func)(buf); + } else { + return lad->marshal_custom_field(buf, m_custom_marshal_func); + } } switch (m_container) { @@ -709,6 +746,11 @@ LogField::unmarshal(char **buf, char *dest, int len, LogEscapeType escape_type) [&](UnmarshalFuncWithMap f) -> unsigned { return (*f)(buf, dest, len, m_alias_map); }, [&](UnmarshalFunc f) -> unsigned { return (*f)(buf, dest, len); }, [&](decltype(nullptr)) -> unsigned { + if (m_custom_unmarshal_func) { + auto [read_len, written_len] = m_custom_unmarshal_func(buf, dest, len); + *buf += LogAccess::padded_length(read_len); + return written_len; + } ink_assert(false); return 0; }}, @@ -982,6 +1024,19 @@ LogFieldList::add(LogField *field, bool copy) } } +void +LogFieldList::remove(LogField *field) +{ + ink_assert(field != nullptr); + + if (field->type() == LogField::sINT) { + m_marshal_len -= INK_MIN_ALIGN; + } + m_field_list.remove(field); + + delete field; +} + LogField * LogFieldList::find_by_name(const char *name) const { diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index da674bae1c..6a2ede3c9f 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2332,6 +2332,16 @@ main(int /* argc ATS_UNUSED */, const char **argv) pluginInitCheck.notify_one(); } + // Give plugins a chance to customize log fields + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIALIZED_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIALIZED, nullptr); + hook = hook->next(); + } + + // Log config needs to be loaded after the custom field registration + Log::load_config(); + if (IpAllow::has_no_rules()) { Error("No ip_allow.yaml entries found. All requests will be denied!"); }
