Skip to content

Commit

Permalink
add config to control how pool behaves when empty
Browse files Browse the repository at this point in the history
  • Loading branch information
karlseguin committed Jan 12, 2024
1 parent 525e80a commit b089e21
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 9 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
iMozilla Public License Version 2.0
Mozilla Public License Version 2.0
==================================

1. Definitions
Expand Down
8 changes: 6 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Structured Logging for Zig

logz is an opinionated structured logger that outputs to stdout using logfmt or JSON. It aims to minimize runtime memory allocation by using a pool of pre-allocated loggers.
logz is an opinionated structured logger that outputs to stdout or stderr using logfmt or JSON. It aims to minimize runtime memory allocation by using a pool of pre-allocated loggers.

# Installation
This library supports native Zig module (introduced in 0.11). Add a "logz" dependency to your `build.zig.zon`.
Expand Down Expand Up @@ -41,7 +41,7 @@ requestLogs.err().
# Important Notes
1. Attribute keys are never escaped. logz assumes that attribute keys can be written as is.
2. Logz will silently truncate attributes if the log entry exceeds the configured `max_size`. This truncation only happens at the attribute level (not in the middle of a key or value), thus either the whole key=value is written or none of it is.
3. If the pool is empty, logz will attempt to dynamically allocate a new logger. If this fails, a noop logger will be return. The log entry is silently dropped. An error message **is** written using `std.log.err`.
3. Depending on the `pool_empty` configuration, when empty the pool will either dynamically create a logger (`.pool_empty = .create`) or return a noop logger (`.pool_empty = .noop)`. If creation fails, a noop logger will be return and an error is written using `std.log.err`. When `.pool_empty = .noop`, then there will be no memory allocations made after `Pool.init` returns.

## Pools and Loggers
Pools are thread-safe.
Expand Down Expand Up @@ -120,6 +120,10 @@ pub const Config = struct {
// The number of loggers to pre-allocate.
pool_size: usize = 32,
// Controls what the pool does when empty. It can either dynamically create
// a new Logger, or return the Noop logger.
pool_empty: PoolEmpty = .create,
// The maximum size, in bytes, that each log entry can be.
// Writing more data than max_size will truncate the log at a key=value
// boundary (in other words, you won't get a key or value randomly
Expand Down
10 changes: 8 additions & 2 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ pub const Config = struct {
prefix: ?[]const u8 = null,
output: Output = .stdout,
encoding: Encoding = .logfmt,
pool_empty: PoolEmpty = .create,

const Output = enum {
pub const Output = enum {
stdout,
stderr,
};

const Encoding = enum {
pub const Encoding = enum {
json,
logfmt,
};

pub const PoolEmpty = enum {
create,
noop,
};
};
37 changes: 33 additions & 4 deletions src/pool.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub const Pool = struct {
available: usize,
loggers: []Logger,
allocator: Allocator,
empty: Config.PoolEmpty,

pub fn init(allocator: Allocator, config: Config) !*Pool {
const size = config.pool_size;
Expand All @@ -32,6 +33,7 @@ pub const Pool = struct {
.loggers = loggers,
.available = size,
.allocator = allocator,
.empty = config.pool_empty,
.level = @intFromEnum(config.level),
};

Expand Down Expand Up @@ -68,6 +70,10 @@ pub const Pool = struct {
// dont hold the lock over factory
self.mutex.unlock();

if (self.empty == .noop) {
return logz.noop;
}

const l = self.createLogger() catch |e| {
logDynamicAllocationFailure(e);
return logz.noop;
Expand Down Expand Up @@ -268,14 +274,37 @@ test "pool: acquire and release" {
try t.expectEqual(false, l1a.inner.logfmt == l2a.inner.logfmt);
try t.expectEqual(false, l2a.inner.logfmt == l3a.inner.logfmt);

p.release(l1a);
l1a.release();

const l1b = p.acquire();
try t.expectEqual(true, l1a.inner.logfmt == l1b.inner.logfmt);

l3a.release();
l2a.release();
l1b.release();
}

test "pool: empty noop" {
// not 100% sure this is testing exactly what I want, but it's ....something ?
const min_config = Config{.pool_size = 2, .max_size = 1, .pool_empty = .noop};
var p = try Pool.init(t.allocator, min_config);
defer p.deinit();

const l1a = p.acquire();
const l2a = p.acquire();
const l3a = p.acquire(); // this should be noop

try t.expectEqual(false, l1a.inner.logfmt == l2a.inner.logfmt);
try t.expectEqual(.noop, std.meta.activeTag(l3a.inner));

l1a.release();

const l1b = p.acquire();
try t.expectEqual(true, l1a.inner.logfmt == l1b.inner.logfmt);

p.release(l3a);
p.release(l2a);
p.release(l1b);
l3a.release();
l2a.release();
l1b.release();
}

test "pool: log to kv" {
Expand Down

0 comments on commit b089e21

Please sign in to comment.