This is an automated email from the ASF dual-hosted git repository.
maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 40b99c7b57 Add support for custom logging field (#12872)
40b99c7b57 is described below
commit 40b99c7b57eec10ff8e549a6d6d2b87663804eea
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Tue Feb 24 14:40:31 2026 -0700
Add support for custom logging field (#12872)
This adds TSLogFieldRegister to enable plugins to add or redefine access
log fields.
---
.../api/functions/TSLifecycleHookAdd.en.rst | 6 +
.../api/functions/TSLogFieldRegister.en.rst | 99 +++++++++
doc/developer-guide/api/types/TSEvent.en.rst | 4 +
example/plugins/c-api/CMakeLists.txt | 1 +
.../c-api/custom_logfield/custom_logfield.cc | 230 +++++++++++++++++++++
include/proxy/logging/Log.h | 1 +
include/proxy/logging/LogAccess.h | 4 +
include/proxy/logging/LogField.h | 8 +
include/ts/apidefs.h.in | 18 +-
include/ts/ts.h | 44 ++++
src/api/InkAPI.cc | 141 +++++++++++++
src/proxy/logging/Log.cc | 19 +-
src/proxy/logging/LogAccess.cc | 7 +
src/proxy/logging/LogField.cc | 61 +++++-
src/traffic_server/traffic_server.cc | 10 +
15 files changed, 640 insertions(+), 13 deletions(-)
diff --git a/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst
b/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst
index e67ca32bc3..b9c3a70976 100644
--- a/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst
+++ b/doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst
@@ -120,6 +120,12 @@ Types
Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_SHUTDOWN` and
``nullptr`` data.
+ .. cpp:enumerator:: TS_LIFECYCLE_LOG_INITIALIZED_HOOK
+
+ Called after |TS| logging system is initialized but before logging
configuration is loaded.
+
+ Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_LOG_INITIALIZED`
and ``nullptr`` data.
+
.. struct:: TSPluginMsg
The data for the plugin message event :enumerator:`TS_EVENT_LIFECYCLE_MSG`.
diff --git a/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst
b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst
new file mode 100644
index 0000000000..4acdf40c84
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSLogFieldRegister.en.rst
@@ -0,0 +1,99 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed
+ with this work for additional information regarding copyright
+ ownership. The ASF licenses this file to you under the Apache
+ License, Version 2.0 (the "License"); you may not use this file
+ except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ permissions and limitations under the License.
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSLogFieldRegister
+******************
+
+Registers a custom log field, or modifies an existing log field with a new
definition.
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+ #include <ts/ts.h>
+
+.. function:: TSReturnCode TSLogFieldRegister(std::string_view name,
std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb,
TSLogUnmarshalCallback unmarshal_cb, bool replace = false);
+
+.. enum:: TSLogType
+
+ Specify the type of a log field
+
+ .. enumerator:: TS_LOG_TYPE_INT
+
+ Integer field.
+
+ .. enumerator:: TS_LOG_TYPE_STRING
+
+ String field.
+
+ .. enumerator:: TS_LOG_TYPE_ADDR
+
+ Address field. It supports IPv4 address, IPv6 address, and Unix Domain
Socket address (path).
+
+.. type:: int (*TSLogMarshalCallback)(TSHttpTxn, char *);
+
+ Callback signature for functions to marshal log fields.
+
+.. type:: std::tuple<int, int> (*TSLogUnmarshalCallback)(char **, char *, int);
+
+ Callback signature for functions to unmarshal log fields.
+
+.. function:: int TSLogStringMarshal(char *buf, std::string_view str);
+.. function:: int TSLogIntMarshal(char *buf, int64_t value);
+.. function:: int TSLogAddrMarshal(char *buf, sockaddr *addr);
+.. function:: std::tuple<int, int> TSLogStringUnmarshal(char **buf, char
*dest, int len);
+.. function:: std::tuple<int, int> TSLogIntUnmarshal(char **buf, char *dest,
int len);
+.. function:: std::tuple<int, int> TSLogAddrUnmarshal(char **buf, char *dest,
int len);
+
+ Predefined marshaling and unmarshaling functions.
+
+Description
+===========
+
+The function registers or modifies a log field for access log. This is useful
if you want to log something that |TS| does not expose,
+log plugin state, or redefine existing log fields.
+
+The `name` is a human friendly name, and only used for debugging. The `symbol`
is the keyword you'd want to use on logging.yaml for
+the log field. It needs to be unique unless you are replacing an existing
field by passing `true` to the optional argument
+`replace`, otherwise the API call fails.
+
+The `type` is the data type of a log field. You can log any data as a string
value, but please note that aggregating functions such
+as AVG and SUM are only available for integer log fields.
+
+In many cases, you don't need to write code for marshaling and unmarshaling
from scratch. The predefined functions are provided for
+your convenience, and you only needs to pass a value that you want to log,
+
+Example:
+
+ .. code-block:: cpp
+
+ TSLogFieldRegister("Example", "exmpl", TS_LOG_TYPE_INT,
+ [](TSHttpTxn txnp, char *buf) -> int {
+ return TSLogIntMarshal(buf, 123);
+ },
+ TSLogIntUnmarshal);
+
+Return Values
+=============
+
+:func:`TSLogFieldRegister` returns :enumerator:`TS_SUCCESS` if it successfully
registeres a new field, or :enumerator:`TS_ERROR` if it
+fails due to symbol conflict. If :arg:`replace` is set to `true`, the function
resolve the conflict by replacing the existing
+field definition with a new one, and returns :enumerator:`TS_SUCCESS`.
diff --git a/doc/developer-guide/api/types/TSEvent.en.rst
b/doc/developer-guide/api/types/TSEvent.en.rst
index 1f06546358..9de2fe9d01 100644
--- a/doc/developer-guide/api/types/TSEvent.en.rst
+++ b/doc/developer-guide/api/types/TSEvent.en.rst
@@ -198,6 +198,10 @@ Enumeration Members
The |TS| process has is shutting down.
+.. enumerator:: TS_EVENT_LIFECYCLE_LOG_INITIALIZED
+
+ The logging system is initialized.
+
.. enumerator:: TS_EVENT_INTERNAL_60200
.. enumerator:: TS_EVENT_INTERNAL_60201
diff --git a/example/plugins/c-api/CMakeLists.txt
b/example/plugins/c-api/CMakeLists.txt
index 678fe416dd..65594cd391 100644
--- a/example/plugins/c-api/CMakeLists.txt
+++ b/example/plugins/c-api/CMakeLists.txt
@@ -65,3 +65,4 @@ add_atsplugin(statistic ./statistic/statistic.cc)
add_atsplugin(protocol_stack ./protocol_stack/protocol_stack.cc)
add_atsplugin(client_context_dump ./client_context_dump/client_context_dump.cc)
target_link_libraries(client_context_dump PRIVATE OpenSSL::SSL
libswoc::libswoc)
+add_atsplugin(custom_logfield ./custom_logfield/custom_logfield.cc)
diff --git a/example/plugins/c-api/custom_logfield/custom_logfield.cc
b/example/plugins/c-api/custom_logfield/custom_logfield.cc
new file mode 100644
index 0000000000..975dd2900a
--- /dev/null
+++ b/example/plugins/c-api/custom_logfield/custom_logfield.cc
@@ -0,0 +1,230 @@
+/** @file
+
+ This plugin demonstrates custom log field registration and usage.
+ It populates custom log fields from per-transaction user arguments.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+
+DbgCtl dbg_ctl{"custom_logfield"};
+
+char PLUGIN_NAME[] = "custom_logfield";
+char VENDOR_NAME[] = "Apache Software Foundation";
+char SUPPORT_EMAIL[] = "[email protected]";
+char USER_ARG_CSTM[] = "cstm_field";
+char USER_ARG_CSTMI[] = "cstmi_field";
+char USER_ARG_CSSN[] = "cssn_field";
+
+int
+write_text_from_user_arg(TSHttpTxn txnp, char *buf, const char *user_arg_name)
+{
+ int len = 0;
+ int index;
+
+ if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, user_arg_name, &index,
nullptr) == TS_SUCCESS) {
+ Dbg(dbg_ctl, "User Arg Index: %d", index);
+ if (char *value = static_cast<char *>(TSUserArgGet(txnp, index)); value) {
+ Dbg(dbg_ctl, "Value: %s", value);
+ len = strlen(value);
+ if (buf) {
+ TSstrlcpy(buf, value, len + 1);
+ }
+ }
+ }
+ return len + 1;
+}
+
+int
+marshal_function_cstm(TSHttpTxn txnp, char *buf)
+{
+ if (buf) {
+ Dbg(dbg_ctl, "Marshaling a custom field cstm");
+ } else {
+ Dbg(dbg_ctl, "Marshaling a custom field cstm for size calculation");
+ }
+ return write_text_from_user_arg(txnp, buf, USER_ARG_CSTM);
+}
+
+int
+marshal_function_cssn(TSHttpTxn txnp, char *buf)
+{
+ if (buf) {
+ Dbg(dbg_ctl, "Marshaling a built-in field cssn");
+ } else {
+ Dbg(dbg_ctl, "Marshaling a built-in field cssn for size calculation");
+ }
+ return write_text_from_user_arg(txnp, buf, USER_ARG_CSSN);
+}
+
+int
+marshal_function_cstmi(TSHttpTxn txnp, char *buf)
+{
+ // This implementation is just to demonstrate marshaling an integer value.
+ // Predefined marshal function, TSLogIntMarshal, works for simple integer
values
+
+ int index;
+
+ if (buf) {
+ Dbg(dbg_ctl, "Marshaling a custom field cstmi");
+ } else {
+ Dbg(dbg_ctl, "Marshaling a custom field cstmi for size calculation");
+ }
+
+ if (buf) {
+ if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index,
nullptr) == TS_SUCCESS) {
+ Dbg(dbg_ctl, "User Arg Index: %d", index);
+ if (int64_t value = reinterpret_cast<int64_t>(TSUserArgGet(txnp,
index)); value) {
+ Dbg(dbg_ctl, "Value: %" PRId64, value);
+ *(reinterpret_cast<int64_t *>(buf)) = value;
+ }
+ }
+ }
+ return sizeof(int64_t);
+}
+
+std::tuple<int, int>
+unmarshal_function_string(char **buf, char *dest, int len)
+{
+ Dbg(dbg_ctl, "Unmarshaling a string field");
+
+ // This implementation is just to demonstrate unmarshaling a string value.
+ // Predefined unmarshal function, TSLogStringUnmarshal, works for simple
string values
+
+ int l = strlen(*buf);
+ Dbg(dbg_ctl, "Dest buf size: %d", len);
+ Dbg(dbg_ctl, "Unmarshaled value length: %d", l);
+ if (l < len) {
+ memcpy(dest, *buf, l);
+ Dbg(dbg_ctl, "Unmarshaled value: %.*s", l, dest);
+ return {
+ l, // The length of data read from buf
+ l // The length of data written to dest
+ };
+ } else {
+ return {-1, -1};
+ }
+}
+
+int
+lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void *
/* edata ATS_UNUSED */)
+{
+ TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIALIZED);
+
+ // This registers a custom log field "cstm".
+ Dbg(dbg_ctl, "Registering cstm log field");
+ TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING,
marshal_function_cstm, unmarshal_function_string);
+
+ // This replaces marshaling and unmarshaling functions for a built-in log
field "cssn".
+ Dbg(dbg_ctl, "Overriding cssn log field");
+ TSLogFieldRegister("modified cssn", "cssn", TS_LOG_TYPE_STRING,
marshal_function_cssn, TSLogStringUnmarshal, true);
+
+ // This registers a custom log field "cstmi"
+ Dbg(dbg_ctl, "Registering cstmi log field");
+ TSLogFieldRegister("custom integer log field", "cstmi", TS_LOG_TYPE_INT,
marshal_function_cstmi, TSLogIntUnmarshal);
+
+ // This replaces marshaling and unmarshaling functions for a built-in log
field "chi".
+ Dbg(dbg_ctl, "Overriding chi log field");
+ TSLogFieldRegister(
+ "modified cssn", "chi", TS_LOG_TYPE_ADDR,
+ [](TSHttpTxn /* txnp */, char *buf) -> int {
+ sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(80);
+ addr.sin_addr.s_addr = inet_addr("192.168.0.1");
+ return TSLogAddrMarshal(buf, reinterpret_cast<sockaddr *>(&addr));
+ },
+ TSLogAddrUnmarshal, true);
+
+ return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
+{
+ Dbg(dbg_ctl, "Initializing plugin");
+
+ TSPluginRegistrationInfo info = {PLUGIN_NAME, VENDOR_NAME, SUPPORT_EMAIL};
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[%s](%s) Plugin registration failed. \n", PLUGIN_NAME,
__FUNCTION__);
+ }
+
+ TSCont cont = TSContCreate(lifecycle_event_handler, nullptr);
+ TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIALIZED_HOOK, cont);
+
+ int argIndex;
+ TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTM, "This is for cstm log
field", &argIndex);
+ Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
+ TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSSN, "This is for cssn log
field", &argIndex);
+ Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
+ TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTMI, "This is for cstmi
log field", &argIndex);
+ Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
+}
+
+TSReturnCode
+TSRemapInit(TSRemapInterface *, char *, int)
+{
+ return TS_SUCCESS;
+}
+
+TSReturnCode
+TSRemapNewInstance(int, char **, void **, char *, int)
+{
+ return TS_SUCCESS;
+}
+
+void
+TSRemapDeleteInstance(void *)
+{
+}
+
+TSRemapStatus
+TSRemapDoRemap(void *, TSHttpTxn txn, TSRemapRequestInfo *)
+{
+ Dbg(dbg_ctl, "Remapping");
+
+ int index;
+
+ // Store a string value for cstm field
+ if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTM, &index,
nullptr) == TS_SUCCESS) {
+ Dbg(dbg_ctl, "User Arg Index: %d", index);
+ TSUserArgSet(txn, index, const_cast<char *>("abc"));
+ }
+
+ // Store a string value for cssn field
+ if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSSN, &index,
nullptr) == TS_SUCCESS) {
+ Dbg(dbg_ctl, "User Arg Index: %d", index);
+ TSUserArgSet(txn, index, const_cast<char *>("xyz"));
+ }
+
+ // Store an integer value for cstmi field
+ if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index,
nullptr) == TS_SUCCESS) {
+ Dbg(dbg_ctl, "User Arg Index: %d", index);
+ TSUserArgSet(txn, index, reinterpret_cast<void *>(43));
+ }
+
+ return TSREMAP_NO_REMAP;
+}
diff --git a/include/proxy/logging/Log.h b/include/proxy/logging/Log.h
index 4cf40db096..ef7df731c0 100644
--- a/include/proxy/logging/Log.h
+++ b/include/proxy/logging/Log.h
@@ -155,6 +155,7 @@ public:
// main interface
static void init(int configFlags = 0);
static void init_fields();
+ static void load_config();
static bool
transaction_logging_enabled()
diff --git a/include/proxy/logging/LogAccess.h
b/include/proxy/logging/LogAccess.h
index c50bb359c5..1a799ca3b0 100644
--- a/include/proxy/logging/LogAccess.h
+++ b/include/proxy/logging/LogAccess.h
@@ -307,6 +307,10 @@ public:
int marshal_milestones_csv(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 dcc5b23bb0..b883b7c558 100644
--- a/include/proxy/logging/LogField.h
+++ b/include/proxy/logging/LogField.h
@@ -26,6 +26,7 @@
#include <string_view>
#include <string>
#include <variant>
+#include <tuple>
#include "tscore/ink_inet.h"
#include "tscore/ink_platform.h"
@@ -84,6 +85,8 @@ public:
using UnmarshalFuncWithSlice = int (*)(char **, char *, int, LogSlice *,
LogEscapeType);
using UnmarshalFuncWithMap = int (*)(char **, char *, int, const
Ptr<LogFieldAliasMap> &);
using SetFunc = void (LogAccess::*)(char *, int);
+ using CustomMarshalFunc = int (*)(void *, char *);
+ using CustomUnmarshalFunc = std::tuple<int, int> (*)(char **, char *,
int);
using VarUnmarshalFuncSliceOnly = std::variant<UnmarshalFunc,
UnmarshalFuncWithSlice>;
using VarUnmarshalFunc = std::variant<decltype(nullptr),
UnmarshalFunc, UnmarshalFuncWithSlice, UnmarshalFuncWithMap>;
@@ -132,6 +135,8 @@ public:
LogField(const char *name, const char *symbol, Type type, MarshalFunc
marshal, UnmarshalFuncWithMap unmarshal,
const Ptr<LogFieldAliasMap> &map, SetFunc _setFunc = nullptr);
+ LogField(const char *name, const char *symbol, Type type, CustomMarshalFunc
custom_marshal, CustomUnmarshalFunc custom_unmarshal);
+
LogField(const char *field, Container container);
LogField(const LogField &rhs);
~LogField();
@@ -207,6 +212,8 @@ private:
SetFunc m_set_func;
TSMilestonesType milestone_from_m_name();
int milestones_from_m_name(TSMilestonesType *m1,
TSMilestonesType *m2);
+ CustomMarshalFunc m_custom_marshal_func = nullptr;
+ CustomUnmarshalFunc m_custom_unmarshal_func = nullptr;
public:
LINK(LogField, link);
@@ -234,6 +241,7 @@ public:
void clear();
void add(LogField *field, bool copy = true);
+ void remove(LogField *field);
LogField *find_by_name(const char *name) const;
LogField *find_by_symbol(const char *symbol) const;
unsigned marshal_len(LogAccess *lad);
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index 5515aa7665..c979ac8e94 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -46,6 +46,7 @@
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <tuple>
/** 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,
};
@@ -1084,9 +1087,11 @@ using TSRemapPluginInfo = struct
tsapi_remap_plugin_info *;
using TSFetchSM = struct tsapi_fetchsm *;
-using TSThreadFunc = void *(*)(void *data);
-using TSEventFunc = int (*)(TSCont contp, TSEvent event, void *edata);
-using TSConfigDestroyFunc = void (*)(void *data);
+using TSThreadFunc = void *(*)(void *data);
+using TSEventFunc = int (*)(TSCont contp, TSEvent event, void
*edata);
+using TSConfigDestroyFunc = void (*)(void *data);
+using TSLogMarshalCallback = int (*)(TSHttpTxn, char *);
+using TSLogUnmarshalCallback = std::tuple<int, int> (*)(char **, char *, int);
struct TSFetchEvent {
int success_event_id;
@@ -1571,6 +1576,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 d62d5d8156..c63febb360 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<int, int> TSLogStringUnmarshal(char **buf, char *dest, int len);
+
+/**
+ Helper function to unmarshal an integer
+*/
+std::tuple<int, int> TSLogIntUnmarshal(char **buf, char *dest, int len);
+
+/**
+ Helper function to unmarshal an address
+*/
+std::tuple<int, int> TSLogAddrUnmarshal(char **buf, char *dest, int len);
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 462d15cf38..1a4b734e28 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -26,6 +26,7 @@
#include <unordered_map>
#include <string_view>
#include <string>
+#include <charconv>
#include "iocore/net/NetVConnection.h"
#include "iocore/net/NetHandler.h"
@@ -9017,3 +9018,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<LogField::Type>(type),
+
reinterpret_cast<LogField::CustomMarshalFunc>(marshal_cb), unmarshal_cb);
+ Log::global_field_list.add(field, false);
+ Log::field_symbol_hash.emplace(symbol.data(), field);
+
+ return TS_SUCCESS;
+}
+
+int
+TSLogStringMarshal(char *buf, std::string_view str)
+{
+ if (buf) {
+ ink_strlcpy(buf, str.data(), str.length() + 1);
+ }
+ return str.length() + 1;
+}
+
+std::tuple<int, int>
+TSLogStringUnmarshal(char **buf, char *dest, int len)
+{
+ // We cannot use LogAccess::unmarshal_str, etc. here because those internal
+ // functions take care of log buffer alignment. This function needs to be
+ // implemented as if it's a piece of code in plugin code, which is unaware
+ // of the alignment.
+ if (int l = strlen(*buf); l < len) {
+ memcpy(dest, *buf, l);
+ return {l, l};
+ } else {
+ return {-1, -1};
+ }
+}
+
+int
+TSLogIntMarshal(char *buf, int64_t value)
+{
+ if (buf) {
+ *(reinterpret_cast<int64_t *>(buf)) = value;
+ }
+ return sizeof(int64_t);
+}
+
+std::tuple<int, int>
+TSLogIntUnmarshal(char **buf, char *dest, int len)
+{
+ int64_t val = *(reinterpret_cast<int64_t *>(*buf));
+ auto [end, err] = std::to_chars(dest, dest + len, val);
+ if (err == std::errc()) {
+ *end = '\0';
+ return {sizeof(uint64_t), end - dest};
+ }
+
+ return {-1, -1};
+}
+
+int
+TSLogAddrMarshal(char *buf, sockaddr *addr)
+{
+ LogFieldIpStorage data;
+ int len = sizeof(data._ip);
+
+ if (nullptr == addr) {
+ data._ip._family = AF_UNSPEC;
+ } else if (ats_is_ip4(addr)) {
+ if (buf) {
+ data._ip4._family = AF_INET;
+ data._ip4._addr = ats_ip4_addr_cast(addr);
+ }
+ len = sizeof(data._ip4);
+ } else if (ats_is_ip6(addr)) {
+ if (buf) {
+ data._ip6._family = AF_INET6;
+ data._ip6._addr = ats_ip6_addr_cast(addr);
+ }
+ len = sizeof(data._ip6);
+ } else if (ats_is_unix(addr)) {
+ if (buf) {
+ data._un._family = AF_UNIX;
+ strncpy(data._un._path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE);
+ }
+ len = sizeof(data._un);
+ } else {
+ data._ip._family = AF_UNSPEC;
+ }
+
+ if (buf) {
+ memcpy(buf, &data, len);
+ }
+ return len;
+}
+
+std::tuple<int, int>
+TSLogAddrUnmarshal(char **buf, char *dest, int len)
+{
+ IpEndpoint endpoint;
+ int read_len = sizeof(LogFieldIp);
+
+ LogFieldIp *raw = reinterpret_cast<LogFieldIp *>(*buf);
+ if (AF_INET == raw->_family) {
+ LogFieldIp4 *ip4 = static_cast<LogFieldIp4 *>(raw);
+ ats_ip4_set(&endpoint, ip4->_addr);
+ read_len = sizeof(*ip4);
+ } else if (AF_INET6 == raw->_family) {
+ LogFieldIp6 *ip6 = static_cast<LogFieldIp6 *>(raw);
+ ats_ip6_set(&endpoint, ip6->_addr);
+ read_len = sizeof(*ip6);
+ } else if (AF_UNIX == raw->_family) {
+ LogFieldUn *un = static_cast<LogFieldUn *>(raw);
+ ats_unix_set(&endpoint, un->_path, TS_UNIX_SIZE);
+ read_len = sizeof(*un);
+ } else {
+ ats_ip_invalidate(&endpoint);
+ }
+
+ if (!ats_is_ip(&endpoint) && !ats_is_unix(&endpoint)) {
+ dest[0] = '0';
+ dest[1] = '\0';
+ return {-1, 1};
+ } else if (ats_ip_ntop(&endpoint, dest, len)) {
+ return {read_len, static_cast<int>(::strlen(dest))};
+ }
+
+ return {-1, -1};
+}
diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc
index 3bca7c5333..29b0949698 100644
--- a/src/proxy/logging/Log.cc
+++ b/src/proxy/logging/Log.cc
@@ -1148,13 +1148,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
@@ -1179,6 +1172,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 336238c345..7d2bcb54da 100644
--- a/src/proxy/logging/LogAccess.cc
+++ b/src/proxy/logging/LogAccess.cc
@@ -474,6 +474,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<LogFieldAliasMap> &map, const char *msg)
{
diff --git a/src/proxy/logging/LogField.cc b/src/proxy/logging/LogField.cc
index 039aca32a7..914363ced6 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 811539f143..9a24991059 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!");
}