This is an automated email from the ASF dual-hosted git repository.

grobmeier pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 9ea02235af first draft of eventlogger rewrite
9ea02235af is described below

commit 9ea02235afd880adb0fd2004b931d8ff81eaa9ff
Author: Christian Grobmeier <[email protected]>
AuthorDate: Mon May 20 15:15:10 2024 +0200

    first draft of eventlogger rewrite
---
 .../modules/ROOT/pages/manual/eventlogging.adoc    | 279 +++++++++++++++------
 1 file changed, 196 insertions(+), 83 deletions(-)

diff --git a/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc 
b/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc
index d89e5e4780..ede1bc3901 100644
--- a/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/eventlogging.adoc
@@ -14,39 +14,135 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 ////
-= Event Logging
-
-The `EventLogger` class provides a simple mechanism for logging events
-that occur in an application. While the `EventLogger` is useful as a way
-of initiating events that should be processed by an audit Logging
-system, by itself it does not implement any of the features an audit
-logging system would require such as guaranteed delivery.
-
-The recommended way of using the `EventLogger` in a typical web
-application is to populate the `ThreadContext` Map with data that is
-related to the entire lifespan of the request such as the user's id, the
-user's IP address, the product name, etc. This can easily be done in a
-servlet filter where the `ThreadContext` Map can also be cleared at the
-end of the request. When an event that needs to be recorded occurs a
-`StructuredDataMessage` should be created and populated. Then call
-`EventLogger.logEvent(msg)` where msg is a reference to the
-`StructuredDataMessage`.
-
-[source,java]
+= Event Logging with Log4j
+
+The `EventLogger` class provides a mechanism for logging significant events 
+in an application using structured data. This approach is beneficial for 
+tracking user actions, monitoring system behavior, and debugging issues.
+
+Theoretically, every `Logger` can be used to perform this kind of action;
+however, the `EventLogger` class provides a shortcut to log events with 
structured data
+since it allows for a static method to log events.
+
+== Advantages of Structured Logging
+
+Structured logging means events Log4j records events with detailed and 
structured information.
+That way, logs are easier to read and process. They provide better context and
+are also more consistent than plain text logs.
+
+== Integration with Syslog
+
+Log4j complies with Syslogs RFC5424 standard. 
+This feature allows for easy integration with existing log management and 
monitoring systems.
+
+== Example Configuration
+
+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. 
+Developers can use the following configuration to log events to a local Syslog 
server:
+
+[source, xml]
+----
+<Appenders>
+    <Syslog name="Syslog" host="localhost" port="514"> <1>
+        <StructuredDataLayout complete="true" /> <2>
+    </Syslog>
+</Appenders>
+
+<Loggers>
+    <Logger name="MyApp" level="info" additivity="false">
+        <AppenderRef ref="Syslog"/>
+    </Logger>
+</Loggers>
+----
+<1> The `Syslog` appender sends logs to a local Syslog server.
+<2> The `StructuredDataLayout` layout formats logs in RFC5424 format.
+
+Of course, sending logs to a Syslog server is unnecessary. 
+Developers can use the `StructuredDataLayout` layout with any other appender, 
such as `FileAppender` or `ConsoleAppender`.
+
+As an example, the output could look like this:
+
+[source, text]
+----
+<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
+----
+
+== Using the `EventLogger`
+
+The `EventLogger` class provides a simple way to log structured events.
+It uses the `StructuredDataMessage` class to create structured log messages.
+
+Assume a simple application that performs funds transfers between accounts.
+This application should send a certain amount of funds from one account to 
another and log the event.
+
+The account class is defined as follows, with a unique ID and a balance:
+
+[source, java]
+----
+class Account {
+    private String id;
+    private long balance;
+    // Getters and setters omitted for brevity
+}
+----
+
+The `transfer()` method transfers funds between two accounts and logs the 
event.
+It needs to take two accounts and the amount to transfer as parameters.
+
+Apart from the key-value pairs provided in the map of the 
`StructuredDataMessage,`
+the `StructuredDataMessage` also takes a unique ID, a free-form message, and a 
type as parameters.
+
+The free-form message is a human-readable description of the event. 
+This message is good for operators who need to understand the event quickly, 
+but not so much for automated processing.
+
+[source, java]
+----
+public static String transferFunds(Account toAccount, Account fromAccount, 
long amount) {
+    toAccount.deposit(amount);
+    fromAccount.withdraw(amount);
+
+    // Create a unique ID for the transaction
+    String confirm = UUID.randomUUID().toString();
+
+    String freeFormMessage = "User " + fromAccount.getId() + " has transferred 
" + amount + " to account " + toAccount.getId(); <1>
+
+    // Create the StructuredDataMessage
+    StructuredDataMessage msg = new StructuredDataMessage(confirm, 
freeFormMessage, "transfer"); <2>
+    msg.put("toAccount", toAccount.getId()); <3>
+    msg.put("fromAccount", fromAccount.getId());
+    msg.put("amount", amount);
+
+    // Log the event
+    EventLogger.logEvent(msg); <4>
+
+    return confirm;
+}
+----
+<1> The free-form message is a human-readable description of the event.
+<2> The `StructuredDataMessage` constructor takes an ID, the free-form 
message, and a type.
+<3> Developers can add key-value pairs to the message.
+<4> The `EventLogger` logs the event.
+
+That way, the `transferFunds()` method logs the event with structured data
+by using the `EventLogger`.
+
+== Web Application Example
+
+In a web application, developers can use a servlet filter to populate the 
+`ThreadContext` map with data related to the request.
+
+The following example demonstrates how a `Filter` could investigate the request
+and populate the `ThreadContext` map with data such as the user's IP address,
+the user's login ID, the server's hostname, the product name, the locale, and 
the timezone.
+
+[source, java]
 ----
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.commons.lang.time.DateUtils;
 
-import javax.servlet.Filter;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.FilterChain;
-import javax.servlet.http.HttpSession;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletResponse;
+import javax.servlet.*;
+import javax.servlet.http.*;
 import java.io.IOException;
 import java.util.TimeZone;
 
@@ -54,9 +150,7 @@ public class RequestFilter implements Filter {
     private FilterConfig filterConfig;
     private static String TZ_NAME = "timezoneOffset";
 
-    public void init(FilterConfig filterConfig) throws ServletException {
-        this.filterConfig = filterConfig;
-    }
+    // Other methods ommitted for brevity
 
     /**
      * Sample filter that populates the MDC on every request.
@@ -64,75 +158,94 @@ public class RequestFilter implements Filter {
     public void doFilter(ServletRequest servletRequest, ServletResponse 
servletResponse, FilterChain filterChain)
             throws IOException, ServletException {
         HttpServletRequest request = (HttpServletRequest)servletRequest;
-        HttpServletResponse response = (HttpServletResponse)servletResponse;
-        ThreadContext.put("ipAddress", request.getRemoteAddr());
+        
+        ThreadContext.put("ipAddress", request.getRemoteAddr()); <1>
+
         HttpSession session = request.getSession(false);
-        TimeZone timeZone = null;
         if (session != null) {
-            // Something should set this after authentication completes
-            String loginId = (String)session.getAttribute("LoginId");
+            // Assuming, an authentication filter has already populated the 
loginId in the session
+            String loginId = (String)session.getAttribute("loginId");
             if (loginId != null) {
                 ThreadContext.put("loginId", loginId);
             }
-            // This assumes there is some javascript on the user's page to 
create the cookie.
-            if (session.getAttribute(TZ_NAME) == null) {
-                if (request.getCookies() != null) {
-                    for (Cookie cookie : request.getCookies()) {
-                        if (TZ_NAME.equals(cookie.getName())) {
-                            int tzOffsetMinutes = 
Integer.parseInt(cookie.getValue());
-                            timeZone = TimeZone.getTimeZone("GMT");
-                            timeZone.setRawOffset((int)(tzOffsetMinutes * 
DateUtils.MILLIS_PER_MINUTE));
-                            request.getSession().setAttribute(TZ_NAME, 
tzOffsetMinutes);
-                            cookie.setMaxAge(0);
-                            response.addCookie(cookie);
-                        }
-                    }
-                }
-            }
         }
+
         ThreadContext.put("hostname", servletRequest.getServerName());
         ThreadContext.put("productName", 
filterConfig.getInitParameter("ProductName"));
         ThreadContext.put("locale", 
servletRequest.getLocale().getDisplayName());
-        if (timeZone == null) {
-            timeZone = TimeZone.getDefault();
-        }
-        ThreadContext.put("timezone", timeZone.getDisplayName());
+        ThreadContext.put("timezone", TimeZone.getDefault().getDisplayName());
+
         filterChain.doFilter(servletRequest, servletResponse);
         ThreadContext.clear();
     }
-
-    public void destroy() {
-    }
 }
 ----
+<1> The `doFilter()` method populates the `ThreadContext` map with data 
related to the request.
+
+The `Filter` needs to be registered in your `web.xml` file:
+
+[source, xml]
+----
+<filter>
+    <filter-name>RequestFilter</filter-name>
+    <filter-class>com.example.RequestFilter</filter-class>
+    <init-param>
+        <param-name>ProductName</param-name>
+        <param-value>YourProductName</param-value>
+    </init-param>
+</filter>
+<filter-mapping>
+    <filter-name>RequestFilter</filter-name>
+    <url-pattern>/*</url-pattern> <1>
+</filter-mapping>
+----
+<1> The `RequestFilter` is mapped to all requests.
 
-Sample class that uses `EventLogger`.
+Eventually, a `Servlet` or any other related class, such as a Spring 
Controller, can be used to log events with structured data. 
+The following example uses a `Servlet` to call the `EventLogger` and log a 
user action.
 
-[source,java]
+[source, java]
 ----
-import org.apache.logging.log4j.StructuredDataMessage;
-import org.apache.logging.log4j.EventLogger;
-
-import java.util.Date;
-import java.util.UUID;
-
-public class MyApp {
-
-    public String doFundsTransfer(Account toAccount, Account fromAccount, long 
amount) {
-        toAccount.deposit(amount);
-        fromAccount.withdraw(amount);
-        String confirm = UUID.randomUUID().toString();
-        StructuredDataMessage msg = new StructuredDataMessage(confirm, null, 
"transfer");
-        msg.put("toAccount", toAccount);
-        msg.put("fromAccount", fromAccount);
-        msg.put("amount", amount);
-        EventLogger.logEvent(msg);
-        return confirm;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import java.io.IOException;
+
+public class UserActionServlet extends HttpServlet {
+    
+    protected void doPost(HttpServletRequest request, HttpServletResponse 
response)
+            throws ServletException, IOException {
+        String userId = request.getParameter("userId");
+        String action = request.getParameter("action");
+        String details = request.getParameter("details");
+
+        // Perform and log the user action
+        String message = "User " + userId + " performed action: " + action;
+        StructuredDataMessage msg = new 
StructuredDataMessage(UUID.randomUUID().toString(), message, "userAction"); <1>
+        msg.put("userId", userId);
+        msg.put("action", action);
+        msg.put("details", details);
+
+        // Log the event
+        EventLogger.logEvent(msg); 
+
+        // Respond to the client
+        response.getWriter().write("Action logged successfully");
     }
 }
 ----
+<1> `userAction` is the name of the current transaction
+
+That way, not only the data provided to the `EventLogger` is used, but also 
all the 
+data populated in the `ThreadContext` map is included in the log message.
+
+== Benefits of Structured Logging
+
+1. **Improved Readability and Context:** 
+   Structured logs include detailed information, making them easier to 
understand and analyze.
+2. **Better for Automated Processing:**
+   Structured logs are easily parsed by existing log management tools.
+3. **Consistency:**
+   Structured logging enforces a consistent format, helping to identify 
patterns in logs.
+4. **Performance Optimization:** 
+   Structured messages are - as all messages - only constructed when 
necessary, keeping overhead low.
 
-The `EventLogger` class uses a `Logger` named "EventLogger". `EventLogger`
-uses a logging level of OFF as the default to indicate that it cannot be
-filtered. These events can be formatted for printing using the
-link:../javadoc/log4j-core/org/apache/logging/log4j/core/layout/StructuredDataLayout.html[`StructuredDataLayout`].

Reply via email to