Instead of defining the specific buffering behavior on a Buffer, we can
now mark individual flows as auto_flushing or now. An auto_flushing Flow
with a buffering Buffer behaves the same as a Buffer with `buffering:
:data` would before.
This allows us to simplify the buffering logic on the Buffer. Also, we
can now use "normal" flows and auto_flushing flows on the same logger in
parallel. Each of them behaves as expected with the same unchanged logger
code.
It is thus easier to define behavior for a development or production
environment of an app since the necessary changes can all be defined on
the logger itself (through the defined flows) without having to adapt
the code which creates suitable Buffers with the Logger#with_buffer
method in any way.
With that, we can lazy-transform the Buffer to the event hash. B using
the common `.to_h` protocol, we can also support various other objects
here instead of just Buffers (including actual raw Hashes).
Previously, we would only setup a `::Rack::BodyProxy` to eventually log
the request once the response is deivered to the client. We did this
mainly in order to have a more "accurate" duration in the logs, similar
to what the `::Rack::CommonLogger` middleware does.
This has some significant disadvantages though:
* If an exception is raised by an outer middleware after we returned,
the response is never delivered and the log is lost.
* If a timeout occured (and e.g. a Unicorn worker is killed or a
`Rack::Timeout` stroke) before we return the full response, the log is
lost.
* If the client goes away before the request is finished, the app server
might decide no just throw away the response.
Besides addressing these issues, logging directly after the request
leaves our middleware scope makes the whole process much easier to
reason about.
Since
1. we are using binmode and do not expect any conversation of newlines
or encodings to take place, and
2. we are only writing to the open file when we have squired the mutex
of the adapter instance
we can safely use the syswrite method to write logs. In the end, this
uses the same low-level syscall as `IO#write` but with less overhead.
We still want to avoid the deprecated call to `IPAddr#ipv4_compat?`.
However, since we are always masking of at least one bit of any given IP
address anyways, we can ignore the specific tests for '::' and '::1' in
that method. The result of masking off these addresses will always be
'::' regardless of how many bits we are masking off anyways.
Most notably, we want to use the plus character in URI schemes. By only
checking the first character of the scheme registration, we can better
fullfil our contract and better distinguish between schemes and class
names of registered Adapters.
If we were to fall through, undefined schemes would always end up as a
file adapter. This is generally undesired since it hides the fact that
we have not found a suitable adapter. Most of the time, this will be a
configuration error which should be reported early.
If a user still wants to create a file adapter with a filename that
looks like a URI, they can create a Rackstash::Adapter::File object
manually.
Now you can define optional filters which only run if some condition is
true or false. This can be used to e.g. update fields depending on some
tags being present in the event.
This removes the requirement that filters need to be defined in the
Rackstash::Filters namespace in order to be usable with a short name.
Filters can be registered with an arbitrary name.
As before, raw blocks / procs can be defined as ad-hoc filters.
Using the ClassRegistry, we can register short names for filter and
encoder classes so that they can be specified in a shorter and more
readable way during creation of the logger.