Know when your players were last seen — right in-game, every time they log in.
PlayerSessionLog is a lightweight Paper server plugin that records every login and logout in a persistent plain-text log file and greets each player on join with a private summary of their last sessions — like the Unix last command, but built into Minecraft.
- Automatic session logging — every login and logout is written to
session.logwith a precise timestamp and session duration - Private login history — players see their last sessions (up to 10 by default) as a private message on join; no chat broadcast, no clutter
- Unix
last-style output — column-alignedLOGIN/LOGOUTentries withhh:mm:ssduration, colour-coded green and yellow - Configurable timezone and timestamp format — set any Java
ZoneId(e.g.Europe/Berlin,UTC,America/New_York) and anyDateTimeFormatterpattern - Admin
/psl history <name>command — look up any player's session history from the console or in-game without opening files - Hot-reload —
/psl reloadapplies config changes without a server restart - Zero external dependencies — only the Paper API; the final JAR is under 20 KB
| Component | Version |
|---|---|
| Paper Server | 26.1.2 (Minecraft 1.21.2) |
| Java (runtime) | 25+ |
PlayerSessionLog uses the Paper Adventure API and Paper-specific APIs. It will not run on Spigot.
- Download
PlayerSessionLog-1.0.0.jarfrom the Releases page. - Drop the JAR into your server's
plugins/folder. - Start or restart the server. The plugin creates
plugins/PlayerSessionLog/config.ymlandplugins/PlayerSessionLog/session.logautomatically. - Adjust
config.ymlas needed and run/psl reload— no restart required.
After connecting, each player receives a private history message (visible only to them):
-------- Your last sessions --------
[2026-06-01 09:00:14] LOGIN Notch
[2026-06-01 09:47:32] LOGOUT Notch (Duration: 00:47:18)
[2026-05-31 20:05:11] LOGIN Notch
[2026-05-31 22:17:43] LOGOUT Notch (Duration: 02:12:32)
[2026-05-30 18:30:00] LOGIN Notch
[2026-05-30 19:45:22] LOGOUT Notch (Duration: 01:15:22)
-----------------------------
LOGIN lines are shown in green, LOGOUT lines in yellow. The current session is always excluded from the history (it would only show the login entry, with no logout yet).
plugins/PlayerSessionLog/session.log — plain text, one event per line, human-readable and greppable:
[2026-06-01 09:00:14] LOGIN Notch
[2026-06-01 09:47:32] LOGOUT Notch (Duration: 00:47:18)
[2026-06-01 10:15:03] LOGIN Steve
[2026-06-01 10:15:05] LOGIN Alex
[2026-06-01 11:20:44] LOGOUT Steve (Duration: 01:05:41)
[2026-06-01 12:00:00] LOGIN Notch
[2026-06-01 12:31:17] LOGOUT Alex (Duration: 02:16:12)
[2026-06-01 14:22:09] LOGOUT Notch (Duration: 02:22:09)
All entries from all players go into the same chronological file, matching standard Unix syslog conventions. The file grows continuously; see Known Limitations for notes on rotation.
plugins/PlayerSessionLog/config.yml
# Maximum number of history entries shown to the player on login (1–20)
max-history-entries: 10
# Delay in ticks before the login message is sent (20 ticks = 1 second)
login-message-delay-ticks: 20
# Log file name (always written to plugins/PlayerSessionLog/)
log-filename: "session.log"
# Timestamp format — any valid Java DateTimeFormatter pattern
timestamp-format: "yyyy-MM-dd HH:mm:ss"
# Timezone — any valid Java ZoneId
timezone: "Europe/Berlin"
# Print each logged event to the server console
debug: false
# In-game history message formatting (&-style colour codes supported)
history-header: "&8&m----&r &6Your last sessions &8&m----"
history-footer: "&8&m-----------------------------"
no-history-message: "&7No previous sessions found."| Value | Region |
|---|---|
UTC |
Universal Coordinated Time |
Europe/Berlin |
Central European Time (CET/CEST) |
Europe/London |
British Time (GMT/BST) |
America/New_York |
Eastern Time (ET) |
America/Los_Angeles |
Pacific Time (PT) |
Asia/Tokyo |
Japan Standard Time |
Any identifier from the IANA Time Zone Database is accepted.
| Command | Description | Permission |
|---|---|---|
/psl reload |
Hot-reloads config.yml |
playersessionlog.admin |
/psl history <name> |
Shows the session history for any player | playersessionlog.admin |
/sessionlog |
Alias for /psl |
— |
| Permission | Description | Default |
|---|---|---|
playersessionlog.admin |
Access to all /psl sub-commands |
op |
playersessionlog.history.self |
Receive the private session history on login | true |
Revoke playersessionlog.history.self from a group to silence the login message for that group without affecting logging.
PlayerSessionLog was designed with security as a first-class concern, not an afterthought.
No sensitive data is ever written to the log file. IP addresses, passwords, chat messages, and inventory contents are never recorded — only the player name, a timestamp, and the session duration.
Log injection is prevented at the source. Before any player name is written to the file, it is stripped of all control characters and line breaks (CR, LF, TAB, and every character in the \x00–\x1F and \x7F ranges), then validated against the Mojang-standard username whitelist regex [a-zA-Z0-9_]{1,16}. A name that fails validation is rejected entirely — nothing is written, and a warning is emitted to the server console instead.
Path traversal in log-filename is blocked. The configured file name is passed through new File(name).getName() before use, which strips any leading path components. A value like ../../server.properties silently resolves to server.properties in the plugin's own data folder.
All file I/O is thread-safe. Every write goes through a synchronized block on a dedicated lock object. Login timestamps are tracked in a ConcurrentHashMap. The BufferedWriter is flushed and closed cleanly in onDisable().
No Log4Shell exposure. Player-controlled data is never passed to Log4j or SLF4J. All logging of player names uses a custom FileWriter-based class that never evaluates ${}-style expressions.
PlayerJoinEvent (MONITOR priority)
└─ Store login timestamp in ConcurrentHashMap<UUID, Instant>
└─ Write LOGIN entry to session.log
└─ Schedule sendHistoryMessage() with 1-tick delay (player fully loaded)
└─ Read last N+1 entries from session.log (reverse scan, last 5 MB)
└─ Drop entry 0 (the current login just written)
└─ Send remaining entries as a private Adventure Component message
PlayerQuitEvent (MONITOR priority)
└─ Look up login timestamp from ConcurrentHashMap
└─ Compute Duration.between(loginTime, now)
└─ Write LOGOUT entry with duration to session.log
└─ Remove player from ConcurrentHashMap
The history reader uses RandomAccessFile to scan only the tail of the log file (at most the last 5 MB), so it stays fast even on servers that have been running for years. It reads lines forward through that window and maintains a fixed-size deque — no loading the entire file into memory.
| Tool | Version |
|---|---|
| JDK | 21 (to run Gradle) + 25 (to compile against Paper API) |
| Git | any |
The Gradle wrapper is included — no system-wide Gradle installation required.
Install OpenJDK 21 and 25 on Ubuntu/Debian:
sudo apt-get install openjdk-21-jdk openjdk-25-jdk-headless# Clone the repository
git clone https://github.com/yourname/playersessionlog.git
cd playersessionlog
# Build (Gradle runs on Java 21; the toolchain compiles against Java 25)
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew clean jarThe compiled plugin JAR is written to:
build/libs/PlayerSessionLog-1.0.0.jar
Copy it into your server's plugins/ folder and you're done.
The Paper 26.1.2 API is compiled as Java 25 class files, so the compiler must target Java 25. However, Gradle's Kotlin DSL build scripts have a known compatibility issue when Gradle itself is launched with Java 25. The solution is to run Gradle with Java 21 and let Gradle's toolchain support automatically invoke the Java 25 javac for the compilation step. Gradle will locate the Java 25 JDK automatically from your system's installed JVMs.
# Compile only, no JAR
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew compileJava
# Wipe all build output
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew clean
# Clean + build in one step
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 ./gradlew clean jarThe two closest open-source alternatives are M0-SessionLogger (SpigotMC) and LastLog (SpigotMC/CurseForge). The table below is based on verified source code and documentation. ✅ and ❌ are only used where a feature's presence or absence is confirmed; — means the feature is not documented and its status is unknown.
| Feature | PlayerSessionLog | M0-SessionLogger | LastLog |
|---|---|---|---|
| Private session history shown to player on login | ✅ | ❌ | ❌ |
| Session duration tracked and logged | ✅ | ✅ | ❌ |
| Persistent plain-text log file | ✅ | ✅ | ❌ ¹ |
| No IP address logging | ✅ | ✅ | ✅ |
| Admin history lookup command | ✅ | ❌ | ✅ |
| Configurable timestamp format | ✅ | ✅ | ❌ |
| Configurable timezone | ✅ | — | ❌ |
| Hot-reload config without restart | ✅ | ❌ | ❌ |
| Zero external dependencies | ✅ | ❌ ² | ✅ |
| Log injection protection | ✅ | — | — |
| Modern Paper (1.21+) support | ✅ | ❌ ³ | — |
¹ LastLog has no data storage of its own — it reads Bukkit's built-in player data, which means it cannot record session duration or build a history that persists beyond what Bukkit already tracks.
² M0-SessionLogger requires EssentialsX as a hard dependency.
³ M0-SessionLogger targets Minecraft 1.12–1.16 and is not compatible with modern Paper builds.
- No automatic log rotation.
session.loggrows indefinitely. For production servers it is recommended to set up external rotation (e.g.logrotateon Linux) or periodically archive the file. The reader scans at most the last 5 MB, so very old entries beyond that window will not appear in the in-game history even if they exist in the file. - Crash resilience. If the server crashes hard (SIGKILL, power loss) without calling
onDisable(), theLOGOUTentry for any active session will be missing. The next login for that player will show the orphanedLOGINline in their history. - Single file, all players. All events are written to one shared file. This is intentional (it mirrors
lastsemantics and keeps the format greppable), but there is no per-player file option currently.
MIT — see LICENSE for details.