.. title:: Sinks

Sinks
=====

Use this page to understand how sinks are created, shared between loggers, and extended with custom implementations.

Sinks are objects responsible for writing logs to their respective targets.

A :cpp:class:`Sink` object serves as the base class for various sink-derived classes.

Each sink handles outputting logs, metrics, or both to a single target, such as a file, console, database, or metrics backend.

Upon creation, a sink object is registered and owned by a central manager object, the `SinkManager`.

For files, one sink is created per normalized file path, and the file is opened once. If a
compatible sink type is requested for an already opened file, the existing Sink object is returned
and any additional constructor arguments are ignored. In RTTI-enabled builds, requesting the same
name with an incompatible type throws ``QuillError``.

When creating a logger, one or more sinks for that logger can be specified. Sinks can only be registered during the logger creation.

Sinks can be obtained using :cpp:func:`FrontendImpl::get_sink`, :cpp:func:`FrontendImpl::create_or_get_sink`, or :cpp:func:`FrontendImpl::create_sink`.

- ``create_sink`` creates a new sink and throws ``QuillError`` if a sink with the same name already exists.
- ``create_or_get_sink`` creates a new sink if one does not already exist; otherwise, it returns a
  compatible existing sink and ignores the provided constructor arguments. In RTTI-enabled builds,
  an incompatible existing type throws ``QuillError``.
- ``get_sink`` retrieves an existing sink by name and throws ``QuillError`` if it does not exist.

In builds configured with ``QUILL_NO_EXCEPTIONS``, conditions documented as throwing ``QuillError``
invoke Quill's fatal error path instead.

.. warning::

   Builds without RTTI cannot diagnose an incompatible type passed to ``create_or_get_sink``.
   In those builds, request a given sink name only with the same sink type, or with a compatible
   base type.

Configuring Sinks
-----------------

Some sinks accept a configuration object (e.g. ``FileSinkConfig``, ``SyslogSinkConfig``). These are
passed as additional constructor arguments to the ``create_or_get_sink`` or ``create_sink`` call.
A common pattern is to use a lambda to construct the config inline:

.. code:: cpp

     auto sink = quill::Frontend::create_or_get_sink<quill::SyslogSink>(
       "my_syslog",
       []()
       {
         quill::SyslogSinkConfig cfg;
         cfg.set_identifier("my_app");
         return cfg;
       }());

Sharing Sinks Between Loggers
-----------------------------
It is possible to share the same `Sink` object between multiple `Logger` objects. For example, when all logger objects are writing to the same file. The following code is also thread-safe.

.. code:: cpp

     auto file_sink = Frontend::create_or_get_sink<FileSink>(
       filename,
       []()
       {
         FileSinkConfig cfg;
         cfg.set_open_mode('w');
         return cfg;
       }(),
       FileEventNotifier{});

     quill::Logger* logger_a = Frontend::create_or_get_logger("logger_a", file_sink);
     quill::Logger* logger_b = Frontend::create_or_get_logger("logger_b", file_sink);

Customizing the Library with User-Defined Sinks
-----------------------------------------------
You can extend the library by creating and integrating your own ``Sink`` types. The code within the ``Sink`` class is executed by a single backend worker thread.

This can be useful if you want to direct log output to alternative destinations, such as a database, a network service, or even to write ``Parquet`` files.

A custom sink can override ``write_log()``, ``write_metric()``, or both. The default
``Sink::write_metric()`` implementation is a no-op, so existing log-only sinks do not need to
change. If you want to export metrics, implement ``write_metric()`` and bind a logger to that
sink. See :doc:`Metrics <metrics>` for the metric publishing model and the Prometheus/custom-sink
examples.

.. literalinclude:: ../examples/user_defined_sink.cpp
   :language: cpp
   :linenos:

Routing Log Messages to Multiple Sinks
--------------------------------------

The library provides multiple approaches for routing log messages to different sinks. The following examples demonstrate two common patterns using ``FileSink``, but the same principles apply when using custom ``Sinks`` and ``Filters``.

1. Using multiple ``Logger`` instances, each bound to a specific ``Sink``:

   This approach is straightforward and provides clear separation of log streams.

.. literalinclude:: snippets/quill_docs_example_multiple_sinks.cpp
   :language: cpp
   :linenos:

2. Using a single ``Logger`` instance with multiple ``Sinks`` and tag-based filtering:

   This approach centralizes logging through a single logger while still directing messages to different destinations.

.. literalinclude:: snippets/quill_docs_example_multiple_sinks_tags.cpp
   :language: cpp
   :linenos:
