Commit 629e2e7844
Changed files (4)
lib/std/heap/log_to_writer_allocator.zig
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+const std = @import("../std.zig");
+const Allocator = std.mem.Allocator;
+
+/// This allocator is used in front of another allocator and logs to the provided writer
+/// on every call to the allocator. Writer errors are ignored.
+pub fn LogToWriterAllocator(comptime Writer: type) type {
+ return struct {
+ allocator: Allocator,
+ parent_allocator: *Allocator,
+ writer: Writer,
+
+ const Self = @This();
+
+ pub fn init(parent_allocator: *Allocator, writer: Writer) Self {
+ return Self{
+ .allocator = Allocator{
+ .allocFn = alloc,
+ .resizeFn = resize,
+ },
+ .parent_allocator = parent_allocator,
+ .writer = writer,
+ };
+ }
+
+ fn alloc(
+ allocator: *Allocator,
+ len: usize,
+ ptr_align: u29,
+ len_align: u29,
+ ra: usize,
+ ) error{OutOfMemory}![]u8 {
+ const self = @fieldParentPtr(Self, "allocator", allocator);
+ self.writer.print("alloc : {}", .{len}) catch {};
+ const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra);
+ if (result) |buff| {
+ self.writer.print(" success!\n", .{}) catch {};
+ } else |err| {
+ self.writer.print(" failure!\n", .{}) catch {};
+ }
+ return result;
+ }
+
+ fn resize(
+ allocator: *Allocator,
+ buf: []u8,
+ buf_align: u29,
+ new_len: usize,
+ len_align: u29,
+ ra: usize,
+ ) error{OutOfMemory}!usize {
+ const self = @fieldParentPtr(Self, "allocator", allocator);
+ if (new_len == 0) {
+ self.writer.print("free : {}\n", .{buf.len}) catch {};
+ } else if (new_len <= buf.len) {
+ self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {};
+ } else {
+ self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {};
+ }
+ if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| {
+ if (new_len > buf.len) {
+ self.writer.print(" success!\n", .{}) catch {};
+ }
+ return resized_len;
+ } else |e| {
+ std.debug.assert(new_len > buf.len);
+ self.writer.print(" failure!\n", .{}) catch {};
+ return e;
+ }
+ }
+ };
+}
+
+/// This allocator is used in front of another allocator and logs to the provided writer
+/// on every call to the allocator. Writer errors are ignored.
+pub fn logToWriterAllocator(
+ parent_allocator: *Allocator,
+ writer: anytype,
+) LogToWriterAllocator(@TypeOf(writer)) {
+ return LogToWriterAllocator(@TypeOf(writer)).init(parent_allocator, writer);
+}
+
+test "LogToWriterAllocator" {
+ var log_buf: [255]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&log_buf);
+
+ var allocator_buf: [10]u8 = undefined;
+ var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
+ const allocator = &logToWriterAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator;
+
+ var a = try allocator.alloc(u8, 10);
+ a = allocator.shrink(a, 5);
+ try std.testing.expect(a.len == 5);
+ try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
+ allocator.free(a);
+
+ try std.testing.expectEqualSlices(u8,
+ \\alloc : 10 success!
+ \\shrink: 10 to 5
+ \\expand: 5 to 20 failure!
+ \\free : 5
+ \\
+ , fbs.getWritten());
+}
lib/std/heap/logging_allocator.zig
@@ -6,28 +6,56 @@
const std = @import("../std.zig");
const Allocator = std.mem.Allocator;
-/// This allocator is used in front of another allocator and logs to the provided stream
-/// on every call to the allocator. Stream errors are ignored.
-/// If https://github.com/ziglang/zig/issues/2586 is implemented, this API can be improved.
-pub fn LoggingAllocator(comptime Writer: type) type {
+/// This allocator is used in front of another allocator and logs to `std.log`
+/// on every call to the allocator.
+/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
+pub fn LoggingAllocator(
+ comptime success_log_level: std.log.Level,
+ comptime failure_log_level: std.log.Level,
+) type {
+ return ScopedLoggingAllocator(.default, success_log_level, failure_log_level);
+}
+
+/// This allocator is used in front of another allocator and logs to `std.log`
+/// with the given scope on every call to the allocator.
+/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
+pub fn ScopedLoggingAllocator(
+ comptime scope: @Type(.EnumLiteral),
+ comptime success_log_level: std.log.Level,
+ comptime failure_log_level: std.log.Level,
+) type {
+ const log = std.log.scoped(scope);
+
return struct {
allocator: Allocator,
parent_allocator: *Allocator,
- writer: Writer,
const Self = @This();
- pub fn init(parent_allocator: *Allocator, writer: Writer) Self {
- return Self{
+ pub fn init(parent_allocator: *Allocator) Self {
+ return .{
.allocator = Allocator{
.allocFn = alloc,
.resizeFn = resize,
},
.parent_allocator = parent_allocator,
- .writer = writer,
};
}
+ // This function is required as the `std.log.log` function is not public
+ fn logHelper(comptime log_level: std.log.Level, comptime format: []const u8, args: anytype) callconv(.Inline) void {
+ switch (log_level) {
+ .emerg => log.emerg(format, args),
+ .alert => log.alert(format, args),
+ .crit => log.crit(format, args),
+ .err => log.err(format, args),
+ .warn => log.warn(format, args),
+ .notice => log.notice(format, args),
+ .info => log.info(format, args),
+ .debug => log.debug(format, args),
+ }
+ }
+
fn alloc(
allocator: *Allocator,
len: usize,
@@ -36,12 +64,19 @@ pub fn LoggingAllocator(comptime Writer: type) type {
ra: usize,
) error{OutOfMemory}![]u8 {
const self = @fieldParentPtr(Self, "allocator", allocator);
- self.writer.print("alloc : {}", .{len}) catch {};
const result = self.parent_allocator.allocFn(self.parent_allocator, len, ptr_align, len_align, ra);
if (result) |buff| {
- self.writer.print(" success!\n", .{}) catch {};
+ logHelper(
+ success_log_level,
+ "alloc - success - len: {}, ptr_align: {}, len_align: {}",
+ .{ len, ptr_align, len_align },
+ );
} else |err| {
- self.writer.print(" failure!\n", .{}) catch {};
+ logHelper(
+ failure_log_level,
+ "alloc - failure: {s} - len: {}, ptr_align: {}, len_align: {}",
+ .{ @errorName(err), len, ptr_align, len_align },
+ );
}
return result;
}
@@ -55,53 +90,41 @@ pub fn LoggingAllocator(comptime Writer: type) type {
ra: usize,
) error{OutOfMemory}!usize {
const self = @fieldParentPtr(Self, "allocator", allocator);
- if (new_len == 0) {
- self.writer.print("free : {}\n", .{buf.len}) catch {};
- } else if (new_len <= buf.len) {
- self.writer.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {};
- } else {
- self.writer.print("expand: {} to {}", .{ buf.len, new_len }) catch {};
- }
+
if (self.parent_allocator.resizeFn(self.parent_allocator, buf, buf_align, new_len, len_align, ra)) |resized_len| {
- if (new_len > buf.len) {
- self.writer.print(" success!\n", .{}) catch {};
+ if (new_len == 0) {
+ logHelper(success_log_level, "free - success - len: {}", .{buf.len});
+ } else if (new_len <= buf.len) {
+ logHelper(
+ success_log_level,
+ "shrink - success - {} to {}, len_align: {}, buf_align: {}",
+ .{ buf.len, new_len, len_align, buf_align },
+ );
+ } else {
+ logHelper(
+ success_log_level,
+ "expand - success - {} to {}, len_align: {}, buf_align: {}",
+ .{ buf.len, new_len, len_align, buf_align },
+ );
}
+
return resized_len;
- } else |e| {
+ } else |err| {
std.debug.assert(new_len > buf.len);
- self.writer.print(" failure!\n", .{}) catch {};
- return e;
+ logHelper(
+ failure_log_level,
+ "expand - failure: {s} - {} to {}, len_align: {}, buf_align: {}",
+ .{ @errorName(err), buf.len, new_len, len_align, buf_align },
+ );
+ return err;
}
}
};
}
-pub fn loggingAllocator(
- parent_allocator: *Allocator,
- writer: anytype,
-) LoggingAllocator(@TypeOf(writer)) {
- return LoggingAllocator(@TypeOf(writer)).init(parent_allocator, writer);
-}
-
-test "LoggingAllocator" {
- var log_buf: [255]u8 = undefined;
- var fbs = std.io.fixedBufferStream(&log_buf);
-
- var allocator_buf: [10]u8 = undefined;
- var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
- const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator;
-
- var a = try allocator.alloc(u8, 10);
- a = allocator.shrink(a, 5);
- try std.testing.expect(a.len == 5);
- try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
- allocator.free(a);
-
- try std.testing.expectEqualSlices(u8,
- \\alloc : 10 success!
- \\shrink: 10 to 5
- \\expand: 5 to 20 failure!
- \\free : 5
- \\
- , fbs.getWritten());
+/// This allocator is used in front of another allocator and logs to `std.log`
+/// on every call to the allocator.
+/// For logging to a `std.io.Writer` see `std.heap.LogToWriterAllocator`
+pub fn loggingAllocator(parent_allocator: *Allocator) LoggingAllocator(.debug, .crit) {
+ return LoggingAllocator(.debug, .crit).init(parent_allocator);
}
lib/std/heap.zig
@@ -16,6 +16,9 @@ const maxInt = std.math.maxInt;
pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator;
pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator;
+pub const ScopedLoggingAllocator = @import("heap/logging_allocator.zig").ScopedLoggingAllocator;
+pub const LogToWriterAllocator = @import("heap/log_to_writer_allocator.zig").LogToWriterAllocator;
+pub const logToWriterAllocator = @import("heap/log_to_writer_allocator.zig").logToWriterAllocator;
pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;
pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator;
@@ -1162,4 +1165,5 @@ pub fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) !void {
test "heap" {
_ = @import("heap/logging_allocator.zig");
+ _ = @import("heap/log_to_writer_allocator.zig");
}
test/compare_output.zig
@@ -599,4 +599,50 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\emergency(c):
\\
);
+
+ // It is required to override the log function in order to print to stdout instead of stderr
+ cases.add("std.heap.LoggingAllocator logs to std.log",
+ \\const std = @import("std");
+ \\
+ \\pub const log_level: std.log.Level = .debug;
+ \\
+ \\pub fn main() !void {
+ \\ var allocator_buf: [10]u8 = undefined;
+ \\ var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf));
+ \\ const allocator = &std.heap.loggingAllocator(&fixedBufferAllocator.allocator).allocator;
+ \\
+ \\ var a = try allocator.alloc(u8, 10);
+ \\ a = allocator.shrink(a, 5);
+ \\ try std.testing.expect(a.len == 5);
+ \\ try std.testing.expectError(error.OutOfMemory, allocator.resize(a, 20));
+ \\ allocator.free(a);
+ \\}
+ \\
+ \\pub fn log(
+ \\ comptime level: std.log.Level,
+ \\ comptime scope: @TypeOf(.EnumLiteral),
+ \\ comptime format: []const u8,
+ \\ args: anytype,
+ \\) void {
+ \\ const level_txt = switch (level) {
+ \\ .emerg => "emergency",
+ \\ .alert => "alert",
+ \\ .crit => "critical",
+ \\ .err => "error",
+ \\ .warn => "warning",
+ \\ .notice => "notice",
+ \\ .info => "info",
+ \\ .debug => "debug",
+ \\ };
+ \\ const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
+ \\ const stdout = std.io.getStdOut().writer();
+ \\ nosuspend stdout.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
+ \\}
+ ,
+ \\debug: alloc - success - len: 10, ptr_align: 1, len_align: 0
+ \\debug: shrink - success - 10 to 5, len_align: 0, buf_align: 1
+ \\critical: expand - failure: OutOfMemory - 5 to 20, len_align: 0, buf_align: 1
+ \\debug: free - success - len: 5
+ \\
+ );
}