From c3faea39bf6e16f57dbebb506ffae7c07f378714 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 9 Feb 2026 16:10:16 -0700 Subject: [PATCH 01/25] Add support for custom logging field --- example/plugins/c-api/CMakeLists.txt | 1 + .../c-api/custom_logfield/custom_logfield.cc | 130 ++++++++++++++++++ include/proxy/logging/Log.h | 1 + include/proxy/logging/LogAccess.h | 5 + include/proxy/logging/LogField.h | 6 + include/ts/apidefs.h.in | 17 ++- include/ts/ts.h | 3 + src/api/InkAPI.cc | 12 ++ src/proxy/logging/Log.cc | 14 ++ src/proxy/logging/LogAccess.cc | 13 ++ src/proxy/logging/LogField.cc | 49 ++++++- src/traffic_server/traffic_server.cc | 6 +- 12 files changed, 248 insertions(+), 9 deletions(-) create mode 100644 example/plugins/c-api/custom_logfield/custom_logfield.cc 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..433d33bcb3e --- /dev/null +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -0,0 +1,130 @@ +/** @file + + This plugin counts the number of times every header has appeared. + Maintains separate counts for client and origin headers. + + @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 + +DbgCtl dbg_ctl{"custom_logfield"}; + +char PLUGIN_NAME[] = "header_freq"; +char VENDOR_NAME[] = "Apache Software Foundation"; +char SUPPORT_EMAIL[] = "dev@trafficserver.apache.org"; +char USER_ARG_NAME[] = "cstm_field"; + +int +marshal_function(TSHttpTxn txnp, char *buf) +{ + int len = 0; + int index; + + Dbg(dbg_ctl, "Marshaling a custom field"); + + 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) { + len = strlen(value); + if (buf) { + strlcpy(buf, value, len + 1); + } + } + } + return len + 1; +} + +int +unmarshal_function(char **buf, char *dest, int len) +{ + Dbg(dbg_ctl, "Unmarshaling a custom field"); + + 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; + } else { + return -1; + } +} + +int +lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) +{ + Dbg(dbg_ctl, "Registering a custom field"); + + TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIAZLIED); + TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING, marshal_function, unmarshal_function); + + 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_INITIAZLIED_HOOK, cont); + + int argIndex; + TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_NAME, "This is for cstm 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; + if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_NAME, &index, nullptr) == TS_SUCCESS) { + Dbg(dbg_ctl, "User Arg Index: %d", index); + TSUserArgSet(txn, index, const_cast("abc")); + } + + return TSREMAP_NO_REMAP; +} diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h index 4cf40db0969..e9af78fadce 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 init_plugin_fields(); static bool transaction_logging_enabled() diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h index 0cdd6e1098c..90a8802b6d3 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -303,6 +303,11 @@ 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); + static int unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func); + // // unmarshalling routines // diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h index dcc5b23bb0c..b49b3bf6b06 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -84,6 +84,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 = int (*)(char **, char *, int); using VarUnmarshalFuncSliceOnly = std::variant; using VarUnmarshalFunc = std::variant; @@ -132,6 +134,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 +211,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; + CustomUnmarshalFunc m_custom_unmarshal_func; public: LINK(LogField, link); diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 078ce0eb69c..de23be16de6 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -361,6 +361,7 @@ enum TSEvent { TS_EVENT_LIFECYCLE_MSG = 60105, TS_EVENT_LIFECYCLE_TASK_THREADS_READY = 60106, TS_EVENT_LIFECYCLE_SHUTDOWN = 60107, + TS_EVENT_LIFECYCLE_LOG_INITIAZLIED = 60108, TS_EVENT_INTERNAL_60200 = 60200, TS_EVENT_INTERNAL_60201 = 60201, @@ -578,6 +579,7 @@ enum TSLifecycleHookID { TS_LIFECYCLE_TASK_THREADS_READY_HOOK, TS_LIFECYCLE_SHUTDOWN_HOOK, TS_LIFECYCLE_SSL_SECRET_HOOK, + TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, TS_LIFECYCLE_LAST_HOOK, }; @@ -1083,9 +1085,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 = int (*)(char **, char *, int); struct TSFetchEvent { int success_event_id; @@ -1570,6 +1574,13 @@ struct TSResponseAction { bool no_cache; }; +enum TSLogType { + TS_LOG_TYPE_S_INT, + TS_LOG_TYPE_D_INT, + TS_LOG_TYPE_STRING, + TS_LOG_TYPE_IP, +}; + /* -------------------------------------------------------------------------- Init */ diff --git a/include/ts/ts.h b/include/ts/ts.h index d62d5d81561..3bd6aeff8e7 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3224,3 +3224,6 @@ TSReturnCode TSVConnPPInfoGet(TSVConn vconn, uint16_t key, const char **value, i */ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); + +TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 1c832eb9dd6..042f09fbd23 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -8996,3 +8996,15 @@ TSConnectionLimitExemptListClear() { ConnectionTracker::clear_client_exempt_list(); } + +TSReturnCode +TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, + TSLogUnmarshalCallback unmarshal_cb) +{ + 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; +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 35d2c871874..9a40d4b6de1 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -54,6 +54,8 @@ #include "tscore/MgmtDefs.h" +#include + #define PERIODIC_TASKS_INTERVAL_FALLBACK 5 // Log global objects @@ -1043,9 +1045,21 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("vs", field); + Log::init_plugin_fields(); + init_status |= FIELDS_INITIALIZED; } +void +Log::init_plugin_fields() +{ + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); + hook = hook->next(); + } +} + /*------------------------------------------------------------------------- Initialization functions diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc index 9c0ce48bd48..0edecc9fce3 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -473,6 +473,19 @@ 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); +} + +int +LogAccess::unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func) +{ + return plugin_unmarshal_func(buf, dest, 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..587b4fded05 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -287,6 +287,34 @@ 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); + ink_assert(m_marshal_func != (MarshalFunc) nullptr); + + 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 +441,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 +471,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 +557,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 +653,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) { + int l = m_custom_unmarshal_func(buf, dest, len); + buf += LogAccess::padded_length(l); + return l; + } ink_assert(false); return 0; }}, diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index 811539f1433..acbf531f522 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2212,9 +2212,6 @@ main(int /* argc ATS_UNUSED */, const char **argv) hostDBProcessor.start(); - // initialize logging (after event and net processor) - Log::init(); - (void)parsePluginConfig(); // Init plugins as soon as logging is ready. @@ -2228,6 +2225,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) pluginInitCheck.notify_one(); } + // initialize logging (after event and net processor) + Log::init(); + if (IpAllow::has_no_rules()) { Error("No ip_allow.yaml entries found. All requests will be denied!"); } From 9796a030328ff971e1e1b29cbeca875084569ec5 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 10 Feb 2026 15:18:09 -0700 Subject: [PATCH 02/25] Don't call the hook on traffic_logstats --- src/proxy/logging/Log.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 9a40d4b6de1..a11ce606466 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1053,10 +1053,12 @@ Log::init_fields() void Log::init_plugin_fields() { - APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); - while (hook) { - hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); - hook = hook->next(); + if (g_lifecycle_hooks) { + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); + hook = hook->next(); + } } } From 319d90b872fad6b1eeb7d449a94a2b9ad10af0ac Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 10 Feb 2026 15:22:45 -0700 Subject: [PATCH 03/25] Use TSstrlcpy instead of strlcpy --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 433d33bcb3e..df0e9266f66 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -45,7 +45,7 @@ marshal_function(TSHttpTxn txnp, char *buf) if (char *value = static_cast(TSUserArgGet(txnp, index)); value) { len = strlen(value); if (buf) { - strlcpy(buf, value, len + 1); + TSstrlcpy(buf, value, len + 1); } } } From 4cf0fb66856598b70b17bb3514a5f452c02d9fd9 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 10 Feb 2026 16:37:50 -0700 Subject: [PATCH 04/25] Initialize the variables with nullptr --- include/proxy/logging/LogField.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h index b49b3bf6b06..20bcb3fc638 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -211,8 +211,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; - CustomUnmarshalFunc m_custom_unmarshal_func; + CustomMarshalFunc m_custom_marshal_func = nullptr; + CustomUnmarshalFunc m_custom_unmarshal_func = nullptr; public: LINK(LogField, link); From aa2bd4863731d47b42fd9e86314f5df10cf8aff6 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 10 Feb 2026 16:49:54 -0700 Subject: [PATCH 05/25] Fix the plugin name on the source code --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index df0e9266f66..c6383bc4572 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -27,7 +27,7 @@ DbgCtl dbg_ctl{"custom_logfield"}; -char PLUGIN_NAME[] = "header_freq"; +char PLUGIN_NAME[] = "custom_logfield"; char VENDOR_NAME[] = "Apache Software Foundation"; char SUPPORT_EMAIL[] = "dev@trafficserver.apache.org"; char USER_ARG_NAME[] = "cstm_field"; From 40e4445ce8ece9eb0dd32e3a18fbf3190623460c Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 10 Feb 2026 17:57:55 -0700 Subject: [PATCH 06/25] Add support for overriding built-in fields --- .../c-api/custom_logfield/custom_logfield.cc | 61 +++++++++++++++---- include/proxy/logging/LogField.h | 1 + include/ts/ts.h | 2 +- src/api/InkAPI.cc | 16 ++++- src/proxy/logging/LogField.cc | 15 ++++- 5 files changed, 79 insertions(+), 16 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index c6383bc4572..0f33727ce42 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -30,19 +30,19 @@ 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_NAME[] = "cstm_field"; +char USER_ARG_CSTM[] = "cstm_field"; +char USER_ARG_CSSN[] = "cssn_field"; int -marshal_function(TSHttpTxn txnp, char *buf) +write_text_from_user_arg(TSHttpTxn txnp, char *buf, const char *user_arg_name) { int len = 0; int index; - Dbg(dbg_ctl, "Marshaling a custom field"); - - if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_NAME, &index, nullptr) == TS_SUCCESS) { + 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); @@ -53,9 +53,31 @@ marshal_function(TSHttpTxn txnp, char *buf) } int -unmarshal_function(char **buf, char *dest, int len) +marshal_function_cstm(TSHttpTxn txnp, char *buf) { - Dbg(dbg_ctl, "Unmarshaling a custom field"); + 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 +unmarshal_function_string(char **buf, char *dest, int len) +{ + Dbg(dbg_ctl, "Unmarshaling a string field"); int l = strlen(*buf); Dbg(dbg_ctl, "Dest buf size: %d", len); @@ -72,10 +94,15 @@ unmarshal_function(char **buf, char *dest, int len) int lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) { - Dbg(dbg_ctl, "Registering a custom field"); - TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIAZLIED); - TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING, marshal_function, unmarshal_function); + + // 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, unmarshal_function_string, true); return TS_SUCCESS; } @@ -94,7 +121,9 @@ TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, cont); int argIndex; - TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_NAME, "This is for cstm log field", &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); } @@ -121,10 +150,18 @@ TSRemapDoRemap(void *, TSHttpTxn txn, TSRemapRequestInfo *) Dbg(dbg_ctl, "Remapping"); int index; - if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_NAME, &index, nullptr) == TS_SUCCESS) { + + // Store 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 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")); + } + return TSREMAP_NO_REMAP; } diff --git a/include/proxy/logging/LogField.h b/include/proxy/logging/LogField.h index 20bcb3fc638..db1b05d275b 100644 --- a/include/proxy/logging/LogField.h +++ b/include/proxy/logging/LogField.h @@ -240,6 +240,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/ts.h b/include/ts/ts.h index 3bd6aeff8e7..edd08f3d189 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3226,4 +3226,4 @@ TSReturnCode TSVConnPPInfoGet(TSVConn vconn, uint16_t key, const char **value, i TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, - TSLogUnmarshalCallback unmarshal_cb); + TSLogUnmarshalCallback unmarshal_cb, bool replace = false); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 042f09fbd23..c6135344ebf 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -8999,8 +8999,20 @@ TSConnectionLimitExemptListClear() TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, - TSLogUnmarshalCallback unmarshal_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); diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc index 587b4fded05..04528cd6549 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -655,7 +655,7 @@ LogField::unmarshal(char **buf, char *dest, int len, LogEscapeType escape_type) [&](decltype(nullptr)) -> unsigned { if (m_custom_unmarshal_func) { int l = m_custom_unmarshal_func(buf, dest, len); - buf += LogAccess::padded_length(l); + *buf += LogAccess::padded_length(l); return l; } ink_assert(false); @@ -826,6 +826,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 { From 544d31b9051d67f64c77ae444e5ce9c4cda7c31d Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Wed, 11 Feb 2026 10:53:45 -0700 Subject: [PATCH 07/25] Adjust the timing to invoke the lifecycle hook --- include/proxy/logging/Log.h | 1 - src/proxy/logging/Log.cc | 12 ------------ src/traffic_server/traffic_server.cc | 11 +++++++++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h index e9af78fadce..4cf40db0969 100644 --- a/include/proxy/logging/Log.h +++ b/include/proxy/logging/Log.h @@ -155,7 +155,6 @@ class Log // main interface static void init(int configFlags = 0); static void init_fields(); - static void init_plugin_fields(); static bool transaction_logging_enabled() diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index a11ce606466..1e8b4dc5c11 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1050,18 +1050,6 @@ Log::init_fields() init_status |= FIELDS_INITIALIZED; } -void -Log::init_plugin_fields() -{ - if (g_lifecycle_hooks) { - APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); - while (hook) { - hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); - hook = hook->next(); - } - } -} - /*------------------------------------------------------------------------- Initialization functions diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index acbf531f522..f61358e8bd7 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2212,6 +2212,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) hostDBProcessor.start(); + // initialize logging (after event and net processor) + Log::init(); + (void)parsePluginConfig(); // Init plugins as soon as logging is ready. @@ -2225,8 +2228,12 @@ main(int /* argc ATS_UNUSED */, const char **argv) pluginInitCheck.notify_one(); } - // initialize logging (after event and net processor) - Log::init(); + // Give plugins a chance to customize log fields + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); + hook = hook->next(); + } if (IpAllow::has_no_rules()) { Error("No ip_allow.yaml entries found. All requests will be denied!"); From 09867feec03c9dac025b24744f904d571f79cadc Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Wed, 11 Feb 2026 11:01:52 -0700 Subject: [PATCH 08/25] Fix a build error --- src/proxy/logging/Log.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 1e8b4dc5c11..65d9ad60c6b 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1045,8 +1045,6 @@ Log::init_fields() global_field_list.add(field, false); field_symbol_hash.emplace("vs", field); - Log::init_plugin_fields(); - init_status |= FIELDS_INITIALIZED; } From f73be025b08c335115043b9e2d8ab0a36c9ee9a9 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 16:32:38 -0700 Subject: [PATCH 09/25] Add support for integer field --- .../c-api/custom_logfield/custom_logfield.cc | 61 ++++++++++++++++--- include/proxy/logging/Log.h | 1 + include/proxy/logging/LogAccess.h | 3 +- include/proxy/logging/LogField.h | 3 +- include/ts/apidefs.h.in | 7 ++- include/ts/ts.h | 2 + src/api/InkAPI.cc | 28 +++++++++ src/proxy/logging/Log.cc | 19 +++--- src/proxy/logging/LogAccess.cc | 6 -- src/proxy/logging/LogField.cc | 6 +- src/traffic_server/traffic_server.cc | 3 + 11 files changed, 107 insertions(+), 32 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 0f33727ce42..17a8b0d52b9 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -22,16 +22,19 @@ limitations under the License. */ +#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_CSSN[] = "cssn_field"; +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) @@ -75,19 +78,45 @@ marshal_function_cssn(TSHttpTxn txnp, char *buf) } int +marshal_function_cstmi(TSHttpTxn txnp, char *buf) +{ + 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. + // 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; + return {l, l}; } else { - return -1; + return {-1, -1}; } } @@ -102,7 +131,11 @@ lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* // 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, unmarshal_function_string, true); + 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); return TS_SUCCESS; } @@ -125,6 +158,8 @@ TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) 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 @@ -151,17 +186,23 @@ TSRemapDoRemap(void *, TSHttpTxn txn, TSRemapRequestInfo *) int index; - // Store value for cstm field + // 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 value for cssn field + // 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 90a8802b6d3..7caa02201f0 100644 --- a/include/proxy/logging/LogAccess.h +++ b/include/proxy/logging/LogAccess.h @@ -305,8 +305,7 @@ class LogAccess 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); - static int unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func); + 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 db1b05d275b..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" @@ -85,7 +86,7 @@ class LogField using UnmarshalFuncWithMap = int (*)(char **, char *, int, const Ptr &); using SetFunc = void (LogAccess::*)(char *, int); using CustomMarshalFunc = int (*)(void *, char *); - using CustomUnmarshalFunc = int (*)(char **, char *, int); + using CustomUnmarshalFunc = std::tuple (*)(char **, char *, int); using VarUnmarshalFuncSliceOnly = std::variant; using VarUnmarshalFunc = std::variant; diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index de23be16de6..69a7844f7f6 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. * @@ -1089,7 +1090,7 @@ 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 = int (*)(char **, char *, int); +using TSLogUnmarshalCallback = std::tuple (*)(char **, char *, int); struct TSFetchEvent { int success_event_id; @@ -1575,8 +1576,8 @@ struct TSResponseAction { }; enum TSLogType { - TS_LOG_TYPE_S_INT, - TS_LOG_TYPE_D_INT, + TS_LOG_TYPE_INT, + TS_LOG_TYPE_DINT, TS_LOG_TYPE_STRING, TS_LOG_TYPE_IP, }; diff --git a/include/ts/ts.h b/include/ts/ts.h index edd08f3d189..7f40ad69b5e 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3227,3 +3227,5 @@ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false); +std::tuple TSLogStringUnmarshal(char **buf, char *dest, int len); +std::tuple TSLogIntUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index c6135344ebf..db702d8246e 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -9020,3 +9020,31 @@ TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType typ return TS_SUCCESS; } + +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}; + } +} + +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}; +} diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 65d9ad60c6b..18013ccf7e6 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -1136,13 +1136,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 @@ -1167,6 +1160,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 0edecc9fce3..93af068f31b 100644 --- a/src/proxy/logging/LogAccess.cc +++ b/src/proxy/logging/LogAccess.cc @@ -480,12 +480,6 @@ LogAccess::marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_ma return LogAccess::padded_length(len); } -int -LogAccess::unmarshal_custom_field(char **buf, char *dest, int len, LogField::CustomUnmarshalFunc plugin_unmarshal_func) -{ - return plugin_unmarshal_func(buf, dest, 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 04528cd6549..a6672a7d6db 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -654,9 +654,9 @@ LogField::unmarshal(char **buf, char *dest, int len, LogEscapeType escape_type) [&](UnmarshalFunc f) -> unsigned { return (*f)(buf, dest, len); }, [&](decltype(nullptr)) -> unsigned { if (m_custom_unmarshal_func) { - int l = m_custom_unmarshal_func(buf, dest, len); - *buf += LogAccess::padded_length(l); - return l; + 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; diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index f61358e8bd7..5536e2400ef 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2235,6 +2235,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) 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!"); } From b4105ed611a6743ba8886d3b66cccf3e33acf297 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 17:25:53 -0700 Subject: [PATCH 10/25] Include charconv header --- src/api/InkAPI.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index db702d8246e..a5c87357050 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" From 87dd7854ad283dfc6c532a399ce2edd30da61361 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 18:41:11 -0700 Subject: [PATCH 11/25] Add support for socket address field --- .../c-api/custom_logfield/custom_logfield.cc | 22 +++++- include/ts/apidefs.h.in | 2 +- include/ts/ts.h | 2 + src/api/InkAPI.cc | 70 +++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 17a8b0d52b9..fb95da4760b 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -23,6 +23,9 @@ */ #include +#include +#include +#include #include #include @@ -114,7 +117,10 @@ unmarshal_function_string(char **buf, char *dest, int len) if (l < len) { memcpy(dest, *buf, l); Dbg(dbg_ctl, "Unmarshaled value: %.*s", l, dest); - return {l, l}; + return { + l, // The length of data read from buf + l // The length of data written to dest + }; } else { return {-1, -1}; } @@ -137,6 +143,20 @@ lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* 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{ + .sin_family = AF_INET, + .sin_addr.s_addr = inet_addr("192.168.0.1"), + .sin_port = htons(80), + }; + return TSLogAddrMarshal(buf, reinterpret_cast(&addr)); + }, + TSLogAddrUnmarshal, true); + return TS_SUCCESS; } diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 69a7844f7f6..cb75a1fd2f4 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1579,7 +1579,7 @@ enum TSLogType { TS_LOG_TYPE_INT, TS_LOG_TYPE_DINT, TS_LOG_TYPE_STRING, - TS_LOG_TYPE_IP, + TS_LOG_TYPE_ADDR, }; /* -------------------------------------------------------------------------- diff --git a/include/ts/ts.h b/include/ts/ts.h index 7f40ad69b5e..7fb4544f33e 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3227,5 +3227,7 @@ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false); +int TSLogAddrMarshal(char *buf, sockaddr *addr); std::tuple TSLogStringUnmarshal(char **buf, char *dest, int len); std::tuple TSLogIntUnmarshal(char **buf, char *dest, int len); +std::tuple TSLogAddrUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index a5c87357050..8a05f69c7a2 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -9049,3 +9049,73 @@ TSLogIntUnmarshal(char **buf, char *dest, int len) 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'; + *dest = '\0'; + return {-1, 1}; + } else if (ats_ip_ntop(&endpoint, dest, len)) { + return {read_len, static_cast(::strlen(dest))}; + } + + return {-1, -1}; +} From 0b8fdb01a981f4593c6a793e15515e8074511e7b Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 19:11:20 -0700 Subject: [PATCH 12/25] Add more marshaling functions --- .../c-api/custom_logfield/custom_logfield.cc | 5 ++++- include/ts/ts.h | 2 ++ src/api/InkAPI.cc | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index fb95da4760b..21afb5d12c4 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -83,6 +83,9 @@ marshal_function_cssn(TSHttpTxn txnp, char *buf) 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) { @@ -108,7 +111,7 @@ unmarshal_function_string(char **buf, char *dest, int len) { Dbg(dbg_ctl, "Unmarshaling a string field"); - // This implementation is just to demonstrate unmarshaling. + // This implementation is just to demonstrate unmarshaling a string value. // Predefined unmarshal function, TSLogStringUnmarshal, works for simple string values int l = strlen(*buf); diff --git a/include/ts/ts.h b/include/ts/ts.h index 7fb4544f33e..bdc941939d5 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3227,6 +3227,8 @@ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false); +int TSLogStringMarshal(char *buf, char *str, int str_len); +int TSLogIntMarshal(char *buf, int64_t value); int TSLogAddrMarshal(char *buf, sockaddr *addr); std::tuple TSLogStringUnmarshal(char **buf, char *dest, int len); std::tuple TSLogIntUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 8a05f69c7a2..236a1890b73 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -9022,6 +9022,15 @@ TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType typ return TS_SUCCESS; } +int +TSLogStringMarshal(char *buf, char *str, int str_len) +{ + if (buf) { + ink_strlcpy(buf, str, str_len + 1); + } + return str_len + 1; +} + std::tuple TSLogStringUnmarshal(char **buf, char *dest, int len) { @@ -9037,6 +9046,15 @@ TSLogStringUnmarshal(char **buf, char *dest, int len) } } +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) { From 336791343f17f715a8bc6c2936adf165d7f16a22 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 19:27:04 -0700 Subject: [PATCH 13/25] Use string_view --- include/ts/ts.h | 2 +- src/api/InkAPI.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ts/ts.h b/include/ts/ts.h index bdc941939d5..406cbd3b2f1 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3227,7 +3227,7 @@ TSReturnCode TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value); TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false); -int TSLogStringMarshal(char *buf, char *str, int str_len); +int TSLogStringMarshal(char *buf, std::string_view str); int TSLogIntMarshal(char *buf, int64_t value); int TSLogAddrMarshal(char *buf, sockaddr *addr); std::tuple TSLogStringUnmarshal(char **buf, char *dest, int len); diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index 236a1890b73..b7e0346dfc3 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -9023,12 +9023,12 @@ TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType typ } int -TSLogStringMarshal(char *buf, char *str, int str_len) +TSLogStringMarshal(char *buf, std::string_view str) { if (buf) { - ink_strlcpy(buf, str, str_len + 1); + ink_strlcpy(buf, str.data(), str.length() + 1); } - return str_len + 1; + return str.length() + 1; } std::tuple From e25597ef91272797e8b84c13381f3c58bad04f14 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 19:29:58 -0700 Subject: [PATCH 14/25] Revert unnecessary changes --- src/proxy/logging/Log.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc index 18013ccf7e6..89ac5e0eb42 100644 --- a/src/proxy/logging/Log.cc +++ b/src/proxy/logging/Log.cc @@ -54,8 +54,6 @@ #include "tscore/MgmtDefs.h" -#include - #define PERIODIC_TASKS_INTERVAL_FALLBACK 5 // Log global objects From ea6d8eed5f79272b39d709c5a5d217533be46673 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 19:35:33 -0700 Subject: [PATCH 15/25] Address warnings --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 21afb5d12c4..4f257443d1e 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -153,8 +153,8 @@ lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* [](TSHttpTxn /* txnp */, char *buf) -> int { sockaddr_in addr{ .sin_family = AF_INET, - .sin_addr.s_addr = inet_addr("192.168.0.1"), .sin_port = htons(80), + .sin_addr.s_addr = inet_addr("192.168.0.1"), }; return TSLogAddrMarshal(buf, reinterpret_cast(&addr)); }, From 3b1e57f3df8beb00ac7f90cdb389ce44fc65a2e0 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Fri, 13 Feb 2026 20:03:32 -0700 Subject: [PATCH 16/25] Address build errors --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 4f257443d1e..7a505f303c3 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -151,11 +151,10 @@ lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* TSLogFieldRegister( "modified cssn", "chi", TS_LOG_TYPE_ADDR, [](TSHttpTxn /* txnp */, char *buf) -> int { - sockaddr_in addr{ - .sin_family = AF_INET, - .sin_port = htons(80), - .sin_addr.s_addr = inet_addr("192.168.0.1"), - }; + 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); From 4ed806f154053a2f9542580789fde0c78a75df3e Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 16 Feb 2026 14:19:55 -0700 Subject: [PATCH 17/25] Add API documentation --- .../api/functions/TSLogFieldRegister.en.rst | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 doc/developer-guide/api/functions/TSLogFieldRegister.en.rst 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..906c834ddb7 --- /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 modify 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 modify 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 debug log. 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 function 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`. From b1d67e4d8e1671552fa00fb0a3fbac117d34bf6c Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 16 Feb 2026 14:54:20 -0700 Subject: [PATCH 18/25] Add documentation about LOG_INITIALIZED hook and event --- doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst | 6 ++++++ doc/developer-guide/api/types/TSEvent.en.rst | 4 ++++ 2 files changed, 10 insertions(+) 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/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 From 3c5fd7f17b64ba4f73590ab28a3f0e2f86df7d2d Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Mon, 16 Feb 2026 16:13:20 -0700 Subject: [PATCH 19/25] Fix typo --- .../api/functions/TSLogFieldRegister.en.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst index 906c834ddb7..2ac24e67af5 100644 --- a/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst +++ b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst @@ -21,7 +21,7 @@ TSLogFieldRegister ****************** -Registers a custom log field, or modify an existing log field with a new definition. +Registers a custom log field, or modifies an existing log field with a new definition. Synopsis ======== @@ -68,14 +68,14 @@ Synopsis Description =========== -The function registers or modify a log field for access log. This is useful if you want to log something that |TS| does not expose, +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 debug log. The `symbol` is the keyword you'd want to use on logging.yaml for +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 function such +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 From d82a3e0921a1d2df2ab6fbdf9df24d3ca54e6ae1 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 13:17:09 -0700 Subject: [PATCH 20/25] Fix typo INITIAZLIED --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 4 ++-- include/ts/apidefs.h.in | 4 ++-- src/traffic_server/traffic_server.cc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 7a505f303c3..2bd18c9567b 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -132,7 +132,7 @@ unmarshal_function_string(char **buf, char *dest, int len) int lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */) { - TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIAZLIED); + TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIALIZED); // This registers a custom log field "cstm". Dbg(dbg_ctl, "Registering cstm log field"); @@ -173,7 +173,7 @@ TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) } TSCont cont = TSContCreate(lifecycle_event_handler, nullptr); - TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, cont); + TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIALIZED_HOOK, cont); int argIndex; TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTM, "This is for cstm log field", &argIndex); diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index cb75a1fd2f4..f3b34ef7b39 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -362,7 +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_INITIAZLIED = 60108, + TS_EVENT_LIFECYCLE_LOG_INITIALIZED = 60108, TS_EVENT_INTERNAL_60200 = 60200, TS_EVENT_INTERNAL_60201 = 60201, @@ -580,7 +580,7 @@ enum TSLifecycleHookID { TS_LIFECYCLE_TASK_THREADS_READY_HOOK, TS_LIFECYCLE_SHUTDOWN_HOOK, TS_LIFECYCLE_SSL_SECRET_HOOK, - TS_LIFECYCLE_LOG_INITIAZLIED_HOOK, + TS_LIFECYCLE_LOG_INITIALIZED_HOOK, TS_LIFECYCLE_LAST_HOOK, }; diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index 5536e2400ef..9a249910590 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -2229,9 +2229,9 @@ main(int /* argc ATS_UNUSED */, const char **argv) } // Give plugins a chance to customize log fields - APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIAZLIED_HOOK); + APIHook *hook = g_lifecycle_hooks->get(TS_LIFECYCLE_LOG_INITIALIZED_HOOK); while (hook) { - hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIAZLIED, nullptr); + hook->invoke(TS_EVENT_LIFECYCLE_LOG_INITIALIZED, nullptr); hook = hook->next(); } From 5cc7352a79a96c73c8ddfbeb066ed95f5f82da9c Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 13:19:01 -0700 Subject: [PATCH 21/25] Remove an invalid assertion --- src/proxy/logging/LogField.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc index a6672a7d6db..55db25a8a4d 100644 --- a/src/proxy/logging/LogField.cc +++ b/src/proxy/logging/LogField.cc @@ -309,7 +309,6 @@ LogField::LogField(const char *name, const char *symbol, Type type, CustomMarsha ink_assert(m_name != nullptr); ink_assert(m_symbol != nullptr); ink_assert(m_type >= 0 && m_type < N_TYPES); - ink_assert(m_marshal_func != (MarshalFunc) nullptr); 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); From 8ad9a9f5f3bfafb1dc522ebbff8b6a0985aba7ff Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 13:26:18 -0700 Subject: [PATCH 22/25] Fix null termination --- src/api/InkAPI.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index b7e0346dfc3..000ff2dd278 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -9128,8 +9128,8 @@ TSLogAddrUnmarshal(char **buf, char *dest, int len) } if (!ats_is_ip(&endpoint) && !ats_is_unix(&endpoint)) { - *dest = '0'; - *dest = '\0'; + dest[0] = '0'; + dest[1] = '\0'; return {-1, 1}; } else if (ats_ip_ntop(&endpoint, dest, len)) { return {read_len, static_cast(::strlen(dest))}; From 46b78e8dce142b5d73e0cd5498ab1742a46e90c8 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 13:28:15 -0700 Subject: [PATCH 23/25] Update sample plugin description --- example/plugins/c-api/custom_logfield/custom_logfield.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc b/example/plugins/c-api/custom_logfield/custom_logfield.cc index 2bd18c9567b..975dd2900ac 100644 --- a/example/plugins/c-api/custom_logfield/custom_logfield.cc +++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc @@ -1,7 +1,7 @@ /** @file - This plugin counts the number of times every header has appeared. - Maintains separate counts for client and origin headers. + This plugin demonstrates custom log field registration and usage. + It populates custom log fields from per-transaction user arguments. @section license License From 4765b632c55642d213cb2311264eeacd89cf4713 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 13:54:46 -0700 Subject: [PATCH 24/25] Hide log type DINT --- include/ts/apidefs.h.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index f3b34ef7b39..fc1fb7576cc 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1577,9 +1577,9 @@ struct TSResponseAction { enum TSLogType { TS_LOG_TYPE_INT, - TS_LOG_TYPE_DINT, - TS_LOG_TYPE_STRING, - TS_LOG_TYPE_ADDR, + // 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, }; /* -------------------------------------------------------------------------- From 2243064fed9427210c07fd960e3c0f718216d433 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 17 Feb 2026 14:03:43 -0700 Subject: [PATCH 25/25] Add more comments --- include/ts/ts.h | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/include/ts/ts.h b/include/ts/ts.h index 406cbd3b2f1..c63febb3607 100644 --- a/include/ts/ts.h +++ b/include/ts/ts.h @@ -3225,11 +3225,46 @@ 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); -int TSLogStringMarshal(char *buf, std::string_view str); -int TSLogIntMarshal(char *buf, int64_t value); -int TSLogAddrMarshal(char *buf, sockaddr *addr); +/** + 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);