Skip to content
Open
Show file tree
Hide file tree
Changes from 18 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
2 changes: 1 addition & 1 deletion src/graphics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ pub const TextBuffer = struct { // MARK: TextBuffer
return next[0];
}

fn parse(self: *Parser) void {
pub fn parse(self: *Parser) void {
self.curIndex = @intCast(self.unicodeIterator.i);
self.curChar = self.unicodeIterator.nextCodepoint() orelse return;
while (true) {
Expand Down
215 changes: 215 additions & 0 deletions src/log.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
const std = @import("std");
const main = @import("main");
const files = main.files;
const fmt = main.fmt;
const graphics = main.graphics;
const gui = main.gui;
const List = main.List;
const settings = main.settings;

pub const Level = enum {
/// Error: something has gone wrong. This might be recoverable or might
/// be followed by the program exiting.
err,
/// Warning: it is uncertain if something has gone wrong or not, but the
/// circumstances would be worth investigating.
warn,
/// Info: general messages about the state of the program.
info,
/// Debug: messages only useful for debugging.
debug,
/// server messages
server,
/// chat messages
chat,

fn isColorCoded(self: Level) bool {
return self == .chat or self == .server;
}
};

var logFile: ?std.Io.File = undefined;
var logFileTs: ?std.Io.File = undefined;
var supportsANSIColors: bool = undefined;
var openingErrorWindow: bool = false;

pub noinline fn runtimeLogFn(level: Level, format: []const u8, args: []const fmt.FormatArg) void {
var buf: [65536]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
fmt.format(&writer, format, args) catch {
std.log.err("Truncated long log message.", .{});
};

const color: []const u8 = switch (level) {
.err => "\x1b[31m",
.info => "",
.warn => "\x1b[33m",
.debug => "\x1b[37;44m",
.server => "\x1b[34mserver\x1b[0m: ",
.chat => "\x1b[36mchat\x1b[0m: ",
};
const colorReset = "\x1b[0m\n";
const filePrefix = switch (level) {
.err => "error",
.warn => "warning",
.info => "info",
.debug => "debug",
.server => "server",
.chat => "chat",
};
const fileSuffix = "\n";

logToFile("[{s}]: {s}{s}", .{filePrefix, writer.buffered(), fileSuffix});
if (supportsANSIColors) {
logToStdErr(level, "{s}{s}{s}", .{color, writer.buffered(), colorReset});
} else {
logToStdErr(level, "[{s}]: {s}{s}", .{filePrefix, writer.buffered(), fileSuffix});
}

if (level == .err and !openingErrorWindow and !settings.launchConfig.headlessServer) {
openingErrorWindow = true;
gui.openWindow("error_prompt");
openingErrorWindow = false;
}
}

pub fn init() void {
logFile = null;
files.cwd().makePath("logs") catch |err| {
std.log.err("Couldn't create logs folder: {s}", .{@errorName(err)});
return;
};
logFile = std.Io.Dir.cwd().createFile(main.io, "logs/latest.log", .{}) catch |err| {
std.log.err("Couldn't create logs/latest.log: {s}", .{@errorName(err)});
return;
};

const _timestamp = std.Io.Clock.Timestamp.now(main.io, .real).raw;

const _path_str = std.fmt.allocPrint(main.stackAllocator.allocator, "logs/ts_{}.log", .{_timestamp.nanoseconds}) catch unreachable;
defer main.stackAllocator.free(_path_str);

logFileTs = std.Io.Dir.cwd().createFile(main.io, _path_str, .{}) catch |err| {
std.log.err("Couldn't create {s}: {s}", .{_path_str, @errorName(err)});
return;
};

supportsANSIColors = std.Io.File.stdout().supportsAnsiEscapeCodes(main.io) catch unreachable;
}

pub fn deinit() void {
if (logFile) |_logFile| {
_logFile.close(main.io);
logFile = null;
}

if (logFileTs) |_logFileTs| {
_logFileTs.close(main.io);
logFileTs = null;
}
}

fn logToFile(comptime format: []const u8, args: anytype) void {
var buf: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();

const string = std.fmt.allocPrint(allocator, format, args) catch format;
(logFile orelse return).writeStreamingAll(main.io, string) catch {};
(logFileTs orelse return).writeStreamingAll(main.io, string) catch {};
}

fn logToStdErr(level: Level, comptime format: []const u8, args: anytype) void {
var buf: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();

const _string = std.fmt.allocPrint(allocator, format, args) catch format;
const string = if (level.isColorCoded() and supportsANSIColors) convertColorToANSI(_string) else _string;
defer if (level.isColorCoded() and supportsANSIColors) main.stackAllocator.free(string);

const writer = std.debug.lockStderr(&.{});
defer std.debug.unlockStderr();
nosuspend writer.file_writer.interface.writeAll(string) catch {};
}

fn convertColorToANSI(text: []const u8) []const u8 {
Comment thread
Wunka marked this conversation as resolved.
Outdated
var list: List(u8) = .empty;

var parser = graphics.TextBuffer.Parser{
.unicodeIterator = std.unicode.Utf8Iterator{.bytes = text, .i = 0},
.currentFontEffect = .{},
.parsedText = .init(main.stackAllocator),
.fontEffects = .init(main.stackAllocator),
.characterIndex = .init(main.stackAllocator),
.showControlCharacters = false,
};
defer parser.fontEffects.deinit();
defer parser.parsedText.deinit();
defer parser.characterIndex.deinit();
parser.parse();

parser.currentFontEffect = .{};
Comment thread
Wunka marked this conversation as resolved.
Outdated
for (0..parser.parsedText.items.len) |i| {
if (parser.fontEffects.items[i].color != parser.currentFontEffect.color) {
list.appendSlice(main.stackAllocator, "\x1b[38;2");
var shift: u5 = 16;
while (true) : (shift -= 8) {
list.print(main.stackAllocator, ";{d}", .{@as(u8, @truncate(parser.fontEffects.items[i].color >> shift))});
if (shift == 0) break;
}
list.append(main.stackAllocator, 'm');
}
if (parser.fontEffects.items[i].bold != parser.currentFontEffect.bold) {
list.appendSlice(main.stackAllocator, "\x1b[");
Comment thread
Wunka marked this conversation as resolved.
Outdated
if (!parser.currentFontEffect.bold) {
list.append(main.stackAllocator, '1');
} else {
list.appendSlice(main.stackAllocator, "22");
}
list.append(main.stackAllocator, 'm');
}
if (parser.fontEffects.items[i].italic != parser.currentFontEffect.italic) {
list.appendSlice(main.stackAllocator, "\x1b[");
if (parser.currentFontEffect.italic) {
list.append(main.stackAllocator, '2');
}
list.appendSlice(main.stackAllocator, "3m");
}
if (parser.fontEffects.items[i].strikethrough != parser.currentFontEffect.strikethrough) {
list.appendSlice(main.stackAllocator, "\x1b[");
if (parser.currentFontEffect.strikethrough) {
list.append(main.stackAllocator, '2');
}
list.appendSlice(main.stackAllocator, "9m");
}
if (parser.fontEffects.items[i].underline != parser.currentFontEffect.underline) {
list.appendSlice(main.stackAllocator, "\x1b[");
if (parser.currentFontEffect.underline) {
list.append(main.stackAllocator, '2');
}
list.appendSlice(main.stackAllocator, "4m");
}
parser.currentFontEffect = parser.fontEffects.items[i];
var testBuff: [3]u8 = undefined;
const len = std.unicode.utf8Encode(@truncate(parser.parsedText.items[i]), &testBuff) catch continue;
Comment thread
Wunka marked this conversation as resolved.
Outdated
list.appendSlice(main.stackAllocator, testBuff[0..len]);
}
return list.toOwnedSlice(main.stackAllocator);
}

pub fn server(comptime format: []const u8, args: anytype) void {
var runtimeArgs: [args.len]fmt.FormatArg = undefined;
inline for (0..args.len) |i| {
runtimeArgs[i] = .fromAnytype(@TypeOf(args[i]), &args[i]);
}
runtimeLogFn(.server, format, &runtimeArgs);
}

pub fn chat(comptime format: []const u8, args: anytype) void {
var runtimeArgs: [args.len]fmt.FormatArg = undefined;
inline for (0..args.len) |i| {
runtimeArgs[i] = .fromAnytype(@TypeOf(args[i]), &args[i]);
}
runtimeLogFn(.chat, format, &runtimeArgs);
}
105 changes: 5 additions & 100 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub const game = @import("game.zig");
pub const graphics = @import("graphics.zig");
pub const itemdrop = @import("itemdrop.zig");
pub const items = @import("items.zig");
pub const log = @import("log.zig");
pub const meta = @import("meta.zig");
pub const migrations = @import("migrations.zig");
pub const models = @import("models.zig");
Expand Down Expand Up @@ -86,10 +87,7 @@ fn cacheStringImpl(comptime len: usize, comptime str: [len]u8) []const u8 {
fn cacheString(comptime str: []const u8) []const u8 {
return cacheStringImpl(str.len, str[0..].*);
}
var logFile: ?std.Io.File = undefined;
var logFileTs: ?std.Io.File = undefined;
var supportsANSIColors: bool = undefined;
var openingErrorWindow: bool = false;

// overwrite the log function:
pub const std_options: std.Options = .{ // MARK: std_options
.log_level = .debug,
Expand All @@ -104,104 +102,11 @@ pub const std_options: std.Options = .{ // MARK: std_options
inline for (0..args.len) |i| {
runtimeArgs[i] = .fromAnytype(@TypeOf(args[i]), &args[i]);
}
runtimeLogFn(level, format, &runtimeArgs);
log.runtimeLogFn(@enumFromInt(@intFromEnum(level)), format, &runtimeArgs);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good idea, please use a switch statement or a string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all done

}
}.logFn,
};

noinline fn runtimeLogFn(level: std.log.Level, format: []const u8, args: []const fmt.FormatArg) void {
var buf: [65536]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
fmt.format(&writer, format, args) catch {
std.log.err("Truncated long log message.", .{});
};

const color: []const u8 = switch (level) {
std.log.Level.err => "\x1b[31m",
std.log.Level.info => "",
std.log.Level.warn => "\x1b[33m",
std.log.Level.debug => "\x1b[37;44m",
};
const colorReset = "\x1b[0m\n";
const filePrefix = switch (level) {
.err => "error",
.warn => "warning",
.info => "info",
.debug => "debug",
};
const fileSuffix = "\n";

logToFile("[{s}]: {s}{s}", .{filePrefix, writer.buffered(), fileSuffix});
if (supportsANSIColors) {
logToStdErr("{s}{s}{s}", .{color, writer.buffered(), colorReset});
} else {
logToStdErr("[{s}]: {s}{s}", .{filePrefix, writer.buffered(), fileSuffix});
}

if (level == .err and !openingErrorWindow and !settings.launchConfig.headlessServer) {
openingErrorWindow = true;
gui.openWindow("error_prompt");
openingErrorWindow = false;
}
}

fn initLogging() void {
logFile = null;
files.cwd().makePath("logs") catch |err| {
std.log.err("Couldn't create logs folder: {s}", .{@errorName(err)});
return;
};
logFile = std.Io.Dir.cwd().createFile(io, "logs/latest.log", .{}) catch |err| {
std.log.err("Couldn't create logs/latest.log: {s}", .{@errorName(err)});
return;
};

const _timestamp = std.Io.Clock.Timestamp.now(io, .real).raw;

const _path_str = std.fmt.allocPrint(stackAllocator.allocator, "logs/ts_{}.log", .{_timestamp.nanoseconds}) catch unreachable;
defer stackAllocator.free(_path_str);

logFileTs = std.Io.Dir.cwd().createFile(io, _path_str, .{}) catch |err| {
std.log.err("Couldn't create {s}: {s}", .{_path_str, @errorName(err)});
return;
};

supportsANSIColors = std.Io.File.stdout().supportsAnsiEscapeCodes(io) catch unreachable;
}

fn deinitLogging() void {
if (logFile) |_logFile| {
_logFile.close(io);
logFile = null;
}

if (logFileTs) |_logFileTs| {
_logFileTs.close(io);
logFileTs = null;
}
}

fn logToFile(comptime format: []const u8, args: anytype) void {
var buf: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();

const string = std.fmt.allocPrint(allocator, format, args) catch format;
(logFile orelse return).writeStreamingAll(io, string) catch {};
(logFileTs orelse return).writeStreamingAll(io, string) catch {};
}

fn logToStdErr(comptime format: []const u8, args: anytype) void {
var buf: [65536]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
const allocator = fba.allocator();

const string = std.fmt.allocPrint(allocator, format, args) catch format;
const writer = std.debug.lockStderr(&.{});
defer std.debug.unlockStderr();
nosuspend writer.file_writer.interface.writeAll(string) catch {};
}

// MARK: Callbacks
fn escape(mods: Window.Key.Modifiers) void {
if (gui.selectedTextInput != null) gui.setSelectedTextInput(null);
Expand Down Expand Up @@ -434,8 +339,8 @@ pub fn main(args: std.process.Init.Minimal) void { // MARK: main()
threadedIo = .init(globalAllocator.allocator, .{});
defer threadedIo.deinit();

initLogging();
defer deinitLogging();
log.init();
defer log.deinit();

std.log.info("Starting game with version {s}", .{settings.version.version});

Expand Down
2 changes: 1 addition & 1 deletion src/server/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ pub fn messageFrom(msg: []const u8, source: *User) void { // MARK: message
fn sendRawMessage(msg: []const u8) void {
chatMutex.lock();
defer chatMutex.unlock();
std.log.info("Chat: {s}", .{msg}); // TODO use color \033[0;32m
main.log.chat("{s}", .{msg});
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for (userList) |user| {
Expand Down
2 changes: 1 addition & 1 deletion src/sync.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1751,7 +1751,7 @@ pub const Command = struct { // MARK: Command
if (ctx.side == .server) {
const user = ctx.user orelse return;
if (main.server.world.?.settings.allowCheats) {
std.log.info("User \"{f}\" executed command \"{s}\"", .{user, self.message}); // TODO use color \033[0;32m
main.log.server("User \"{f}§#ffffff\" executed command \"{s}\"", .{user, self.message});
main.server.command.execute(self.message, user);
} else {
user.sendRawMessage("Commands are not allowed because cheats are disabled");
Expand Down
Loading