Commit 629e2e7844

Lee Cannon <leecannon@leecannon.xyz>
2021-06-09 20:42:07
Add a logging allocator that uses std.log (#8511)
1 parent 96c60bc
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
+        \\
+    );
 }