diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 1cf74435c8..a5150e50fe 100644 --- a/lib/std/heap.zig +++ b/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"); } diff --git a/lib/std/heap/log_to_writer_allocator.zig b/lib/std/heap/log_to_writer_allocator.zig new file mode 100644 index 0000000000..96c6971f38 --- /dev/null +++ b/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()); +} diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index ac301425bb..2c82c3abf4 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/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); } diff --git a/test/compare_output.zig b/test/compare_output.zig index 91ace76b60..b6d333a073 100644 --- a/test/compare_output.zig +++ b/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 + \\ + ); }