Instead, let the encoders (and if necessary the adapters) add newlines
if necessary.
This makes it more straight-forward to filter messages and eventually
allows the encoders to format messages with fewer checks. Finally, we
might avoid the creation a lot of intermediary strings.
A absolute Windows path such as `C:/path.to/file.log` is a valid URL
with schema `C`. If a user specifies such a path, they likely want to
create a File adapter in this case. As such, we fallback from
`Adapter.adapter_by_uri_scheme` and create File adapter instance via
`Adapter.adapter_by_type`.
This reduces the change of a Hash argument being interpreted as keyword
argument inadvertently and clarifies the interface. If a resolver scope
is required, you can also call
delayed_tag = ->{ |request| request.host }
logger.tags.merge!(delayed_tag, scope: request)
Since these platforms don't support the File::SHARE_DELETE flag on
files, Windows rejects any attempts to delete or rename a file opened
by us. There's nothing we can do here unfortunately...
This is required on Windows (where we enable this feature by default)
since they don't support concurrent atomic writes above the drive's
sector size (usually between 512 Bytes and 4 KiBytes). Without an
explicit lock, concurrent writes can be interleaved or be partially
lost.
Since fork is not available on some platforms (aka. Windows), we
avoid it and use normal Threads instead. We have fail saves in place to
ensure that tests are still valid even on MRI with its GIL.
Each flow now has an associated executor which performs all actions
(writing events, closing, reopening) asynchronously by default using a
Concurrent::SingleThreadExecutor.
This improves the responsiveness of the application by performing the
(usually) IO-bound task of writing the logs to a background thread.
By creating a flow with `synchronous: true`, all actions are run in the
calling thread as before, making the flow blocking.
With this, we also drop the ability to define conditions in the filter
itself. When adding a filter, users can still setup a condition using
the common functionality of all filters.
We now support two different modes of file rotation at the same time:
* auto_reopen can be used to automatically reopen a logfile at the
original location if the file was moved or deleted from the filesystem
* rotate can be used to write to a rotate file which can be reopened /
created based on Date pattern.
The user can now decide whether they want to use an external logrotate
command or use internal rotation with Rackstash instead.
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.