Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,43 @@ argument:
soulfind -p 1234
```

### Debug Logging
### Logging

Enable detailed debug logging by providing the `--debug` flag:
Enable additional logging by providing a `-l` or `--log` flag:

```
soulfind --debug
soulfind -l
```

```
soulsetup --debug
soulsetup -l
```

This includes the `conn` and `db` log categories by default.

Specific log categories and message codes can also be logged. For example, this
enables the `conn`, `rx` and `t` log categories, then filtering of only message
codes `1`, `2` and `1003` to reduce the amount of output:

```
soulfind -l conn rx t 1 2 1003
```

There is no difference which order the categories and filter numbers are
specified. It's not possible to assign complex filtering rules, the code
numbers simply apply to both received and transmitted network messages.

#### Log categories:

- `conn`: Connections
- `db`: Database operations
- `msg`: Includes both `r` and `t`
- `r`: Received network messages
- `rx`: Received network messages with hexadecimal bytes
- `t`: Transmitted network messages
- `tx`: Transmitted network messages with hexadecimal bytes
- `x`: Includes both `rx` and `tx`
- `1` .. `1003`: Filter by message code

## Authors

Expand Down
81 changes: 45 additions & 36 deletions src/cli.d
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// SPDX-FileCopyrightText: 2025 Soulfind Contributors
// SPDX-FileCopyrightText: 2025-2026 Soulfind Contributors
// SPDX-License-Identifier: GPL-3.0-or-later


module soulfind.cli;
@safe:

import soulfind.defines : VERSION;
import std.algorithm.mutation : remove;
import std.array : Appender;
import std.compiler : name, version_major, version_minor;
import std.stdio : writeln;
Expand All @@ -16,11 +17,12 @@ enum l_prefix = "--";
enum column_spacing = 2;

struct CommandOption {
string s_parameter;
string l_parameter;
string description;
string arg_name;
void delegate(string) callback;
string s_parameter;
string l_parameter;
string description;
string choice_name;
string[] defaults;
void delegate(string[]) callback;
}

final class CommandException : Exception
Expand All @@ -36,15 +38,13 @@ void parse_args(string[] args, CommandOption[] options)
while (i < args.length) {
string arg = args[i];
string name;
string value;
bool found_equals;
string[] values;

if (arg.startsWith(l_prefix)) {
foreach (j, ref c; arg) {
if (c == '=') {
name = arg[l_prefix.length .. j];
value = arg[j + 1 .. $];
found_equals = true;
values ~= arg[j + 1 .. $];
break;
}
}
Expand All @@ -55,7 +55,7 @@ void parse_args(string[] args, CommandOption[] options)
}
else {
throw new CommandException(
"Unexpected positional argument: " ~ arg
args[0] ~ ": Unexpected positional argument '" ~ arg ~ "'"
);
}

Expand All @@ -69,29 +69,33 @@ void parse_args(string[] args, CommandOption[] options)
break;
}
}

if (!found_option)
throw new CommandException("Unknown option: " ~ arg);

if (option.arg_name.length > 0) {
if (found_equals && value.length > 0) {
option.callback(value);
i++;
}
else if (!found_equals
&& args.length > i + 1
&& !args[i + 1].startsWith(s_prefix)) {
option.callback(args[i + 1]);
i += 2;
}
else {
throw new CommandException("Missing value for option: " ~ arg);
}
if (!found_option) throw new CommandException(
args[0] ~ ": Unknown option '" ~ arg ~ "'"
);

while (args.length > i + 1
&& !args[i + 1].startsWith(s_prefix)
&& !args[i + 1].startsWith(l_prefix)) {
values ~= args[i + 1];
args = args.remove(i + 1);
}
else {
option.callback(null);
i++;
if (option.defaults.length == 0) {
auto max_i = (option.choice_name is null ? 0 : 1);
if (values.length > max_i) throw new CommandException(
name ~ ": Unexpected value '" ~ values[max_i] ~ "'"
);
}
if (option.choice_name !is null && values.length == 0) {
if (option.defaults.length == 0) throw new CommandException(
name ~ ": Missing value for <" ~ option.choice_name ~ ">"
);
values = option.defaults;
}

try option.callback(values);
catch (Exception e) throw new CommandException(name ~ ": " ~ e.msg);

args = args.remove(i);
}
}

Expand All @@ -113,9 +117,9 @@ void print_help(string description, CommandOption[] options)
foreach (option; options) {
const s_length = option.s_parameter.length;
auto l_length = option.l_parameter.length;
const argument_len = option.arg_name.length;
const choice_len = option.choice_name.length;

if (argument_len > 0) l_length += argument_len + 3;
if (choice_len > 0) l_length += choice_len + 3;
if (s_length > s_max) s_max = s_length;
if (l_length > l_max) l_max = l_length;
}
Expand All @@ -128,11 +132,16 @@ void print_help(string description, CommandOption[] options)

auto s_parameter = option.s_parameter;
auto l_parameter = option.l_parameter;
auto arg_name = option.arg_name;
auto choice_name = option.choice_name;

if (s_parameter.length > 0) s_parameter = s_prefix ~ s_parameter;
if (l_parameter.length > 0) l_parameter = l_prefix ~ l_parameter;
if (arg_name.length > 0) l_parameter ~= " <" ~ arg_name ~ ">";
if (choice_name.length > 0) {
if (option.defaults.length > 0)
l_parameter ~= " [" ~ choice_name ~ "]";
else
l_parameter ~= " <" ~ choice_name ~ ">";
}

output ~= s_parameter;
output ~= spacing(s_max - s_parameter.length);
Expand Down
13 changes: 9 additions & 4 deletions src/defines.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024-2025 Soulfind Contributors
// SPDX-FileCopyrightText: 2024-2026 Soulfind Contributors
// SPDX-FileCopyrightText: 2005-2017 SeeSchloss <seeschloss@seeschloss.org>
// SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -92,12 +92,17 @@ struct UserStats

enum norm = "\033[0m"; // reset to normal
enum bold = "\033[1m"; // bold intensity
enum dim = "\033[2m"; // dim intensity
enum blue = "\033[01;94m"; // foreground blue
enum red = "\033[01;91m"; // foreground red


// Logging

bool log_db;
bool log_conn;
bool log_msg;
bool log_conn;
bool log_db;
bool log_msg_in;
bool log_msg_out;
bool log_msg_rx;
bool log_msg_tx;
bool[int] log_msg_codes;
26 changes: 16 additions & 10 deletions src/server/conns.d
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// SPDX-FileCopyrightText: 2024-2025 Soulfind Contributors
// SPDX-FileCopyrightText: 2024-2026 Soulfind Contributors
// SPDX-License-Identifier: GPL-3.0-or-later


module soulfind.server.conns;
@safe:

import soulfind.defines : blue, bold, conn_backlog_length, conn_buffer_size,
log_conn, log_msg, max_in_msg_size, norm, red,
dim, log_conn, log_msg_codes, log_msg_in,
log_msg_out, log_msg_tx, max_in_msg_size, norm, red,
user_check_interval, VERSION;
import soulfind.pwhash : process_password_tasks;
import soulfind.server.messages : SMessage;
Expand All @@ -17,6 +18,7 @@ import soulfind.server.user : User;
import std.array : Appender;
import std.bitmanip : Endian, nativeToLittleEndian, peek, read;
import std.datetime : MonoTime, msecs;
import std.format : format;
import std.socket : InternetAddress, parseAddress, Socket, socket_t,
SocketAcceptException, SocketOption, SocketOptionLevel,
SocketOSException, SocketShutdown, TcpSocket, UdpSocket;
Expand Down Expand Up @@ -230,19 +232,15 @@ final class UserConnection
void send_message(Logging log = Logging.all)(scope SMessage msg,
string target_username)
{
const uint code = msg.code;
const msg_buf = msg.bytes;
const msg_len = msg_buf.length;
const offset = out_buf.length;

if (log == Logging.redacted) target_username = "[ redacted ]";
if (log_msg && log != Logging.disabled) writeln(
"[Msg] Sending -> ", blue, msg.name, norm, " (code ", msg.code,
") -> to user ", blue, target_username, norm
);

if (msg_len > uint.max) {
writeln(
"Message ", red, msg.name, norm, " (code ", msg.code,
"Message ", red, msg.name, norm, " (code ", code,
") of ", msg_len, " bytes to user ", blue, target_username,
norm, " is too large, not sending"
);
Expand All @@ -254,6 +252,14 @@ final class UserConnection
.nativeToLittleEndian;
out_buf[offset + uint.sizeof .. $] = msg_buf;

if (log_msg_out && log != Logging.disabled && code in log_msg_codes) {
writeln(
"[MSG] Transmit -> ", blue, msg.name, norm, " (code ", code,
") -> to user ", blue, target_username, norm
);
if (log_msg_tx) writeln(dim, format!"%-(%02x %)"(out_buf), norm);
}

selector.register(sock.handle, SelectEvent.write);
}

Expand All @@ -275,8 +281,8 @@ final class UserConnection
in_msg_size = in_buf.read!(uint, Endian.littleEndian);
}
if (in_msg_size < 0 || in_msg_size > max_in_msg_size) {
if (log_msg) writeln(
"[Msg] Received unexpected message size ", in_msg_size,
if (log_msg_in) writeln(
"[MSG] Received unexpected message size ", in_msg_size,
" from user ", blue, target_user.username, norm,
", disconnecting them"
);
Expand Down
19 changes: 12 additions & 7 deletions src/server/messages.d
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
module soulfind.server.messages;
@safe:

import soulfind.defines : blue, log_msg, norm, RoomTicker;
import soulfind.defines : blue, dim, log_msg_codes, log_msg_in, log_msg_rx,
norm, RoomTicker;
import soulfind.server.room : Room;
import soulfind.server.user : User;
import std.array : Appender;
import std.bitmanip : Endian, nativeToLittleEndian, peek;
import std.conv : text;
import std.datetime : days, Duration, SysTime;
import std.format : format;
import std.stdio : writeln;
import std.utf : UTFException, validate;

Expand Down Expand Up @@ -186,10 +188,13 @@ class UMessage
this.in_buf = in_buf;
code = read!uint();

if (log_msg) writeln(
"[Msg] Receive <- ", blue, this.name, norm, " (code ", code,
") <- from user ", blue, in_username, norm
);
if (log_msg_in && code in log_msg_codes) {
writeln(
"[MSG] Received <- ", blue, this.name, norm, " (code ", code,
") <- from user ", blue, in_username, norm
);
if (log_msg_rx) writeln(dim, format!"%-(%02x %)"(in_buf), norm);
}
}

private string name() scope
Expand Down Expand Up @@ -245,8 +250,8 @@ class UMessage
}
}
else {
if (log_msg) writeln(
"[Msg] Message code ", code, ", offset ", offset,
if (log_msg_in) writeln(
"[MSG] Message code ", code, ", offset ", offset,
", not enough data reading ", T.stringof, " of size ", size
);
is_valid = false;
Expand Down
20 changes: 13 additions & 7 deletions src/server/msghandler.d
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
module soulfind.server.msghandler;
@safe:

import soulfind.defines : blue, bold, log_msg, norm, pbkdf2_iterations, red,
RoomMemberType, RoomType, server_username;
import soulfind.defines : blue, bold, log_msg_codes, log_msg_in, log_msg_rx,
norm, pbkdf2_iterations, red, RoomMemberType,
RoomType, server_username;
import soulfind.pwhash : create_salt, hash_password_async;
import soulfind.server.conns : Logging;
import soulfind.server.messages;
Expand All @@ -16,6 +17,7 @@ import soulfind.server.user : User;
import std.array : array;
import std.conv : text;
import std.datetime : Clock, seconds, SysTime;
import std.format : format;
import std.socket : InternetAddress;
import std.stdio : writeln;

Expand Down Expand Up @@ -778,11 +780,15 @@ final class MessageHandler
break;

default:
if (log_msg) writeln(
"[Msg] Unimplemented message code ", red, code, norm,
" from user ", blue, user.username, norm, " with length ",
msg_buf.length
);
if (log_msg_in) {
writeln(
"[MSG] Unimplemented message code ", red, code, norm,
" from user ", blue, user.username, norm, " with length ",
msg_buf.length
);
if (log_msg_rx && code in log_msg_codes)
writeln(red, format!"%-(%02x %)"(msg_buf), norm);
}
break;
}
return true;
Expand Down
Loading
Loading