Skip to content
Merged
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
9 changes: 8 additions & 1 deletion example/example.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ pub fn main() !void {
.err = "ErrOr",
.debug = "DeBuG",
},
.quiet = true, // disable stderr logging, default is false
.quiet = false, // disable stderr logging, default is false
.mutex = .none, // none by default
});
var writer = std.fs.File.stdout().writer(&buffer);
try stdout_log.init(allocator, &.{&writer.interface}, &env);
defer stdout_log.deinit(allocator);

// wait we actually don't want stderr logging let's disable it
stdout_log.quiet = true;

stdout_log.debug("Hello, stdout with no colors", .{});
stdout_log.scoped(.main).err("scoped :)", .{});
}
Expand All @@ -54,6 +57,10 @@ pub fn main() !void {
defer std_log.deinit(allocator);

std.log.info("std.log.info with axe.Axe(.{{}})", .{});

// actually we want forced colors, try running with NO_COLOR=1
std_log.updateTtyConfig(.always);

std.log.scoped(.main).warn("this is scoped", .{});
}

Expand Down
106 changes: 75 additions & 31 deletions src/axe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,14 @@ pub const Config = struct {
strftime: []const u8,
} = .disabled,
/// Whether to enable color output.
color: enum {
/// Check for NO_COLOR, CLICOLOR_FORCE and tty support on stderr.
/// Color output is disabled on other writers.
auto,
/// Enable color output on every writers.
always,
/// Disable color output on every writers.
never,
} = .auto,
/// This can be modified at runtime using `updateTtyConfig`.
color: Color = .auto,
/// Set to `.none` to disable all styles.
styles: Styles = .{},
/// The text to display for each log level.
level_text: LevelText = .{},
/// Whether to write log messages to stderr.
/// This is modifiable at runtime.
quiet: bool = false,
/// The mutex interface to use for the log messages.
mutex: union(enum) {
Expand All @@ -68,15 +62,13 @@ pub const Config = struct {

/// Create a new logger based on the given configuration.
pub fn Axe(comptime config: Config) type {
const writers_tty_config: tty.Config = switch (config.color) {
.always => .escape_codes,
.auto, .never => .no_color,
};

return struct {
/// Whether to write log messages to stderr.
pub var quiet = config.quiet;

var writers: []*std.Io.Writer = &.{};
// zig/llvm can't handle this without explicit type
var stderr_tty_config: if (config.quiet) void else tty.Config = if (config.quiet) {} else .no_color;
var stderr_tty_config = defaultTtyConfig(config.color);
var writers_tty_config = defaultTtyConfig(config.color);
var timezone = if (config.time_format != .disabled) zeit.utc else {};
var mutex = switch (config.mutex) {
.none, .function => {},
Expand Down Expand Up @@ -111,16 +103,7 @@ pub fn Axe(comptime config: Config) type {
if (additional_writers) |_writers| {
writers = try allocator.dupe(*std.Io.Writer, _writers);
}
if (!config.quiet) {
stderr_tty_config = switch (config.color) {
.auto => .detect(std.fs.File.stderr()),
.always => if (builtin.os.tag == .windows) switch (.detect(std.fs.File.stderr())) {
.no_color, .escape_codes => .escape_codes,
.windows_api => |ctx| .{ .windows_api = ctx },
} else .escape_codes,
.never => .no_color,
};
}
updateTtyConfig(config.color);
}

/// Deinitialize the logger.
Expand All @@ -131,6 +114,22 @@ pub fn Axe(comptime config: Config) type {
allocator.free(writers);
}

/// Update tty configuration for stderr and additional writers.
pub fn updateTtyConfig(color: Color) void {
writers_tty_config = defaultTtyConfig(color);
stderr_tty_config = switch (color) {
.auto => .detect(std.fs.File.stderr()),
.always => if (builtin.os.tag == .windows)
switch (tty.Config.detect(std.fs.File.stderr())) {
.no_color, .escape_codes => .escape_codes,
.windows_api => |ctx| .{ .windows_api = ctx },
}
else
.escape_codes,
.never => .no_color,
};
}

/// Returns a scoped logging namespace that logs all messages using the scope provided.
pub fn scoped(comptime scope: @Type(.enum_literal)) type {
return struct {
Expand Down Expand Up @@ -281,7 +280,7 @@ pub fn Axe(comptime config: Config) type {
print(src, writer, writers_tty_config, time, level, scope, format, args);
writer.flush() catch {};
}
if (!config.quiet) {
if (!quiet) {
var buffer: [256]u8 = undefined;
var stderr = std.fs.File.stderr().writer(&buffer);
print(src, &stderr.interface, stderr_tty_config, time, level, scope, format, args);
Expand Down Expand Up @@ -417,6 +416,7 @@ pub const Style = union(enum) {
bg_rgb: struct { r: u8, g: u8, b: u8 },
/// #RRGGBB, #RGB, RRGGBB, RGB
hex: []const u8,
/// #RRGGBB, #RGB, RRGGBB, RGB
bg_hex: []const u8,

inline fn fmt(comptime styles: []const Style, comptime text: []const u8) []const u8 {
Expand Down Expand Up @@ -563,6 +563,16 @@ pub const Style = union(enum) {
}
};

pub const Color = enum {
/// Check for NO_COLOR, CLICOLOR_FORCE and tty support on stderr.
/// Color output is disabled on other writers.
auto,
/// Enable color output on every writers.
always,
/// Disable color output on every writers.
never,
};

pub const Styles = struct {
// levels
err: []const Style = &.{ .bold, .red },
Expand Down Expand Up @@ -713,6 +723,13 @@ fn writeLocation(
}
}

inline fn defaultTtyConfig(color: Color) tty.Config {
return switch (color) {
.always => .escape_codes,
.auto, .never => .no_color,
};
}

test "log without styles" {
const expectEqualStrings = std.testing.expectEqualStrings;
var buffer: [256]u8 = undefined;
Expand Down Expand Up @@ -782,6 +799,7 @@ test "log with complex config" {
}

test "time format" {
const expectEqual = std.testing.expectEqual;
var dest: std.Io.Writer.Allocating = .init(std.testing.allocator);
defer dest.deinit();

Expand All @@ -790,7 +808,6 @@ test "time format" {
.scope_format = "|%",
.time_format = .{ .strftime = "%Y-%m-%d %H:%M:%S" },
.loc_format = "|%m",
.quiet = true,
.color = .never,
.level_text = .{
.debug = "DBG",
Expand All @@ -799,20 +816,21 @@ test "time format" {
.err = "ERR",
},
});
log.quiet = true;
try log.init(std.testing.allocator, &.{&dest.writer}, null);
defer log.deinit(std.testing.allocator);

log.info("Hello {c}", .{'W'});
// [YYYY-mm-dd HH:MM:SS|INF] Hello W
try std.testing.expectEqual(34, dest.written().len);
try expectEqual(34, dest.written().len);
dest.writer.end = 0;
log.scoped(.foo).warn("Hi", .{});
// [YYYY-mm-dd HH:MM:SS|WRN|foo] Hi
try std.testing.expectEqual(33, dest.written().len);
try expectEqual(33, dest.written().len);
dest.writer.end = 0;
log.errAt(@src(), "Bye {}", .{'*'});
// [YYYY-mm-dd HH:MM:SS|ERR|root] Bye 42
try std.testing.expectEqual(38, dest.written().len);
try expectEqual(38, dest.written().len);
}

test "json log" {
Expand Down Expand Up @@ -855,3 +873,29 @@ test "json log" {
\\
, writer.buffered());
}

test "updateTtyConfig" {
const expectEqualStrings = std.testing.expectEqualStrings;
var buffer: [256]u8 = undefined;
var writer = std.Io.Writer.fixed(&buffer);

const log = Axe(.{
.color = .never,
.quiet = true,
});
try log.init(std.testing.allocator, &.{&writer}, null);
defer log.deinit(std.testing.allocator);

log.debug("No color", .{});
try expectEqualStrings("debug: No color\n", writer.buffered());
writer.end = 0;

log.updateTtyConfig(.always);
log.debug("With color", .{});
try expectEqualStrings(Style.fmt(&.{ .bold, .cyan }, "debug") ++ ": With color\n", writer.buffered());
writer.end = 0;

log.updateTtyConfig(.never);
log.debug("No color again", .{});
try expectEqualStrings("debug: No color again\n", writer.buffered());
}
Loading