You can use the standard HttpSessionListener to catch when HTTP session is destroyed.
Below is a solution that creates a log record on user login and updates the record when the user logs out or the session expires.
The log item entity:
@JmixEntity
@Table(name = "USER_AUTHENTICATION_LOG")
@Entity
public class UserAuthenticationLog {
    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    private UUID id;
    @Column(name = "VERSION", nullable = false)
    @Version
    private Integer version;
    @Column(name = "USERNAME", nullable = false)
    @NotNull
    private String username;
    @Column(name = "LOGGED_IN")
    private LocalDateTime loggedIn;
    @Column(name = "LOGGED_OUT")
    private LocalDateTime loggedOut;
    @Column(name = "SESSION_ID")
    private String sessionId;
    // getters and setters
}
The listener bean:
@Component
public class AuthenticationEventListener implements HttpSessionListener {
    @Autowired
    private UnconstrainedDataManager dataManager;
    @EventListener
    public void onInteractiveAuthenticationSuccess(InteractiveAuthenticationSuccessEvent event) {
        UserAuthenticationLog logItem = dataManager.create(UserAuthenticationLog.class);
        User user = (User) event.getAuthentication().getPrincipal();
        logItem.setUsername(user.getUsername());
        logItem.setLoggedIn(LocalDateTime.now());
        // Jmix provides session ID in ClientDetails class
        logItem.setSessionId(((ClientDetails) event.getAuthentication().getDetails()).getSessionId());
        dataManager.save(logItem);
    }
    @EventListener
    public void onLogoutSuccess(LogoutSuccessEvent event) {
        // we use session ID to find matching login record
        String sessionId = ((ClientDetails) event.getAuthentication().getDetails()).getSessionId();
        UserAuthenticationLog logItem = getLogOptional(sessionId)
                .orElseGet(() -> {
                    UserAuthenticationLog newLogItem = dataManager.create(UserAuthenticationLog.class);
                    User user = (User) event.getAuthentication().getPrincipal();
                    newLogItem.setUsername(user.getUsername());
                    return newLogItem;
                });
        if (logItem.getLoggedOut() == null) {
            logItem.setLoggedOut(LocalDateTime.now());
            dataManager.save(logItem);
        }
    }
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // we use session ID to find matching login record
        getLogOptional(se.getSession().getId())
                .ifPresent(logItem -> {
                    if (logItem.getLoggedOut() == null) {
                        logItem.setLoggedOut(LocalDateTime.now());
                        dataManager.save(logItem);
                    }
                });
    }
    private Optional<UserAuthenticationLog> getLogOptional(String sessionId) {
        return dataManager.load(UserAuthenticationLog.class)
                .query("e.sessionId = ?1", sessionId)
                .optional();
    }
}
To test the session expiration in UI, use the following property:
# 2 minutes 
jmix.ui.http-session-expiration-timeout-sec = 120
Regards,
Konstantin