(1) Simple Use
It is possible to directly use tracers by invoking suitable constructors:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
|
final FileTracer tracer = new FileTracer("Example"); |
tracer.setLogDirPath(Path.of(".", "log", "examples")); |
tracer.open(); |
try { |
class Foo { |
void bar() { |
tracer.entry("void", this, "bar()"); |
try { |
tracer.out().printfIndentln("This is an example."); |
} finally { |
tracer.wayout(); |
} |
} |
} |
Foo foo = new Foo(); |
foo.bar(); // nothing will be printed because no tracing context has been provided |
tracer.initCurrentTracingContext(1, true); |
foo.bar(); // this generates output |
} finally { |
tracer.close(); |
} |
|
A FileTracer instance will be created in line 1. The filename of the associated logfile Example.log corresponds to the name
of the tracer. The default location of the logfile would be ./log/Example.log but this is overwritten in line 2 and therefore the logfile is located
at ./log/examples/Example.log. The logfile will be opened in line 3. It is strongly recommended to put the related close()
directive into a finally block otherwise you are risking data loss, see line 20. Within this example we would like to monitor the bar()-method of
the nested class Foo. The corresponding method image will be pushed onto the stack in line 7 and will be popped from the stack in line 11. The
tracing output given in line 9 will be indented according to the stack size at that time. It is of vital importance that every entry()-call is matched
by a related wayout()-call within a finally block. A so called tracing context will be needed to actually generate output. The corresponding
directive in line 17 instructs the tracer to activate print-statements for the current thread until the stack size exceeds one. Below, you can see the tracing output of this
simple example:
1 --> TraceLog opened!
2 Time : 2023-11-07T14:26:27.711423+01:00[Europe/Berlin]
3 Bufsize : 512
4 Autoflush: true
5
6 ENTRY--void Foo[1765795529].bar()--main[1]
7 This is an example.
8 RETURN-void Foo[1765795529].bar()--(+0ms)--(+0ms)--main[1]
9
10 --> TraceLog closing!
11 Time : 2023-11-07T14:26:27.7270374+01:00[Europe/Berlin]
The tracer notes the time of its opening and closing together with the configured buffer size and autoflush status. You see the default values. Autoflush means that after
every call to
wayout() the underlying PrintStream will be automatically flushed. The numbers within the rectangular braces are the hash code given by
System.identityHashCode(java.lang.Object).
The output has been produced by the main-thread with thread-id 1.
top
(2) Access by Name
When using the TracerFactory together with a suitable configuration, tracer instances can be accessed by name. The corresponding method returns a
polymorphic AbstractTracer instance. E.g., by configuration, a regular FileTracer might be replaced by a
NetTracer. The latter one streams its output to a designated socket address. Consider the simple configuration given below:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
|
<?xml version="1.0" encoding="UTF-8" ?> |
<TraceConfig xmlns="http://www.christofreichardt.de/java/tracer"> |
<Pool> |
<TraceLogger name="ExampleTracer" class="de.christofreichardt.diagnosis.file.FileTracer"> |
<LogDir>./log/examples</LogDir> |
<AutoFlush>true</AutoFlush> |
<BufSize>1024</BufSize> |
<Limit>1048576</Limit> |
<Context> |
<Thread name="main"> |
<Online>true</Online> |
<DebugLevel>5</DebugLevel> |
</Thread> |
</Context> |
</TraceLogger> |
</Pool> |
</TraceConfig> |
|
From line 4 onwards a FileTracer named 'ExampleTracer' is defined. The accompanying log will be located at
./log/examples/ExampleTracer.log. AutoFlush is set to true, that is after every call to wayout()
the underlying PrintStream will be flushed. The buffer size is equal to 1 Kibibyte (=1024 bytes). If the logfile grows to roughly 1 MebiByte
(=1024*1024 bytes) the logfile will be rotated. The 'ExampleTracer' is interested in output created by the main-Thread up to a stack depth of 5. Further tracers aren't
defined in the pool. Under the assumption that the configuration file is located at ./config/ExampleConfig.xml, the configuration can be used
as follows:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
|
TracerFactory.getInstance().readConfiguration(Path.of(".", "config", "ExampleConfig.xml").toFile()); |
final AbstractTracer tracer = TracerFactory.getInstance().getTracer("ExampleTracer"); // this will throw an exception if the 'ExampleTracer' doesn't exist |
tracer.open(); |
try { |
class Foo { |
void bar() { |
tracer.entry("void", this, "bar()"); |
try { |
tracer.out().printfIndentln("Within bar()."); |
baz(); |
} finally { |
tracer.wayout(); |
} |
} |
|
void baz() { |
tracer.entry("void", this, "baz()"); |
try { |
tracer.out().printfIndentln("Within baz()."); |
} finally { |
tracer.wayout(); |
} |
} |
} |
Foo foo = new Foo(); |
foo.bar(); // nothing will be printed because no tracing context has been provided |
tracer.initCurrentTracingContext(); // the configured tracing context will be used |
foo.bar(); // this generates output |
} finally { |
tracer.close(); |
} |
|
This will result in the following output:
1 --> TraceLog opened!
2 Time : 2023-11-13T14:40:49.4799299+01:00[Europe/Berlin]
3 Bufsize : 1024
4 Autoflush: true
5
6 ENTRY--void Foo[2106592975].bar()--main[1]
7 Within bar().
8 ENTRY--void Foo[2106592975].baz()--main[1]
9 Within baz().
10 RETURN-void Foo[2106592975].baz()--(+0ms)--(+0ms)--main[1]
11 RETURN-void Foo[2106592975].bar()--(+11ms)--(+15ms)--main[1]
12
13 --> TraceLog closing!
14 Time : 2023-11-13T14:40:49.5019298+01:00[Europe/Berlin]
This approach makes sense if you want to analyze a single-threaded application. Within a multi-threaded environment only the main-thread would generate output when executing
the given code. It is possible to register a tracer for more than one thread but then you would have to synchronize the access to the output stream by yourself. The class
IndentablePrintStream provides synchronization primitives for this purpose. Instead of this, the approach shown in the next section is desired:
top
(3) Access by Thread
This procedure is recommended if you have control over the creation of the utilized threads, e.g. through a
ThreadFactory. For every to be observed
thread an associated tracer must be defined. Thereupon the appropriate tracer can be retrieved via convenient access methods. The association between thread and tracer is
carried out by mapping thread names on tracers since prior to program execution no thread ids exist. Consequently, care must be taken to ensure that the utilized threads are
uniquely named.
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
|
<?xml version="1.0" encoding="UTF-8" ?> |
<TraceConfig xmlns="http://www.christofreichardt.de/java/tracer"> |
<Pool> |
<TraceLogger name="WorkerTracer-0" class="de.christofreichardt.diagnosis.file.FileTracer"> |
<LogDir>./log/examples</LogDir> |
<AutoFlush>true</AutoFlush> |
<BufSize>1024</BufSize> |
<Limit>1048576</Limit> |
<Context> |
<Thread name="Worker-0"> |
<Online>true</Online> |
<DebugLevel>5</DebugLevel> |
</Thread> |
</Context> |
</TraceLogger> |
<TraceLogger name="WorkerTracer-1" class="de.christofreichardt.diagnosis.file.FileTracer"> |
<LogDir>./log/examples</LogDir> |
<AutoFlush>true</AutoFlush> |
<BufSize>1024</BufSize> |
<Limit>1048576</Limit> |
<Context> |
<Thread name="Worker-1"> |
<Online>true</Online> |
<DebugLevel>5</DebugLevel> |
</Thread> |
</Context> |
</TraceLogger> |
<TraceLogger name="WorkerTracer-2" class="de.christofreichardt.diagnosis.file.FileTracer"> |
<LogDir>./log/examples</LogDir> |
<AutoFlush>true</AutoFlush> |
<BufSize>1024</BufSize> |
<Limit>1048576</Limit> |
<Context> |
<Thread name="Worker-2"> |
<Online>true</Online> |
<DebugLevel>5</DebugLevel> |
</Thread> |
</Context> |
</TraceLogger> |
</Pool> |
<Map> |
<Threads> |
<Thread name="Worker-0"> |
<TraceLogger ref="WorkerTracer-0"/> |
</Thread> |
<Thread name="Worker-1"> |
<TraceLogger ref="WorkerTracer-1"/> |
</Thread> |
<Thread name="Worker-2"> |
<TraceLogger ref="WorkerTracer-2"/> |
</Thread> |
</Threads> |
</Map> |
</TraceConfig> |
|
in the above shown configuration we define three tracers (WorkerTracer-0, WorkerTracer-1 and WorkerTracer-2) which are interested in the output of three worker threads
(Worker-0, Worker-1 and Worker-2). Furthermore, a mapping between threads and tracers has been specified (Worker-0 -> WorkerTracer-0, Worker-1 -> WorkerTracer-1 and
Worker-2 -> WorkerTracer-2). The following code snippet illustrates how the given configuration can be applied:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
|
class Foo implements Callable<Integer>, Traceable { |
final int id; |
|
public Foo(int id) { |
this.id = id; |
} |
|
@Override |
public Integer call() throws java.lang.Exception { |
AbstractTracer tracer = getCurrentTracer(); |
tracer.initCurrentTracingContext(); |
tracer.entry("Integer", this, "call()"); |
try { |
tracer.logMessage(LogLevel.INFO, "Consumer(" + this.id + ")", getClass(), "call()"); |
bar(); |
return this.id; |
} finally { |
tracer.wayout(); |
} |
} |
|
void bar() { |
getCurrentTracer().entry("void", this, "bar()"); |
try { |
getCurrentTracer().out().printfIndentln("Within bar()."); |
baz(); |
} finally { |
getCurrentTracer().wayout(); |
} |
} |
|
void baz() { |
getCurrentTracer().entry("void", this, "baz()"); |
try { |
getCurrentTracer().out().printfIndentln("Within baz()."); |
} finally { |
getCurrentTracer().wayout(); |
} |
} |
|
@Override |
public AbstractTracer getCurrentTracer() { |
return TracerFactory.getInstance().getCurrentPoolTracer(); |
} |
} |
|
TracerFactory.getInstance().readConfiguration(Path.of(".", "config", "ExampleConfig_2.xml").toFile()); |
TracerFactory.getInstance().openPoolTracer(); |
try { |
final int THREAD_NUMBER = 3; |
final int ITERATIONS = 100; |
final int TIMEOUT = 5; |
List<Future<Integer>> futures = new ArrayList<>(); |
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER, new ThreadFactory() { |
final AtomicInteger threadNr = new AtomicInteger(); |
|
@Override |
public Thread newThread(Runnable r) { |
return new Thread(r, "Worker-" + this.threadNr.getAndIncrement()); |
} |
}); |
try { |
for (int i = 0; i < ITERATIONS; i++) { |
futures.add(executorService.submit(new Foo(i))); |
} |
try { |
int i = 0; |
for (Future<Integer> future : futures) { |
assert future.get() == i; |
i++; |
} |
} catch (ExecutionException ex) { |
ex.getCause().printStackTrace(System.err); |
} |
} finally { |
executorService.shutdown(); |
assert executorService.awaitTermination(TIMEOUT, TimeUnit.SECONDS); |
} |
} finally { |
TracerFactory.getInstance().closePoolTracer(); |
} |
|
The class
Foo implements the
Callable interface and is therefore suited to
be submitted to an
Executorservice for
parallel execution. In addition the interface
Traceable is implemented, see lines 42-44. The expression
TracerFactory.getInstance().getCurrentPoolTracer() returns the associated tracer for the current thread. The given configuration could
be easily overwritten by e.g.
TracerFactory.getInstance().getDefaultTracer() which would return a
NullTracer.
A
NullTracer discards all tracing output. Specialized
NullTracers may route the INFO-, WARNING- and ERROR-messages
to conventional Logger systems, such like Logback. If no mapping is defined for a given thread,
TracerFactory.getInstance().getCurrentPoolTracer()
would return a
NullTracer, too. We get ourselves an
Executorservice in line 54. This
Executorservice provides a pool containing of three threads operating on a shared unbounded queue of work tasks. The given
ThreadFactory creates the threads with the intended names. 100
Foo objects are submitted to the specified
Executorservice from line 63 to line 65. Which of the three threads works on a particular
Foo task is not predictable, that
is the output of the 100
Foo instances will be more or less evenly distributed between the three accompanying logs. However, output generated from
thread Worker-0 will always be routed to WorkerTracer-0, output generated from thread Worker-1 will be routed to WorkerTracer-1 and so on. An excerpt from
WorkerTracer-0.log is shown below:
1 --> TraceLog opened!
2 Time : 2023-11-15T13:01:52.6094606+01:00[Europe/Berlin]
3 Bufsize : 1024
4 Autoflush: true
...
61 ENTRY--Integer Foo[1724272678].call()--Worker-1[35]
62 +------+
63 | INFO | [2023-11-15T13:01:52.6576449] [35,Worker-1] [de.christofreichardt.diagnosis.ExampleUnit5$2Foo] [call()] "Consumer(16)"
64 +------+
65 ENTRY--void Foo[1724272678].bar()--Worker-1[35]
66 Within bar().
67 ENTRY--void Foo[1724272678].baz()--Worker-1[35]
68 Within baz().
69 RETURN-void Foo[1724272678].baz()--(+0ms)--(+0ms)--Worker-1[35]
70 RETURN-void Foo[1724272678].bar()--(+2ms)--(+0ms)--Worker-1[35]
71 RETURN-Integer Foo[1724272678].call()--(+2ms)--(+0ms)--Worker-1[35]
...
370 --> TraceLog closing!
371 Time : 2023-11-15T13:01:52.7162078+01:00[Europe/Berlin]
Sometimes we didn't control the thread creation, like shown in the example above. Thus, we are not in the position to guarantee the uniqueness of thread names, nor
do we even sometimes know the thread names prior to program execution. Consequently we cannot define a mapping between threads and tracers. In this case, we can use the
Tracer-Queue, see the next section.
top
(4) Access by Queue
Jakarta EE applications are operating under the control of an application server, e.g. Tomcat. Incoming requests will be served by (virtual) threads which might (or might not)
originate from a thread pool. In either case we might not be able to configure the thread pool or the thread creation to suit our requirements. Thus, we define a
(blocking) queue containing some uniform tracers which can be used to track the incoming requests. The queue blocks if no tracer is available at the moment. This will
effectively limit the number of requests which can be concurrently processed, notwithstanding the size of the server's thread pool or other policies when working with virtual
threads. The current tracing context of some active tracer will be automatically cleared when the last method image has been popped from its stack and the now inactive tracer
will be returned to the queue. Consider the following configuration defining a queue with three tracers:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
|
<?xml version="1.0" encoding="UTF-8"?> |
<TraceConfig xmlns="http://www.christofreichardt.de/java/tracer"> |
<Pool/> |
<DefaultTracer class="de.christofreichardt.diagnosis.JDKLoggingRouter"/> |
<Queue> |
<Enabled /> |
<Size>3</Size> |
<TraceLogger name="servlet-tracer-" |
class="de.christofreichardt.diagnosis.file.QueueFileTracer"> |
<LogDir>${catalina.base}/logs</LogDir> |
<AutoFlush>true</AutoFlush> |
<BufSize>1024</BufSize> |
</TraceLogger> |
<Online>true</Online> |
<DebugLevel>5</DebugLevel> |
</Queue> |
</TraceConfig> |
|
Note the declaration regarding the log directory in line 10. Since the example application has been developed using a Apache Tomcat 10, we apply its
catalina.base system property to denote the logs-directory of the Tomcat distribution. (You can use system properties
wherever you want within the XML configuration, that is element content or attributs.)
Under the assumption that the
configuration file has been attached to the package de.christofreichardt.tracefilter as resource, we can implement the following
ServletContextListener:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
|
public class MyContextListener implements ServletContextListener { |
|
final static Logger LOGGER = Logger.getLogger("de.christofreichardt.tracefilter"); |
|
@Override |
public void contextInitialized(ServletContextEvent servletContextEvent) { |
LOGGER.info("Context initialized ..."); |
LOGGER.info("Context path = %s".formatted(servletContextEvent.getServletContext().getContextPath())); |
InputStream resourceAsStream = MyContextListener.class.getClassLoader().getResourceAsStream("de/christofreichardt/tracefilter/trace-config.xml"); |
if (resourceAsStream != null) { |
try { |
TracerFactory.getInstance().reset(); |
TracerFactory.getInstance().readConfiguration(resourceAsStream); |
TracerFactory.getInstance().openQueueTracer(); |
} catch (TracerFactory.Exception ex) { |
LOGGER.log(Level.WARNING, "Problems occured when reading the tracer configuration.", ex); |
} finally { |
try { |
resourceAsStream.close(); |
} catch (IOException ex) { |
} |
} |
} else { |
LOGGER.warning("Missing tracer configuration."); |
} |
} |
|
@Override |
public void contextDestroyed(ServletContextEvent servletContextEvent) { |
TracerFactory.getInstance().closeQueueTracer(); |
LOGGER.info("Context destroyed ..."); |
} |
} |
|
ServletContextListeners are notified before the initialization of filters and servlets or rather are notified after the release of all servlets and
filters. Thus, context listeners work well for the initialization or rather for the cleanup of the tracers. Conversely, WebFilters can
be used for the activation of tracers, see below:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
|
@WebFilter(filterName = "MyTraceFilter", servletNames = {"MyServlet"}, initParams = { |
@WebInitParam(name = "tracing", value = "on") |
}) |
public class MyTraceFilter implements Filter { |
|
FilterConfig filterConfig; |
final AtomicInteger atomicInteger = new AtomicInteger(); |
|
@Override |
public void init(FilterConfig filterConfig) throws ServletException { |
this.filterConfig = filterConfig; |
} |
|
@Override |
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { |
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; |
int requestCounter = this.atomicInteger.getAndIncrement(); |
servletRequest.setAttribute("de.christofreichardt.tracefilter.requestCounter", requestCounter); |
if ("on".equalsIgnoreCase(this.filterConfig.getInitParameter("tracing"))) { |
AbstractTracer tracer = TracerFactory.getInstance().takeTracer(); |
tracer.initCurrentTracingContext(); |
tracer.entry("void", this, "doFilter(ServletRequest request, ServletResponse response, FilterChain chain)"); |
try { |
tracer.out().printfIndentln("method = %s", httpServletRequest.getMethod()); |
tracer.out().printfIndentln("contextPath = %s", httpServletRequest.getContextPath()); |
tracer.out().printfIndentln("requestCounter = %d", requestCounter); |
filterChain.doFilter(servletRequest, servletResponse); |
} finally { |
tracer.wayout(); |
} |
} else { |
filterChain.doFilter(servletRequest, servletResponse); |
} |
} |
} |
|
Consider line 20: Without fetching a tracer from the queue like this TracerFactory.getInstance().getCurrentQueueTracer() would return a
NullTracer. That is, simply removing the WebFilter from the filter chain would deactivate the tracing.
The stament in line 29 does not only pop the method image from the stack but also puts the the tracer back into the blocking queue, because the stack is then empty.
The tracer can be deactivated by editing the tracing-WebInitParam, too. The to be observed Servlet is shown next:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
|
@WebServlet(name = "MyServlet", urlPatterns = {"/*"}) |
public class MyServlet extends HttpServlet implements Traceable { |
|
@Override |
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { |
AbstractTracer tracer = getCurrentTracer(); |
tracer.entry("void", this, "doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)"); |
try { |
int requestCounter = (int) httpServletRequest.getAttribute("de.christofreichardt.tracefilter.requestCounter"); |
tracer.logMessage(LogLevel.INFO, "%d. request ...".formatted(requestCounter), |
getClass(), "doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)"); |
String command; |
try (BufferedReader bufferedReader = httpServletRequest.getReader()) { |
command = bufferedReader.readLine(); |
} |
tracer.out().printfIndentln("command = %s", command); |
long pause = ThreadLocalRandom.current().nextLong(100L); |
try { |
Thread.sleep(pause); |
} catch (InterruptedException ex) { |
throw new RuntimeException(ex); |
} |
if ("REQUEST_LocalDateTime".equals(command)) { |
try (PrintWriter printWriter = httpServletResponse.getWriter()) { |
printWriter.printf("%3d: %s", requestCounter, LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); |
} |
} |
} finally { |
tracer.wayout(); |
} |
} |
|
@Override |
public AbstractTracer getCurrentTracer() { |
return TracerFactory.getInstance().getCurrentQueueTracer(); |
} |
} |
|
The servlet acts on POST requests by returning the present local date-time in ISO-8601 format. It pauses a random interval (<100ms) before committing the result to
simulate some internal computations. We can trigger the servlet with the following client code:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
|
final int THREAD_NUMBER = 5; |
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER, new ThreadFactory() { |
final AtomicInteger threadNr = new AtomicInteger(); |
|
@Override |
public Thread newThread(Runnable r) { |
return new Thread(r, "Worker-" + this.threadNr.getAndIncrement()); |
} |
}); |
|
HttpClient httpClient = HttpClient.newBuilder() |
.executor(executorService) |
.build(); |
|
HttpRequest httpRequest = HttpRequest.newBuilder() |
.uri(URI.create("http://localhost:8080/trace-filter-example")) |
.POST(HttpRequest.BodyPublishers.ofString("REQUEST_LocalDateTime")) |
.build(); |
|
final int REQUESTS = 25; |
List<CompletableFuture<Stream<String>>> completableFutures = IntStream.range(0, REQUESTS) |
.mapToObj(i -> { |
return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines()) |
.thenApply(httpResponse -> { |
assert httpResponse.statusCode() == 200; |
return httpResponse.body(); |
}); |
}) |
.collect(Collectors.toList()); |
List<String> responses = completableFutures.stream() |
.map(completableFuture -> { |
try { |
return completableFuture.get(); |
} catch (ExecutionException | InterruptedException ex) { |
throw new RuntimeException(ex); |
} |
}) |
.flatMap(bodies -> bodies) |
.collect(Collectors.toList()); |
assert responses.size() == REQUESTS; |
responses.forEach(response -> System.out.printf("response = %s%n", response)); |
|
We are asynchronously sending 25 POST requests by using a thread pool containing five threads. Since we are asynchronously posting the requests and since the service
waits some random time interval during the processing of each request, the responses won't be in order:
response = 12: 2023-11-20T16:39:19.2222441
response = 1: 2023-11-20T16:39:18.8867623
response = 4: 2023-11-20T16:39:18.9627704
response = 18: 2023-11-20T16:39:19.1919087
response = 0: 2023-11-20T16:39:18.824767
response = 7: 2023-11-20T16:39:19.0556773
response = 23: 2023-11-20T16:39:19.3301539
response = 11: 2023-11-20T16:39:18.9807705
response = 17: 2023-11-20T16:39:19.1378188
response = 20: 2023-11-20T16:39:19.2712295
response = 22: 2023-11-20T16:39:19.2865033
response = 10: 2023-11-20T16:39:18.8547792
response = 9: 2023-11-20T16:39:19.0054473
response = 14: 2023-11-20T16:39:19.0264472
response = 13: 2023-11-20T16:39:19.0348963
response = 8: 2023-11-20T16:39:18.8687806
response = 2: 2023-11-20T16:39:19.0264472
response = 16: 2023-11-20T16:39:19.0959044
response = 6: 2023-11-20T16:39:19.1949069
response = 19: 2023-11-20T16:39:19.2654991
response = 24: 2023-11-20T16:39:19.3205006
response = 5: 2023-11-20T16:39:18.9397622
response = 15: 2023-11-20T16:39:19.0988537
response = 21: 2023-11-20T16:39:19.3260907
response = 3: 2023-11-20T16:39:18.8607791
Now, we take a look at one of the trace-logs from the service. An excerpt is shown below:
1 --> TraceLog opened!
2 Time : 2023-11-20T16:39:09.6763475+01:00[Europe/Berlin]
3 Bufsize : 1024
4 Autoflush: true
...
39 ENTRY--void MyTraceFilter[418479555].doFilter(ServletRequest request, ServletResponse response, FilterChain chain)--http-nio-8080-exec-6[41]
40 method = POST
41 contextPath = /trace-filter-example
42 requestCounter = 7
43 ENTRY--void MyServlet[1075871170].doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)--http-nio-8080-exec-6[41]
44 +------+
45 | INFO | [2023-11-20T16:39:19.0379308] [41,http-nio-8080-exec-6] [de.christofreichardt.tracefilter.MyServlet] [doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)] "7. request ..."
46 +------+
47 command = REQUEST_LocalDateTime
48 RETURN-void MyServlet[1075871170].doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)--(+20ms)--(+0ms)--http-nio-8080-exec-6[41]
49 RETURN-void MyTraceFilter[418479555].doFilter(ServletRequest request, ServletResponse response, FilterChain chain)--(+20ms)--(+0ms)--http-nio-8080-exec-6[41]
...
84 --> TraceLog closing!
85 Time : 2023-11-20T16:52:25.1624224+01:00[Europe/Berlin]
With a disabled trace filter, this would become (within the catalina.yyyy-MM-dd.log):
236 20-Nov-2023 17:03:57.174 INFO [main] de.christofreichardt.tracefilter.MyContextListener.contextInitialized Context initialized ...
237 20-Nov-2023 17:03:57.176 INFO [main] de.christofreichardt.tracefilter.MyContextListener.contextInitialized Context path = /trace-filter-example
...
252 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-13] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 13. request ...
253 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-25] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 8. request ...
254 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-18] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 16. request ...
255 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-14] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 19. request ...
256 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-12] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 3. request ...
257 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-20] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 14. request ...
258 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-7] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 22. request ...
259 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-3] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 21. request ...
260 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-23] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 20. request ...
261 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-19] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 17. request ...
262 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-21] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 7. request ...
263 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-4] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 0. request ...
264 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-16] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 18. request ...
265 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-6] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 11. request ...
266 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-24] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 24. request ...
267 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-2] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 9. request ...
268 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-17] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 5. request ...
269 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-10] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 1. request ...
270 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-15] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 12. request ...
271 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-8] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 4. request ...
272 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-1] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 23. request ...
273 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-9] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 15. request ...
274 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-11] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 2. request ...
275 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-5] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 10. request ...
276 20-Nov-2023 17:04:09.199 INFO [http-nio-8080-exec-22] de.christofreichardt.tracefilter.MyServlet.doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) 6. request ...
...
280 20-Nov-2023 17:09:29.413 INFO [main] de.christofreichardt.tracefilter.MyContextListener.contextDestroyed Context destroyed ...
top