diff --git a/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst b/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst index e67ca32bc30..b9c3a70976f 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 00000000000..2ac24e67af5 --- /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 + +.. 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 sginature for functions to marshal log fields. + +.. type:: std::tuple (*TSLogUnmarshalCallback)(char **, char *, int); + + Callback sginature 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 TSLogStringUnmarshal(char **buf, char *dest, int len); +.. function:: std::tuple TSLogIntUnmarshal(char **buf, char *dest, int len); +.. function:: std::tuple 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 1f065463586..9de2fe9d01e 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 678fe416dde..65594cd391e 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 00000000000..975dd2900ac --- /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 +#include +#include +#include + +#include +#include + +DbgCtl dbg_ctl{"custom_logfield"}; + +char PLUGIN_NAME[] = "custom_logfield"; +char VENDOR_NAME[] = "Apache Software Foundation"; +char SUPPORT_EMAIL[] = "dev@trafficserver.apache.org"; +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(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(TSUserArgGet(txnp, index)); value) { + Dbg(dbg_ctl, "Value: %" PRId64, value); + *(reinterpret_cast(buf)) = value; + } + } + } + return sizeof(int64_t); +} + +std::tuple +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(&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("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("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(43)); + } + + return TSREMAP_NO_REMAP; +} diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h index 4cf40db0969..ef7df731c0d 100644 --- a/include/proxy/logging/Log.h +++ b/include/proxy/logging/Log.h @@ -155,6 +155,7 @@ class Log // 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 0cdd6e1098c..7caa02201f0 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -303,6 +303,10 @@ class LogAccess int marshal_milestone_fmt_ms(TSMilestonesType ms, char *buf); int marshal_milestone_diff(TSMilestonesType ms1, TSMilestonesType ms2, char *buf); 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 dcc5b23bb0c..b883b7c558c 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "tscore/ink_inet.h" #include "tscore/ink_platform.h" @@ -84,6 +85,8 @@ class LogField using UnmarshalFuncWithSlice = int (*)(char **, char *, int, LogSlice *, LogEscapeType); using UnmarshalFuncWithMap = int (*)(char **, char *, int, const Ptr &); using SetFunc = void (LogAccess::*)(char *, int); + using CustomMarshalFunc = int (*)(void *, char *); + using CustomUnmarshalFunc = std::tuple (*)(char **, char *, int); using VarUnmarshalFuncSliceOnly = std::variant; using VarUnmarshalFunc = std::variant; @@ -132,6 +135,8 @@ class LogField LogField(const char *name, const char *symbol, Type type, MarshalFunc marshal, UnmarshalFuncWithMap unmarshal, const Ptr &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 LogField &rhs); ~LogField(); @@ -207,6 +212,8 @@ class LogField 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; public: LINK(LogField, link); @@ -234,6 +241,7 @@ class LogFieldList 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 078ce0eb69c..fc1fb7576cc 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -46,6 +46,7 @@ #include #include #include +#include /** Apply printf format string compile-time argument checking to a function. * @@ -361,6 +362,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, @@ -578,6 +580,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, }; @@ -1083,9 +1086,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 (*)(char **, char *, int); struct TSFetchEvent { int success_event_id; @@ -1570,6 +1575,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 d62d5d81561..c63febb3607 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3224,3 +3224,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 TSLogStringUnmarshal(char **buf, char *dest, int len); + +/** + Helper function to unmarshal an integer +*/ +std::tuple TSLogIntUnmarshal(char **buf, char *dest, int len); + +/** + Helper function to unmarshal an address +*/ +std::tuple TSLogAddrUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 1c832eb9dd6..000ff2dd278 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include "iocore/net/NetVConnection.h" #include "iocore/net/NetHandler.h" @@ -8996,3 +8997,143 @@ TSConnectionLimitExemptListClear() { ConnectionTracker::clear_client_exempt_list(); } + +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(type), + reinterpret_cast(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 +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(buf)) = value; + } + return sizeof(int64_t); +} + +std::tuple +TSLogIntUnmarshal(char **buf, char *dest, int len) +{ + int64_t val = *(reinterpret_cast(*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 +TSLogAddrUnmarshal(char **buf, char *dest, int len) +{ + IpEndpoint endpoint; + int read_len = sizeof(LogFieldIp); + + LogFieldIp *raw = reinterpret_cast(*buf); + if (AF_INET == raw->_family) { + LogFieldIp4 *ip4 = static_cast(raw); + ats_ip4_set(&endpoint, ip4->_addr); + read_len = sizeof(*ip4); + } else if (AF_INET6 == raw->_family) { + LogFieldIp6 *ip6 = static_cast(raw); + ats_ip6_set(&endpoint, ip6->_addr); + read_len = sizeof(*ip6); + } else if (AF_UNIX == raw->_family) { + LogFieldUn *un = static_cast(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(::strlen(dest))}; + } + + return {-1, -1}; +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 35d2c871874..89ac5e0eb42 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1134,13 +1134,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 @@ -1165,6 +1158,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 9c0ce48bd48..93af068f31b 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -473,6 +473,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_http_sm, buf); + return LogAccess::padded_length(len); +} + inline int LogAccess::unmarshal_with_map(int64_t code, char *dest, int len, const Ptr &map, const char *msg) { diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc index f7dcbbd067a..55db25a8a4d 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -287,6 +287,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() { @@ -413,7 +440,9 @@ LogField::LogField(const LogField &rhs) m_milestone2(rhs.m_milestone2), m_time_field(rhs.m_time_field), m_alias_map(rhs.m_alias_map), - m_set_func(rhs.m_set_func) + 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) { ink_assert(m_name != nullptr); ink_assert(m_symbol != nullptr); @@ -441,7 +470,11 @@ unsigned 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) { @@ -523,7 +556,11 @@ unsigned 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) { @@ -615,6 +652,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; }}, @@ -783,6 +825,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 811539f1433..9a249910590 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2228,6 +2228,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!"); }