Logs
The server writes its log to standard error. Output from moin-server admin goes to standard output, so the two never mix. There's no log file: systemd, Docker and other supervisors already collect standard error.
Level and format
toml
[log]
level = "info" # error, warn, info, debug or trace
format = "text" # or "json", one object per line| Level | Includes |
|---|---|
error | The server lost something and needs you: a failed write to its data directory, a certificate that won't load, a discovery storage failure. |
warn | Something went wrong or looks hostile, and the server carried on: too many join attempts, a slow device disconnected, a slow shutdown. |
info | Startup, devices connecting and leaving, and the audit log. The default. |
debug | Connections that never became a session, and other detail. |
trace | Everything. |
level applies to Moin's own messages. The libraries it's built on only log warnings and errors, and the audit log is always kept, whatever the level.
For finer control, set the RUST_LOG environment variable. It replaces the level entirely, using EnvFilter syntax:
sh
RUST_LOG=warn,moin_coordinator=debug,audit=info moin-server --config moin-server.tomlKeep audit=info in it if you want the audit log. format still comes from the configuration file.
Text output is coloured when it goes to a terminal and NO_COLOR isn't set.
Startup
moin server starting shows the version, the server ID (endpoint_id), the path of its state file (state), the room list (catalog: configured or managed), max_connected_devices, the access mode, and whether a password, invitations and a bootstrap secret are set. It never shows their values.
It's followed by moin server listening with the bound addresses, or with [server], by stable coordinator started with the origin, the web listener, the UDP sockets, and whether relay, qad and pkarr are on.
Devices connecting
Messages about one device's connection carry its connection_id and device_id, and once it has joined, a session_id. In text they appear as a connection{…} prefix; in JSON they're in a span object.
| Message | Level | Details |
|---|---|---|
session opened | info | user_agent: the app and platform, such as Moin/2026.1 (macos) |
session rejected | info, or warn for RateLimited | reason, below |
session ended | info | reason: Disconnected, SessionReplaced, ProtocolViolation, ServerShutdown or AccessRevoked |
disconnecting a closed or slow control connection | warn | |
incoming connection failed, control stream not opened, session opening not received, control connection ended | debug |
Reasons a device is turned away:
| Reason | Meaning |
|---|---|
InvalidNickname | The app sent a nickname the server doesn't accept. |
ServerFull | max_connected_devices is reached. |
ServerUnavailable | The server couldn't use its data directory, or is shutting down. Look for an error just before it. |
AuthenticationRequired | The server is protected and the device isn't a member. |
PasswordRequired, InvalidPassword | No password, or the wrong one. |
InvalidInvite, InviteExpired, InviteExhausted | The invitation is unknown or revoked, expired, or used up. |
RateLimited | Too many join attempts; see Guessing protection. |
Audit
Every administrative change is logged as privileged action, whether it succeeded or was refused, and whether it came from the app or from moin-server admin. Listings and status aren't logged.
| Field | Meaning |
|---|---|
actor | The device ID that made the change, or local for moin-server admin. |
action | claim_admin, claim_invite, set_role, clear_role, remove_device, create_invite, revoke_invite, revoke_all_invites, move_member, create_room, edit_room, delete_room, configure_access, update_access_policy, enroll_device, revoke_device or revoke_all_devices. |
subject | The device, room or invitation acted on, or the one created. |
role | The role given, for set_role, and the role an invitation carries, for create_invite and claim_invite. |
room | The destination, for move_member; absent when the member was moved out of their room. |
revoked_invite | The invitation revoked along with the device, for remove_device. |
outcome | ok, or why it was refused, such as PermissionDenied. |
claim_invite is a device joining with an invitation that has a role: it became a member and got the role. Only successful ones are logged here; a refused join logs session rejected instead.
Devices disconnected by a change also log session ended with AccessRevoked.
To keep only the audit log, select target == "audit" in JSON output, or lines containing audit: in text.
What the log leaves out
Moin's own messages identify devices by device ID, and rooms and invitations by ID. They never contain nicknames, room or community names, IP addresses, passwords, invitation links or the bootstrap secret. Configuration errors give the line and column, never the line's contents.
Messages from the libraries Moin uses can include network addresses. Keep that in mind when choosing how long to keep logs.
