diff --git a/jpos/src/main/java/org/jpos/iso/ISOServer.java b/jpos/src/main/java/org/jpos/iso/ISOServer.java index f1630b0f75..d3b2410b84 100644 --- a/jpos/src/main/java/org/jpos/iso/ISOServer.java +++ b/jpos/src/main/java/org/jpos/iso/ISOServer.java @@ -31,6 +31,7 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; @@ -334,15 +335,20 @@ public void run() { setChanged (); notifyObservers (); UUID sessionUUID = uuid; - String sessionInfo = ""; String endpoint = null; + String host = null; + int remotePort = 0; + int localPort = 0; + Instant start = Instant.now(); if (channel instanceof BaseChannel baseChannel) { Socket socket = baseChannel.getSocket (); - sessionInfo = socket.toString(); + host = socket.getInetAddress().getHostAddress(); + remotePort = socket.getPort(); + localPort = socket.getLocalPort(); sessionUUID = getSocketUUID(socket); endpoint = baseChannel.toEndpoint(socket); LogEvent ev = createSessionEvent(sessionUUID, endpoint) - .add(new SessionStart(getActiveConnections(), permitsCount, sessionInfo) + .add(new SessionStart(getActiveConnections(), permitsCount, host, remotePort, localPort) ); if (!checkPermission (socket, ev)) return; @@ -387,7 +393,7 @@ public void run() { fireEvent(new ISOServerClientDisconnectEvent(ISOServer.this, channel)); } Logger.log(createSessionEvent(sessionUUID, endpoint) - .add(new SessionEnd(getActiveConnections(), permitsCount, sessionInfo) + .add(new SessionEnd(getActiveConnections(), permitsCount, host, remotePort, localPort, Duration.between(start, Instant.now())) ) ); } diff --git a/jpos/src/main/java/org/jpos/log/evt/SessionEnd.java b/jpos/src/main/java/org/jpos/log/evt/SessionEnd.java index a561e975de..fd068da418 100644 --- a/jpos/src/main/java/org/jpos/log/evt/SessionEnd.java +++ b/jpos/src/main/java/org/jpos/log/evt/SessionEnd.java @@ -18,13 +18,29 @@ package org.jpos.log.evt; +import com.fasterxml.jackson.annotation.JsonInclude; import org.jpos.log.AuditLogEvent; +import java.time.Duration; + /** * Audit event recorded when a server session terminates. * + *

Field names mirror {@link Connect} and {@link Disconnect} so viewers can + * render channel and server sessions with one template.

+ * * @param connections active connection count after this session ended * @param permits remaining session permits - * @param info free-form description of the closing session + * @param host remote address, or {@code null} when the channel exposes no socket + * @param remotePort remote port + * @param localPort local port the session was accepted on + * @param duration session length, from accept to close */ -public record SessionEnd(int connections, int permits, String info) implements AuditLogEvent { } +public record SessionEnd( + int connections, + int permits, + @JsonInclude(JsonInclude.Include.NON_NULL) String host, + int remotePort, + int localPort, + Duration duration +) implements AuditLogEvent { } diff --git a/jpos/src/main/java/org/jpos/log/evt/SessionStart.java b/jpos/src/main/java/org/jpos/log/evt/SessionStart.java index 3e8941a321..d6c6629c37 100644 --- a/jpos/src/main/java/org/jpos/log/evt/SessionStart.java +++ b/jpos/src/main/java/org/jpos/log/evt/SessionStart.java @@ -16,32 +16,27 @@ * along with this program. If not, see . */ -package org.jpos.log.evt;/* - * jPOS Project [http://jpos.org] - * Copyright (C) 2000-2010 Alejandro P. Revilla - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +package org.jpos.log.evt; +import com.fasterxml.jackson.annotation.JsonInclude; import org.jpos.log.AuditLogEvent; /** * Audit event recorded when a server session opens. * + *

Field names mirror {@link Connect} and {@link Disconnect} so viewers can + * render channel and server sessions with one template.

+ * * @param connections active connection count after this session started * @param permits remaining session permits - * @param info free-form description of the opening session + * @param host remote address, or {@code null} when the channel exposes no socket + * @param remotePort remote port + * @param localPort local port the session was accepted on */ -public record SessionStart(int connections, int permits, String info) implements AuditLogEvent { -} +public record SessionStart( + int connections, + int permits, + @JsonInclude(JsonInclude.Include.NON_NULL) String host, + int remotePort, + int localPort +) implements AuditLogEvent { } diff --git a/jpos/src/test/java/org/jpos/iso/ISOServerTest.java b/jpos/src/test/java/org/jpos/iso/ISOServerTest.java index 6468b36201..65654f2147 100644 --- a/jpos/src/test/java/org/jpos/iso/ISOServerTest.java +++ b/jpos/src/test/java/org/jpos/iso/ISOServerTest.java @@ -189,6 +189,21 @@ public void testSessionEventsUseStableRealmAndDynamicTags() throws Exception { assertEquals(org.jpos.util.Kind.ISO_SESSION, sessionEvent.getTag(), "sessionEvent.getTag()"); assertTrue(sessionEvent.getTags().containsKey("session"), "sessionEvent.getTags().containsKey(session)"); assertTrue(sessionEvent.getTags().containsKey("endpoint"), "sessionEvent.getTags().containsKey(endpoint)"); + org.jpos.log.evt.SessionStart start = sessionEvent.getPayLoad().stream() + .filter(p -> p instanceof org.jpos.log.evt.SessionStart) + .map(p -> (org.jpos.log.evt.SessionStart) p) + .findFirst().orElseThrow(); + assertEquals("127.0.0.1", start.host(), "start.host()"); + assertEquals(port, start.localPort(), "start.localPort()"); + assertTrue(start.remotePort() > 0, "start.remotePort()"); + org.jpos.log.evt.SessionEnd end = events.stream() + .flatMap(ev -> ev.getPayLoad().stream()) + .filter(p -> p instanceof org.jpos.log.evt.SessionEnd) + .map(p -> (org.jpos.log.evt.SessionEnd) p) + .findFirst().orElseThrow(); + assertEquals(start.remotePort(), end.remotePort(), "end.remotePort()"); + assertEquals(port, end.localPort(), "end.localPort()"); + assertTrue(end.duration() != null && !end.duration().isNegative(), "end.duration()"); } } finally { server.shutdown(); diff --git a/jpos/src/test/java/org/jpos/util/KindTest.java b/jpos/src/test/java/org/jpos/util/KindTest.java index 448e6b2203..2707403f9c 100644 --- a/jpos/src/test/java/org/jpos/util/KindTest.java +++ b/jpos/src/test/java/org/jpos/util/KindTest.java @@ -85,7 +85,7 @@ void kindOfTypeFollowsTheUmbrella() { assertEquals(Kind.LIFECYCLE, Kind.kindOf("license")); assertEquals(Kind.DEPLOY, Kind.kindOf("undeploy")); assertEquals(Kind.ERROR, Kind.kindOf("throwable")); - assertEquals(Kind.ISO_SESSION, Kind.kindOf(new SessionStart(1, 10, "x"))); + assertEquals(Kind.ISO_SESSION, Kind.kindOf(new SessionStart(1, 10, "127.0.0.1", 4321, 8000))); } @Test @@ -163,11 +163,11 @@ void logLevelConstantsAreUnchangedAndReferenceKind() { @Test void createEventUsesTheImpliedKindAndDefaultStaysInfo() { Log log = new Log(new Logger(), "test"); - LogEvent evt = log.createEvent(new SessionStart(1, 10, "x")); + LogEvent evt = log.createEvent(new SessionStart(1, 10, "127.0.0.1", 4321, 8000)); assertEquals(Kind.ISO_SESSION, evt.getTag()); assertEquals(1, evt.getPayLoad().size()); assertEquals(Kind.INFO, log.createEvent(new ProfilerEvt(0L, List.of())).getTag()); assertEquals(Kind.INFO, new LogEvent().getTag()); - assertEquals(Kind.ISO_SESSION, new LogEvent(log, new SessionStart(1, 10, "x")).getTag()); + assertEquals(Kind.ISO_SESSION, new LogEvent(log, new SessionStart(1, 10, "127.0.0.1", 4321, 8000)).getTag()); } }