Veritable Lasagna
An Allocator & Data Structure Library for C.
Loading...
Searching...
No Matches
vl_log.h File Reference

Thread-safe, sink-based logging for Veritable Lasagna. More...

#include "vl_memory.h"
#include "vl_numtypes.h"
#include "vl_stream.h"
#include "vl_ansi_term.h"
+ Include dependency graph for vl_log.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  vl_log_sink_vtbl
 Virtual function table for a log sink. More...
 
struct  vl_log_sink
 Public descriptor for attaching an output sink to a logger. More...
 
struct  vl_log_config
 Configuration used when creating a logger. More...
 

Macros

#define VL_ENABLE_DEBUG_LOG   1
 
#define VL_LOGD_MSG0(msg)    vlLogMessageF(VL_ANSI_FG_MAGENTA "[DBG | %s @ line %d] " VL_ANSI_RESET "%s", __FILE__, __LINE__, msg)
 Debug-only message log routed through the global logger.
 
#define VL_LOGD_MSGF(fmt, ...)    vlLogMessageF(VL_ANSI_FG_MAGENTA "[DBG | %s @ line %d] " VL_ANSI_RESET fmt, __FILE__, __LINE__, __VA_ARGS__)
 Debug-only formatted log routed through the global logger.
 
#define VL_LOGD_ERR0(msg)    vlLogErrorF(VL_ANSI_FG_YELLOW "[DBG | %s @ line %d] " VL_ANSI_RESET "%s", __FILE__, __LINE__, msg)
 Debug-only error log routed through the global logger.
 
#define VL_LOGD_ERRF(fmt, ...)    vlLogErrorF(VL_ANSI_FG_YELLOW "[DBG | %s @ line %d] " VL_ANSI_RESET fmt, __FILE__, __LINE__, __VA_ARGS__)
 Debug-only formatted error log routed through the global logger.
 
#define VL_LOG_MSG0(msg)   vlLogMessage(msg)
 Always-enabled message log routed through the global logger.
 
#define VL_LOG_MSGF(fmt, ...)   vlLogMessageF((fmt), __VA_ARGS__)
 Always-enabled formatted message log routed through the global logger.
 
#define VL_LOG_ERR0(msg)   vlLogError(msg)
 Always-enabled error log routed through the global logger.
 
#define VL_LOG_ERRF(fmt, ...)   vlLogErrorF((fmt), __VA_ARGS__)
 Always-enabled formatted error log routed through the global logger.
 

Functions

VL_API vl_logger * vlLoggerNew (const vl_log_config *config)
 Create a new logger instance.
 
VL_API void vlLoggerDelete (vl_logger *logger)
 Destroy a logger instance and release all associated resources.
 
VL_API vl_bool_t vlLoggerAddSink (vl_logger *logger, vl_log_sink sink)
 Attach a sink to a logger instance.
 
VL_API void vlLoggerMessage (vl_logger *logger, const char *msg)
 Write a preformatted message through a specific logger.
 
VL_API void vlLoggerMessageF (vl_logger *logger, const char *msgFormat,...)
 Write a formatted message through a specific logger.
 
VL_API void vlLoggerFlush (vl_logger *logger)
 Flush pending messages for a specific logger.
 
VL_API vl_log_sink vlLogSinkStdout (void)
 Create a sink that writes to standard output.
 
VL_API vl_log_sink vlLogSinkStream (vl_stream *stream)
 Create a sink that writes to an existing vl_stream.
 
VL_API void vlLogInit (const vl_log_config *config)
 Initialize the global logger.
 
VL_API void vlLogFlush (void)
 Flush the global logger.
 
VL_API void vlLogShutdown (void)
 Shut down and destroy the global logger.
 
VL_API void vlLogMessage (const char *msg)
 Write a preformatted message through the global logger.
 
VL_API void vlLogMessageF (const char *msgFormat,...)
 Write a formatted message through the global logger.
 
VL_API void vlLogError (const char *msg)
 Write an error message through the global logger.
 
VL_API void vlLogErrorF (const char *msgFormat,...)
 Write a formatted error message through the global logger.
 
VL_API vl_bool_t vlLogAddSink (vl_log_sink sink)
 Attach a sink to the global logger.
 
VL_API vl_bool_t vlLogAddStdoutSink (void)
 Convenience helper that attaches a stdout sink to the global logger.
 
VL_API vl_bool_t vlLogAddStreamSink (vl_stream *stream)
 Convenience helper that attaches a vl_stream sink to the global logger.
 

Detailed Description

Thread-safe, sink-based logging for Veritable Lasagna.

██ ██ ██ █████ ███████ █████ ██████ ███ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██ ███ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ███████ ██ ██ ███████ ██ ██ ██████ ██ ████ ██ ██ ====—: A Data Structure and Algorithms library for C11. :—====

Copyright 2026 Jesse Walker, released under the MIT license. Git Repository: https://github.com/walkerje/veritable_lasagna

This module provides a small logging system built around two ideas:

  • logger instances (vl_logger) which own buffering, synchronization, and dispatch behavior
  • log sinks (vl_log_sink) which define where bytes are written

The API supports both:

  • independent logger instances, created with vlLoggerNew()
  • a global convenience logger, accessed through the vlLog*() family

Design overview

A logger accepts preformatted messages and forwards them to zero or more attached sinks. Sinks are modular output targets such as:

  • standard output
  • a generic vl_stream
  • future custom backends implemented by the caller

Each logger may operate either:

  • synchronously, where the calling thread performs the sink writes
  • asynchronously, where callers enqueue messages and a worker thread performs the actual output

Thread safety

All logger operations are intended to be safe for concurrent use from multiple threads.

In async mode, producer threads only enqueue work and signal the worker, which helps reduce I/O contention. In sync mode, writes occur before the logging call returns.

Message boundaries

Messages are written exactly as provided. This API does not append a newline automatically, so callers should include \n when line-oriented output is desired.

Ownership model

Logger instances own their attached sinks. When a sink is added to a logger, the logger stores a copy of the sink descriptor and assumes ownership of the sink's sink_data. Sink cleanup is performed automatically when the logger is destroyed.


Data Structure Documentation

◆ vl_log_sink

struct vl_log_sink

Public descriptor for attaching an output sink to a logger.

A sink is defined by:

  • a callback table (vtbl)
  • an implementation-defined state pointer (sink_data)

The logger stores a copy of this struct when vlLoggerAddSink() succeeds.

Ownership

After a successful vlLoggerAddSink(), the logger owns sink_data and will eventually pass it to the sink's destroy callback.

Therefore, callers should treat the sink as "moved" into the logger after successful attachment.

+ Collaboration diagram for vl_log_sink:
Data Fields
void * sink_data Sink implementation state.

Ownership transfers to the logger after successful attachment.

const vl_log_sink_vtbl * vtbl Sink callback table.

Must not be NULL for a valid sink.

◆ vl_log_config

struct vl_log_config

Configuration used when creating a logger.

This structure controls per-logger behavior. The configuration is consumed at logger creation time by vlLoggerNew() or by vlLogInit() for the global logger.

+ Collaboration diagram for vl_log_config:
Data Fields
vl_bool_t async Enable asynchronous logging.

If VL_TRUE, the logger creates a background worker thread and logging calls enqueue records for later output.

If VL_FALSE, logging calls write directly to attached sinks before returning.

Async mode is typically preferred for reducing contention in code paths that log frequently.

Macro Definition Documentation

◆ VL_ENABLE_DEBUG_LOG

#define VL_ENABLE_DEBUG_LOG   1

◆ VL_LOG_ERR0

#define VL_LOG_ERR0 (   msg)    vlLogError(msg)

Always-enabled error log routed through the global logger.

◆ VL_LOG_ERRF

#define VL_LOG_ERRF (   fmt,
  ... 
)    vlLogErrorF((fmt), __VA_ARGS__)

Always-enabled formatted error log routed through the global logger.

◆ VL_LOG_MSG0

#define VL_LOG_MSG0 (   msg)    vlLogMessage(msg)

Always-enabled message log routed through the global logger.

◆ VL_LOG_MSGF

#define VL_LOG_MSGF (   fmt,
  ... 
)    vlLogMessageF((fmt), __VA_ARGS__)

Always-enabled formatted message log routed through the global logger.

◆ VL_LOGD_ERR0

#define VL_LOGD_ERR0 (   msg)     vlLogErrorF(VL_ANSI_FG_YELLOW "[DBG | %s @ line %d] " VL_ANSI_RESET "%s", __FILE__, __LINE__, msg)

Debug-only error log routed through the global logger.

◆ VL_LOGD_ERRF

#define VL_LOGD_ERRF (   fmt,
  ... 
)     vlLogErrorF(VL_ANSI_FG_YELLOW "[DBG | %s @ line %d] " VL_ANSI_RESET fmt, __FILE__, __LINE__, __VA_ARGS__)

Debug-only formatted error log routed through the global logger.

◆ VL_LOGD_MSG0

#define VL_LOGD_MSG0 (   msg)     vlLogMessageF(VL_ANSI_FG_MAGENTA "[DBG | %s @ line %d] " VL_ANSI_RESET "%s", __FILE__, __LINE__, msg)

Debug-only message log routed through the global logger.

◆ VL_LOGD_MSGF

#define VL_LOGD_MSGF (   fmt,
  ... 
)     vlLogMessageF(VL_ANSI_FG_MAGENTA "[DBG | %s @ line %d] " VL_ANSI_RESET fmt, __FILE__, __LINE__, __VA_ARGS__)

Debug-only formatted log routed through the global logger.

Function Documentation

◆ vlLogAddSink()

VL_API vl_bool_t vlLogAddSink ( vl_log_sink  sink)

Attach a sink to the global logger.

Parameters
sinkSink descriptor to attach.
Returns
VL_TRUE on success, VL_FALSE on failure.

If the global logger is not initialized yet, it may be initialized lazily before the sink is attached.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLogAddStdoutSink()

VL_API vl_bool_t vlLogAddStdoutSink ( void  )

Convenience helper that attaches a stdout sink to the global logger.

Returns
VL_TRUE on success, VL_FALSE on failure.
+ Here is the call graph for this function:

◆ vlLogAddStreamSink()

VL_API vl_bool_t vlLogAddStreamSink ( vl_stream stream)

Convenience helper that attaches a vl_stream sink to the global logger.

Parameters
streamStream to attach as a sink target.
Returns
VL_TRUE on success, VL_FALSE on failure.
+ Here is the call graph for this function:

◆ vlLogError()

VL_API void vlLogError ( const char *  msg)

Write an error message through the global logger.

Parameters
msgNUL-terminated message string.

This currently behaves like a semantic alias of vlLogMessage(). It exists to make call sites more expressive and to leave room for future severity-aware routing or formatting.

+ Here is the call graph for this function:

◆ vlLogErrorF()

VL_API void vlLogErrorF ( const char *  msgFormat,
  ... 
)

Write a formatted error message through the global logger.

Parameters
msgFormatprintf-style format string.
...Format arguments.

This currently behaves like a semantic alias of vlLogMessageF().

+ Here is the call graph for this function:

◆ vlLogFlush()

VL_API void vlLogFlush ( void  )

Flush the global logger.

If the global logger has not been initialized, this function does nothing.

See also
vlLoggerFlush
+ Here is the call graph for this function:

◆ vlLoggerAddSink()

VL_API vl_bool_t vlLoggerAddSink ( vl_logger *  logger,
vl_log_sink  sink 
)

Attach a sink to a logger instance.

Parameters
loggerLogger receiving the sink.
sinkSink descriptor to attach.
Returns
VL_TRUE on success, VL_FALSE on failure.

On success, the logger stores a copy of sink and assumes ownership of sink.sink_data.

On failure, ownership remains with the caller unless the implementation documents otherwise.

Note
Attaching the same underlying sink state to multiple loggers is unsafe unless that sink implementation explicitly supports shared ownership.
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLoggerDelete()

VL_API void vlLoggerDelete ( vl_logger *  logger)

Destroy a logger instance and release all associated resources.

Contract

  • Ownership: Releases ownership of the logger and all its associated sinks and internal resources.
  • Lifetime: The logger handle becomes invalid immediately after this call.
  • Thread Safety: Safe to call from any thread, provided no other thread is currently using the logger.
  • Nullability: Safe to call with NULL (no-op).
  • Error Conditions: None.
  • Undefined Behavior: Double deletion. Deleting a logger that is being used by another thread.
  • Memory Allocation Expectations: Deallocates all heap-allocated resources associated with the logger.
  • Return-value Semantics: None (void).
Parameters
loggerLogger to destroy. NULL is ignored.

Destruction performs an implicit flush of pending messages before tearing down worker state and attached sinks.

After this call returns, logger must not be used again.

See also
vlLoggerFlush
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLoggerFlush()

VL_API void vlLoggerFlush ( vl_logger *  logger)

Flush pending messages for a specific logger.

Parameters
loggerLogger instance to flush. NULL is ignored.

Behavior depends on the logger mode:

  • async mode: blocks until all messages enqueued before the call have been processed, then flushes sinks
  • sync mode: messages are already written by the time logging calls return, so this primarily flushes sink buffers

This function is useful as a synchronization barrier before shutdown, abnormal termination, or assertions in tests.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLoggerMessage()

VL_API void vlLoggerMessage ( vl_logger *  logger,
const char *  msg 
)

Write a preformatted message through a specific logger.

Contract

  • Ownership: The logger copies the msg internally. The caller retains ownership of the msg pointer.
  • Lifetime: Unchanged.
  • Thread Safety: Thread-safe (atomic enqueue).
  • Nullability: Safe to call with NULL for msg (treated as empty). logger should not be NULL.
  • Error Conditions: None.
  • Undefined Behavior: Passing NULL for logger.
  • Memory Allocation Expectations: Allocates memory on the heap to store the message record.
  • Return-value Semantics: None (void).
Parameters
loggerLogger instance to receive the message.
msgNUL-terminated UTF-8 message string. If NULL, it is treated as an empty string.

The message is copied internally so the caller retains ownership of msg. No newline is appended automatically.

In async mode, this call typically enqueues the message and returns before sink I/O completes.

See also
vlLoggerFlush
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLoggerMessageF()

VL_API void vlLoggerMessageF ( vl_logger *  logger,
const char *  msgFormat,
  ... 
)

Write a formatted message through a specific logger.

Parameters
loggerLogger instance to receive the message.
msgFormatprintf-style format string.
...Format arguments.

The formatted message is materialized into logger-owned memory before being enqueued or written.

No newline is appended automatically.

◆ vlLoggerNew()

VL_API vl_logger * vlLoggerNew ( const vl_log_config config)

Create a new logger instance.

Contract

  • Ownership: The caller owns the returned vl_logger handle and is responsible for calling vlLoggerDelete.
  • Lifetime: The logger remains valid until vlLoggerDelete.
  • Thread Safety: This function is thread-safe.
  • Nullability: Returns NULL if the logger could not be created.
  • Error Conditions: Returns NULL if heap allocation fails or if internal synchronization primitives/worker threads cannot be initialized.
  • Undefined Behavior: None.
  • Memory Allocation Expectations: Allocates the logger structure, internal message queue, mutexes, and semaphores on the heap.
  • Return-value Semantics: Returns an opaque handle to the new logger, or NULL on failure.
Parameters
configOptional logger configuration. If NULL, implementation-defined defaults are used.
Returns
A newly allocated logger instance, or NULL on allocation or initialization failure.

The returned logger initially has no sinks attached. Messages sent to such a logger are accepted but may produce no externally visible output until one or more sinks are added.

See also
vlLoggerAddSink
vlLoggerDelete
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLogInit()

VL_API void vlLogInit ( const vl_log_config config)

Initialize the global logger.

Parameters
configOptional configuration for the global logger. If NULL, implementation defaults are used.

This function initializes the process-wide convenience logger used by the vlLog*() functions.

The global logger is intended for applications that want a simple shared logging facility without manually managing a vl_logger*.

Typical usage:

  • call vlLogInit() once during startup
  • attach one or more sinks
  • use vlLogMessage*() during runtime
  • call vlLogShutdown() during shutdown
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLogMessage()

VL_API void vlLogMessage ( const char *  msg)

Write a preformatted message through the global logger.

Parameters
msgNUL-terminated message string. If NULL, treated as an empty string.

If the global logger has not yet been initialized, it may be initialized lazily using default behavior.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLogMessageF()

VL_API void vlLogMessageF ( const char *  msgFormat,
  ... 
)

Write a formatted message through the global logger.

Parameters
msgFormatprintf-style format string.
...Format arguments.

If the global logger has not yet been initialized, it may be initialized lazily using default behavior.

+ Here is the call graph for this function:

◆ vlLogShutdown()

VL_API void vlLogShutdown ( void  )

Shut down and destroy the global logger.

If the global logger is active, this performs an implicit flush, destroys all attached sinks, stops background worker state if present, and resets the global logger to an uninitialized state.

Reinitialization after shutdown is permitted by calling vlLogInit() again.

+ Here is the call graph for this function:

◆ vlLogSinkStdout()

VL_API vl_log_sink vlLogSinkStdout ( void  )

Create a sink that writes to standard output.

Returns
A sink descriptor suitable for vlLoggerAddSink().

This sink writes to stdout and flushes it when requested by the logger.

The returned sink is intended to be attached to exactly one logger.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ vlLogSinkStream()

VL_API vl_log_sink vlLogSinkStream ( vl_stream stream)

Create a sink that writes to an existing vl_stream.

Parameters
streamStream to write to.
Returns
A sink descriptor suitable for vlLoggerAddSink().

The sink retains the stream when created and releases it when the sink is destroyed. This allows the stream to remain valid for as long as the logger needs it.

Note
The stream pointer should be non-NULL.
Whether stream writes are buffered depends on the underlying stream implementation.
+ Here is the call graph for this function:
+ Here is the caller graph for this function: