    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 {
            parent_allocator: Allocator,
            writer: Writer,

            const Self = @This();

            pub fn init(parent_allocator: Allocator, writer: Writer) Self {
                return Self{
                    .parent_allocator = parent_allocator,
                    .writer = writer,
                };
            }

            pub fn allocator(self: *Self) Allocator {
                return .{
                    .ptr = self,
                    .vtable = &.{
                        .alloc = alloc,
                        .resize = resize,
                        .free = free,
                    },
                };
            }

            fn alloc(
                ctx: *anyopaque,
                len: usize,
                log2_ptr_align: u8,
                ra: usize,
            ) ?[*]u8 {
                const self: *Self = @ptrCast(@alignCast(ctx));
                self.writer.print("alloc : {}", .{len}) catch {};
                const result = self.parent_allocator.rawAlloc(len, log2_ptr_align, ra);
                if (result != null) {
                    self.writer.print(" success!\n", .{}) catch {};
                } else {
                    self.writer.print(" failure!\n", .{}) catch {};
                }
                return result;
            }

            fn resize(
                ctx: *anyopaque,
                buf: []u8,
                log2_buf_align: u8,
                new_len: usize,
                ra: usize,
            ) bool {
                const self: *Self = @ptrCast(@alignCast(ctx));
                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.rawResize(buf, log2_buf_align, new_len, ra)) {
                    if (new_len > buf.len) {
                        self.writer.print(" success!\n", .{}) catch {};
                    }
                    return true;
                }

                std.debug.assert(new_len > buf.len);
                self.writer.print(" failure!\n", .{}) catch {};
                return false;
            }

            fn free(
                ctx: *anyopaque,
                buf: []u8,
                log2_buf_align: u8,
                ra: usize,
            ) void {
                const self: *Self = @ptrCast(@alignCast(ctx));
                self.writer.print("free  : {}\n", .{buf.len}) catch {};
                self.parent_allocator.rawFree(buf, log2_buf_align, ra);
            }
        };
    }

    /// 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));
        var allocator_state = logToWriterAllocator(fixedBufferAllocator.allocator(), fbs.writer());
        const allocator = allocator_state.allocator();

        var a = try allocator.alloc(u8, 10);
        try std.testing.expect(allocator.resize(a, 5));
        a = a[0..5];
        try std.testing.expect(!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());
    }