Skip to content

Commit 9ea0223

Browse files
committed
first draft of eventlogger rewrite
1 parent 8c01341 commit 9ea0223

File tree

1 file changed

+196
-83
lines changed

1 file changed

+196
-83
lines changed

src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc

+196-83
Original file line numberDiff line numberDiff line change
@@ -14,125 +14,238 @@
1414
See the License for the specific language governing permissions and
1515
limitations under the License.
1616
////
17-
= Event Logging
18-
19-
The `EventLogger` class provides a simple mechanism for logging events
20-
that occur in an application. While the `EventLogger` is useful as a way
21-
of initiating events that should be processed by an audit Logging
22-
system, by itself it does not implement any of the features an audit
23-
logging system would require such as guaranteed delivery.
24-
25-
The recommended way of using the `EventLogger` in a typical web
26-
application is to populate the `ThreadContext` Map with data that is
27-
related to the entire lifespan of the request such as the user's id, the
28-
user's IP address, the product name, etc. This can easily be done in a
29-
servlet filter where the `ThreadContext` Map can also be cleared at the
30-
end of the request. When an event that needs to be recorded occurs a
31-
`StructuredDataMessage` should be created and populated. Then call
32-
`EventLogger.logEvent(msg)` where msg is a reference to the
33-
`StructuredDataMessage`.
34-
35-
[source,java]
17+
= Event Logging with Log4j
18+
19+
The `EventLogger` class provides a mechanism for logging significant events
20+
in an application using structured data. This approach is beneficial for
21+
tracking user actions, monitoring system behavior, and debugging issues.
22+
23+
Theoretically, every `Logger` can be used to perform this kind of action;
24+
however, the `EventLogger` class provides a shortcut to log events with structured data
25+
since it allows for a static method to log events.
26+
27+
== Advantages of Structured Logging
28+
29+
Structured logging means events Log4j records events with detailed and structured information.
30+
That way, logs are easier to read and process. They provide better context and
31+
are also more consistent than plain text logs.
32+
33+
== Integration with Syslog
34+
35+
Log4j complies with Syslogs RFC5424 standard.
36+
This feature allows for easy integration with existing log management and monitoring systems.
37+
38+
== Example Configuration
39+
40+
To configure Log4j to output logs in Syslog (RFC5424) format, one needs to use the link:../javadoc/log4j-core/org/apache/logging/log4j/core/layout/StructuredDataLayout.html[`StructuredDataLayout`] layout.
41+
Developers can use the following configuration to log events to a local Syslog server:
42+
43+
[source, xml]
44+
----
45+
<Appenders>
46+
<Syslog name="Syslog" host="localhost" port="514"> <1>
47+
<StructuredDataLayout complete="true" /> <2>
48+
</Syslog>
49+
</Appenders>
50+
51+
<Loggers>
52+
<Logger name="MyApp" level="info" additivity="false">
53+
<AppenderRef ref="Syslog"/>
54+
</Logger>
55+
</Loggers>
56+
----
57+
<1> The `Syslog` appender sends logs to a local Syslog server.
58+
<2> The `StructuredDataLayout` layout formats logs in RFC5424 format.
59+
60+
Of course, sending logs to a Syslog server is unnecessary.
61+
Developers can use the `StructuredDataLayout` layout with any other appender, such as `FileAppender` or `ConsoleAppender`.
62+
63+
As an example, the output could look like this:
64+
65+
[source, text]
66+
----
67+
<165>1 2024-05-16T12:34:56.789Z myapp MyApp - ID47 [transfer@12345 toAccount="123456" fromAccount="654321" amount="1000"] User 654321 has transferred 1000 to account 123456
68+
----
69+
70+
== Using the `EventLogger`
71+
72+
The `EventLogger` class provides a simple way to log structured events.
73+
It uses the `StructuredDataMessage` class to create structured log messages.
74+
75+
Assume a simple application that performs funds transfers between accounts.
76+
This application should send a certain amount of funds from one account to another and log the event.
77+
78+
The account class is defined as follows, with a unique ID and a balance:
79+
80+
[source, java]
81+
----
82+
class Account {
83+
private String id;
84+
private long balance;
85+
// Getters and setters omitted for brevity
86+
}
87+
----
88+
89+
The `transfer()` method transfers funds between two accounts and logs the event.
90+
It needs to take two accounts and the amount to transfer as parameters.
91+
92+
Apart from the key-value pairs provided in the map of the `StructuredDataMessage,`
93+
the `StructuredDataMessage` also takes a unique ID, a free-form message, and a type as parameters.
94+
95+
The free-form message is a human-readable description of the event.
96+
This message is good for operators who need to understand the event quickly,
97+
but not so much for automated processing.
98+
99+
[source, java]
100+
----
101+
public static String transferFunds(Account toAccount, Account fromAccount, long amount) {
102+
toAccount.deposit(amount);
103+
fromAccount.withdraw(amount);
104+
105+
// Create a unique ID for the transaction
106+
String confirm = UUID.randomUUID().toString();
107+
108+
String freeFormMessage = "User " + fromAccount.getId() + " has transferred " + amount + " to account " + toAccount.getId(); <1>
109+
110+
// Create the StructuredDataMessage
111+
StructuredDataMessage msg = new StructuredDataMessage(confirm, freeFormMessage, "transfer"); <2>
112+
msg.put("toAccount", toAccount.getId()); <3>
113+
msg.put("fromAccount", fromAccount.getId());
114+
msg.put("amount", amount);
115+
116+
// Log the event
117+
EventLogger.logEvent(msg); <4>
118+
119+
return confirm;
120+
}
121+
----
122+
<1> The free-form message is a human-readable description of the event.
123+
<2> The `StructuredDataMessage` constructor takes an ID, the free-form message, and a type.
124+
<3> Developers can add key-value pairs to the message.
125+
<4> The `EventLogger` logs the event.
126+
127+
That way, the `transferFunds()` method logs the event with structured data
128+
by using the `EventLogger`.
129+
130+
== Web Application Example
131+
132+
In a web application, developers can use a servlet filter to populate the
133+
`ThreadContext` map with data related to the request.
134+
135+
The following example demonstrates how a `Filter` could investigate the request
136+
and populate the `ThreadContext` map with data such as the user's IP address,
137+
the user's login ID, the server's hostname, the product name, the locale, and the timezone.
138+
139+
[source, java]
36140
----
37141
import org.apache.logging.log4j.ThreadContext;
38142
import org.apache.commons.lang.time.DateUtils;
39143
40-
import javax.servlet.Filter;
41-
import javax.servlet.FilterConfig;
42-
import javax.servlet.ServletException;
43-
import javax.servlet.ServletRequest;
44-
import javax.servlet.ServletResponse;
45-
import javax.servlet.FilterChain;
46-
import javax.servlet.http.HttpSession;
47-
import javax.servlet.http.HttpServletRequest;
48-
import javax.servlet.http.Cookie;
49-
import javax.servlet.http.HttpServletResponse;
144+
import javax.servlet.*;
145+
import javax.servlet.http.*;
50146
import java.io.IOException;
51147
import java.util.TimeZone;
52148
53149
public class RequestFilter implements Filter {
54150
private FilterConfig filterConfig;
55151
private static String TZ_NAME = "timezoneOffset";
56152
57-
public void init(FilterConfig filterConfig) throws ServletException {
58-
this.filterConfig = filterConfig;
59-
}
153+
// Other methods ommitted for brevity
60154
61155
/**
62156
* Sample filter that populates the MDC on every request.
63157
*/
64158
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
65159
throws IOException, ServletException {
66160
HttpServletRequest request = (HttpServletRequest)servletRequest;
67-
HttpServletResponse response = (HttpServletResponse)servletResponse;
68-
ThreadContext.put("ipAddress", request.getRemoteAddr());
161+
162+
ThreadContext.put("ipAddress", request.getRemoteAddr()); <1>
163+
69164
HttpSession session = request.getSession(false);
70-
TimeZone timeZone = null;
71165
if (session != null) {
72-
// Something should set this after authentication completes
73-
String loginId = (String)session.getAttribute("LoginId");
166+
// Assuming, an authentication filter has already populated the loginId in the session
167+
String loginId = (String)session.getAttribute("loginId");
74168
if (loginId != null) {
75169
ThreadContext.put("loginId", loginId);
76170
}
77-
// This assumes there is some javascript on the user's page to create the cookie.
78-
if (session.getAttribute(TZ_NAME) == null) {
79-
if (request.getCookies() != null) {
80-
for (Cookie cookie : request.getCookies()) {
81-
if (TZ_NAME.equals(cookie.getName())) {
82-
int tzOffsetMinutes = Integer.parseInt(cookie.getValue());
83-
timeZone = TimeZone.getTimeZone("GMT");
84-
timeZone.setRawOffset((int)(tzOffsetMinutes * DateUtils.MILLIS_PER_MINUTE));
85-
request.getSession().setAttribute(TZ_NAME, tzOffsetMinutes);
86-
cookie.setMaxAge(0);
87-
response.addCookie(cookie);
88-
}
89-
}
90-
}
91-
}
92171
}
172+
93173
ThreadContext.put("hostname", servletRequest.getServerName());
94174
ThreadContext.put("productName", filterConfig.getInitParameter("ProductName"));
95175
ThreadContext.put("locale", servletRequest.getLocale().getDisplayName());
96-
if (timeZone == null) {
97-
timeZone = TimeZone.getDefault();
98-
}
99-
ThreadContext.put("timezone", timeZone.getDisplayName());
176+
ThreadContext.put("timezone", TimeZone.getDefault().getDisplayName());
177+
100178
filterChain.doFilter(servletRequest, servletResponse);
101179
ThreadContext.clear();
102180
}
103-
104-
public void destroy() {
105-
}
106181
}
107182
----
183+
<1> The `doFilter()` method populates the `ThreadContext` map with data related to the request.
184+
185+
The `Filter` needs to be registered in your `web.xml` file:
186+
187+
[source, xml]
188+
----
189+
<filter>
190+
<filter-name>RequestFilter</filter-name>
191+
<filter-class>com.example.RequestFilter</filter-class>
192+
<init-param>
193+
<param-name>ProductName</param-name>
194+
<param-value>YourProductName</param-value>
195+
</init-param>
196+
</filter>
197+
<filter-mapping>
198+
<filter-name>RequestFilter</filter-name>
199+
<url-pattern>/*</url-pattern> <1>
200+
</filter-mapping>
201+
----
202+
<1> The `RequestFilter` is mapped to all requests.
108203
109-
Sample class that uses `EventLogger`.
204+
Eventually, a `Servlet` or any other related class, such as a Spring Controller, can be used to log events with structured data.
205+
The following example uses a `Servlet` to call the `EventLogger` and log a user action.
110206
111-
[source,java]
207+
[source, java]
112208
----
113-
import org.apache.logging.log4j.StructuredDataMessage;
114-
import org.apache.logging.log4j.EventLogger;
115-
116-
import java.util.Date;
117-
import java.util.UUID;
118-
119-
public class MyApp {
120-
121-
public String doFundsTransfer(Account toAccount, Account fromAccount, long amount) {
122-
toAccount.deposit(amount);
123-
fromAccount.withdraw(amount);
124-
String confirm = UUID.randomUUID().toString();
125-
StructuredDataMessage msg = new StructuredDataMessage(confirm, null, "transfer");
126-
msg.put("toAccount", toAccount);
127-
msg.put("fromAccount", fromAccount);
128-
msg.put("amount", amount);
129-
EventLogger.logEvent(msg);
130-
return confirm;
209+
import javax.servlet.*;
210+
import javax.servlet.http.*;
211+
import java.io.IOException;
212+
213+
public class UserActionServlet extends HttpServlet {
214+
215+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
216+
throws ServletException, IOException {
217+
String userId = request.getParameter("userId");
218+
String action = request.getParameter("action");
219+
String details = request.getParameter("details");
220+
221+
// Perform and log the user action
222+
String message = "User " + userId + " performed action: " + action;
223+
StructuredDataMessage msg = new StructuredDataMessage(UUID.randomUUID().toString(), message, "userAction"); <1>
224+
msg.put("userId", userId);
225+
msg.put("action", action);
226+
msg.put("details", details);
227+
228+
// Log the event
229+
EventLogger.logEvent(msg);
230+
231+
// Respond to the client
232+
response.getWriter().write("Action logged successfully");
131233
}
132234
}
133235
----
236+
<1> `userAction` is the name of the current transaction
237+
238+
That way, not only the data provided to the `EventLogger` is used, but also all the
239+
data populated in the `ThreadContext` map is included in the log message.
240+
241+
== Benefits of Structured Logging
242+
243+
1. **Improved Readability and Context:**
244+
Structured logs include detailed information, making them easier to understand and analyze.
245+
2. **Better for Automated Processing:**
246+
Structured logs are easily parsed by existing log management tools.
247+
3. **Consistency:**
248+
Structured logging enforces a consistent format, helping to identify patterns in logs.
249+
4. **Performance Optimization:**
250+
Structured messages are - as all messages - only constructed when necessary, keeping overhead low.
134251
135-
The `EventLogger` class uses a `Logger` named "EventLogger". `EventLogger`
136-
uses a logging level of OFF as the default to indicate that it cannot be
137-
filtered. These events can be formatted for printing using the
138-
link:../javadoc/log4j-core/org/apache/logging/log4j/core/layout/StructuredDataLayout.html[`StructuredDataLayout`].

0 commit comments

Comments
 (0)