Skip to content

Commit 40b99c7

Browse files
authored
Add support for custom logging field (#12872)
This adds TSLogFieldRegister to enable plugins to add or redefine access log fields.
1 parent bc464c5 commit 40b99c7

15 files changed

Lines changed: 640 additions & 13 deletions

File tree

doc/developer-guide/api/functions/TSLifecycleHookAdd.en.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ Types
120120

121121
Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_SHUTDOWN` and ``nullptr`` data.
122122

123+
.. cpp:enumerator:: TS_LIFECYCLE_LOG_INITIALIZED_HOOK
124+
125+
Called after |TS| logging system is initialized but before logging configuration is loaded.
126+
127+
Invoked with the event :enumerator:`TS_EVENT_LIFECYCLE_LOG_INITIALIZED` and ``nullptr`` data.
128+
123129
.. struct:: TSPluginMsg
124130

125131
The data for the plugin message event :enumerator:`TS_EVENT_LIFECYCLE_MSG`.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one or more
2+
contributor license agreements. See the NOTICE file distributed
3+
with this work for additional information regarding copyright
4+
ownership. The ASF licenses this file to you under the Apache
5+
License, Version 2.0 (the "License"); you may not use this file
6+
except in compliance with the License. You may obtain a copy of
7+
the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
implied. See the License for the specific language governing
15+
permissions and limitations under the License.
16+
17+
.. include:: ../../../common.defs
18+
19+
.. default-domain:: cpp
20+
21+
TSLogFieldRegister
22+
******************
23+
24+
Registers a custom log field, or modifies an existing log field with a new definition.
25+
26+
Synopsis
27+
========
28+
29+
.. code-block:: cpp
30+
31+
#include <ts/ts.h>
32+
33+
.. function:: TSReturnCode TSLogFieldRegister(std::string_view name, std::string_view symbol, TSLogType type, TSLogMarshalCallback marshal_cb, TSLogUnmarshalCallback unmarshal_cb, bool replace = false);
34+
35+
.. enum:: TSLogType
36+
37+
Specify the type of a log field
38+
39+
.. enumerator:: TS_LOG_TYPE_INT
40+
41+
Integer field.
42+
43+
.. enumerator:: TS_LOG_TYPE_STRING
44+
45+
String field.
46+
47+
.. enumerator:: TS_LOG_TYPE_ADDR
48+
49+
Address field. It supports IPv4 address, IPv6 address, and Unix Domain Socket address (path).
50+
51+
.. type:: int (*TSLogMarshalCallback)(TSHttpTxn, char *);
52+
53+
Callback signature for functions to marshal log fields.
54+
55+
.. type:: std::tuple<int, int> (*TSLogUnmarshalCallback)(char **, char *, int);
56+
57+
Callback signature for functions to unmarshal log fields.
58+
59+
.. function:: int TSLogStringMarshal(char *buf, std::string_view str);
60+
.. function:: int TSLogIntMarshal(char *buf, int64_t value);
61+
.. function:: int TSLogAddrMarshal(char *buf, sockaddr *addr);
62+
.. function:: std::tuple<int, int> TSLogStringUnmarshal(char **buf, char *dest, int len);
63+
.. function:: std::tuple<int, int> TSLogIntUnmarshal(char **buf, char *dest, int len);
64+
.. function:: std::tuple<int, int> TSLogAddrUnmarshal(char **buf, char *dest, int len);
65+
66+
Predefined marshaling and unmarshaling functions.
67+
68+
Description
69+
===========
70+
71+
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,
72+
log plugin state, or redefine existing log fields.
73+
74+
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
75+
the log field. It needs to be unique unless you are replacing an existing field by passing `true` to the optional argument
76+
`replace`, otherwise the API call fails.
77+
78+
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
79+
as AVG and SUM are only available for integer log fields.
80+
81+
In many cases, you don't need to write code for marshaling and unmarshaling from scratch. The predefined functions are provided for
82+
your convenience, and you only needs to pass a value that you want to log,
83+
84+
Example:
85+
86+
.. code-block:: cpp
87+
88+
TSLogFieldRegister("Example", "exmpl", TS_LOG_TYPE_INT,
89+
[](TSHttpTxn txnp, char *buf) -> int {
90+
return TSLogIntMarshal(buf, 123);
91+
},
92+
TSLogIntUnmarshal);
93+
94+
Return Values
95+
=============
96+
97+
:func:`TSLogFieldRegister` returns :enumerator:`TS_SUCCESS` if it successfully registeres a new field, or :enumerator:`TS_ERROR` if it
98+
fails due to symbol conflict. If :arg:`replace` is set to `true`, the function resolve the conflict by replacing the existing
99+
field definition with a new one, and returns :enumerator:`TS_SUCCESS`.

doc/developer-guide/api/types/TSEvent.en.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ Enumeration Members
198198

199199
The |TS| process has is shutting down.
200200

201+
.. enumerator:: TS_EVENT_LIFECYCLE_LOG_INITIALIZED
202+
203+
The logging system is initialized.
204+
201205
.. enumerator:: TS_EVENT_INTERNAL_60200
202206

203207
.. enumerator:: TS_EVENT_INTERNAL_60201

example/plugins/c-api/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ add_atsplugin(statistic ./statistic/statistic.cc)
6565
add_atsplugin(protocol_stack ./protocol_stack/protocol_stack.cc)
6666
add_atsplugin(client_context_dump ./client_context_dump/client_context_dump.cc)
6767
target_link_libraries(client_context_dump PRIVATE OpenSSL::SSL libswoc::libswoc)
68+
add_atsplugin(custom_logfield ./custom_logfield/custom_logfield.cc)
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/** @file
2+
3+
This plugin demonstrates custom log field registration and usage.
4+
It populates custom log fields from per-transaction user arguments.
5+
6+
@section license License
7+
8+
Licensed to the Apache Software Foundation (ASF) under one
9+
or more contributor license agreements. See the NOTICE file
10+
distributed with this work for additional information
11+
regarding copyright ownership. The ASF licenses this file
12+
to you under the Apache License, Version 2.0 (the
13+
"License"); you may not use this file except in compliance
14+
with the License. You may obtain a copy of the License at
15+
16+
http://www.apache.org/licenses/LICENSE-2.0
17+
18+
Unless required by applicable law or agreed to in writing, software
19+
distributed under the License is distributed on an "AS IS" BASIS,
20+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
See the License for the specific language governing permissions and
22+
limitations under the License.
23+
*/
24+
25+
#include <inttypes.h>
26+
#include <sys/socket.h>
27+
#include <netinet/in.h>
28+
#include <arpa/inet.h>
29+
30+
#include <ts/ts.h>
31+
#include <ts/remap.h>
32+
33+
DbgCtl dbg_ctl{"custom_logfield"};
34+
35+
char PLUGIN_NAME[] = "custom_logfield";
36+
char VENDOR_NAME[] = "Apache Software Foundation";
37+
char SUPPORT_EMAIL[] = "dev@trafficserver.apache.org";
38+
char USER_ARG_CSTM[] = "cstm_field";
39+
char USER_ARG_CSTMI[] = "cstmi_field";
40+
char USER_ARG_CSSN[] = "cssn_field";
41+
42+
int
43+
write_text_from_user_arg(TSHttpTxn txnp, char *buf, const char *user_arg_name)
44+
{
45+
int len = 0;
46+
int index;
47+
48+
if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, user_arg_name, &index, nullptr) == TS_SUCCESS) {
49+
Dbg(dbg_ctl, "User Arg Index: %d", index);
50+
if (char *value = static_cast<char *>(TSUserArgGet(txnp, index)); value) {
51+
Dbg(dbg_ctl, "Value: %s", value);
52+
len = strlen(value);
53+
if (buf) {
54+
TSstrlcpy(buf, value, len + 1);
55+
}
56+
}
57+
}
58+
return len + 1;
59+
}
60+
61+
int
62+
marshal_function_cstm(TSHttpTxn txnp, char *buf)
63+
{
64+
if (buf) {
65+
Dbg(dbg_ctl, "Marshaling a custom field cstm");
66+
} else {
67+
Dbg(dbg_ctl, "Marshaling a custom field cstm for size calculation");
68+
}
69+
return write_text_from_user_arg(txnp, buf, USER_ARG_CSTM);
70+
}
71+
72+
int
73+
marshal_function_cssn(TSHttpTxn txnp, char *buf)
74+
{
75+
if (buf) {
76+
Dbg(dbg_ctl, "Marshaling a built-in field cssn");
77+
} else {
78+
Dbg(dbg_ctl, "Marshaling a built-in field cssn for size calculation");
79+
}
80+
return write_text_from_user_arg(txnp, buf, USER_ARG_CSSN);
81+
}
82+
83+
int
84+
marshal_function_cstmi(TSHttpTxn txnp, char *buf)
85+
{
86+
// This implementation is just to demonstrate marshaling an integer value.
87+
// Predefined marshal function, TSLogIntMarshal, works for simple integer values
88+
89+
int index;
90+
91+
if (buf) {
92+
Dbg(dbg_ctl, "Marshaling a custom field cstmi");
93+
} else {
94+
Dbg(dbg_ctl, "Marshaling a custom field cstmi for size calculation");
95+
}
96+
97+
if (buf) {
98+
if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index, nullptr) == TS_SUCCESS) {
99+
Dbg(dbg_ctl, "User Arg Index: %d", index);
100+
if (int64_t value = reinterpret_cast<int64_t>(TSUserArgGet(txnp, index)); value) {
101+
Dbg(dbg_ctl, "Value: %" PRId64, value);
102+
*(reinterpret_cast<int64_t *>(buf)) = value;
103+
}
104+
}
105+
}
106+
return sizeof(int64_t);
107+
}
108+
109+
std::tuple<int, int>
110+
unmarshal_function_string(char **buf, char *dest, int len)
111+
{
112+
Dbg(dbg_ctl, "Unmarshaling a string field");
113+
114+
// This implementation is just to demonstrate unmarshaling a string value.
115+
// Predefined unmarshal function, TSLogStringUnmarshal, works for simple string values
116+
117+
int l = strlen(*buf);
118+
Dbg(dbg_ctl, "Dest buf size: %d", len);
119+
Dbg(dbg_ctl, "Unmarshaled value length: %d", l);
120+
if (l < len) {
121+
memcpy(dest, *buf, l);
122+
Dbg(dbg_ctl, "Unmarshaled value: %.*s", l, dest);
123+
return {
124+
l, // The length of data read from buf
125+
l // The length of data written to dest
126+
};
127+
} else {
128+
return {-1, -1};
129+
}
130+
}
131+
132+
int
133+
lifecycle_event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, void * /* edata ATS_UNUSED */)
134+
{
135+
TSAssert(event == TS_EVENT_LIFECYCLE_LOG_INITIALIZED);
136+
137+
// This registers a custom log field "cstm".
138+
Dbg(dbg_ctl, "Registering cstm log field");
139+
TSLogFieldRegister("custom log field", "cstm", TS_LOG_TYPE_STRING, marshal_function_cstm, unmarshal_function_string);
140+
141+
// This replaces marshaling and unmarshaling functions for a built-in log field "cssn".
142+
Dbg(dbg_ctl, "Overriding cssn log field");
143+
TSLogFieldRegister("modified cssn", "cssn", TS_LOG_TYPE_STRING, marshal_function_cssn, TSLogStringUnmarshal, true);
144+
145+
// This registers a custom log field "cstmi"
146+
Dbg(dbg_ctl, "Registering cstmi log field");
147+
TSLogFieldRegister("custom integer log field", "cstmi", TS_LOG_TYPE_INT, marshal_function_cstmi, TSLogIntUnmarshal);
148+
149+
// This replaces marshaling and unmarshaling functions for a built-in log field "chi".
150+
Dbg(dbg_ctl, "Overriding chi log field");
151+
TSLogFieldRegister(
152+
"modified cssn", "chi", TS_LOG_TYPE_ADDR,
153+
[](TSHttpTxn /* txnp */, char *buf) -> int {
154+
sockaddr_in addr;
155+
addr.sin_family = AF_INET;
156+
addr.sin_port = htons(80);
157+
addr.sin_addr.s_addr = inet_addr("192.168.0.1");
158+
return TSLogAddrMarshal(buf, reinterpret_cast<sockaddr *>(&addr));
159+
},
160+
TSLogAddrUnmarshal, true);
161+
162+
return TS_SUCCESS;
163+
}
164+
165+
void
166+
TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
167+
{
168+
Dbg(dbg_ctl, "Initializing plugin");
169+
170+
TSPluginRegistrationInfo info = {PLUGIN_NAME, VENDOR_NAME, SUPPORT_EMAIL};
171+
if (TSPluginRegister(&info) != TS_SUCCESS) {
172+
TSError("[%s](%s) Plugin registration failed. \n", PLUGIN_NAME, __FUNCTION__);
173+
}
174+
175+
TSCont cont = TSContCreate(lifecycle_event_handler, nullptr);
176+
TSLifecycleHookAdd(TS_LIFECYCLE_LOG_INITIALIZED_HOOK, cont);
177+
178+
int argIndex;
179+
TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTM, "This is for cstm log field", &argIndex);
180+
Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
181+
TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSSN, "This is for cssn log field", &argIndex);
182+
Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
183+
TSUserArgIndexReserve(TS_USER_ARGS_TXN, USER_ARG_CSTMI, "This is for cstmi log field", &argIndex);
184+
Dbg(dbg_ctl, "User Arg Index: %d", argIndex);
185+
}
186+
187+
TSReturnCode
188+
TSRemapInit(TSRemapInterface *, char *, int)
189+
{
190+
return TS_SUCCESS;
191+
}
192+
193+
TSReturnCode
194+
TSRemapNewInstance(int, char **, void **, char *, int)
195+
{
196+
return TS_SUCCESS;
197+
}
198+
199+
void
200+
TSRemapDeleteInstance(void *)
201+
{
202+
}
203+
204+
TSRemapStatus
205+
TSRemapDoRemap(void *, TSHttpTxn txn, TSRemapRequestInfo *)
206+
{
207+
Dbg(dbg_ctl, "Remapping");
208+
209+
int index;
210+
211+
// Store a string value for cstm field
212+
if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTM, &index, nullptr) == TS_SUCCESS) {
213+
Dbg(dbg_ctl, "User Arg Index: %d", index);
214+
TSUserArgSet(txn, index, const_cast<char *>("abc"));
215+
}
216+
217+
// Store a string value for cssn field
218+
if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSSN, &index, nullptr) == TS_SUCCESS) {
219+
Dbg(dbg_ctl, "User Arg Index: %d", index);
220+
TSUserArgSet(txn, index, const_cast<char *>("xyz"));
221+
}
222+
223+
// Store an integer value for cstmi field
224+
if (TSUserArgIndexNameLookup(TS_USER_ARGS_TXN, USER_ARG_CSTMI, &index, nullptr) == TS_SUCCESS) {
225+
Dbg(dbg_ctl, "User Arg Index: %d", index);
226+
TSUserArgSet(txn, index, reinterpret_cast<void *>(43));
227+
}
228+
229+
return TSREMAP_NO_REMAP;
230+
}

include/proxy/logging/Log.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class Log
155155
// main interface
156156
static void init(int configFlags = 0);
157157
static void init_fields();
158+
static void load_config();
158159

159160
static bool
160161
transaction_logging_enabled()

include/proxy/logging/LogAccess.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ class LogAccess
307307
int marshal_milestones_csv(char *buf);
308308

309309
void set_http_header_field(LogField::Container container, char *field, char *buf, int len);
310+
311+
// Plugin
312+
int marshal_custom_field(char *buf, LogField::CustomMarshalFunc plugin_marshal_func);
313+
310314
//
311315
// unmarshalling routines
312316
//

0 commit comments

Comments
 (0)