logstash-logger 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +5 -3
- data/Appraisals +2 -4
- data/CHANGELOG.md +33 -0
- data/Gemfile +2 -1
- data/README.md +2 -2
- data/docs/buffering.md +16 -0
- data/docs/outputs.md +23 -0
- data/docs/rails.md +7 -2
- data/docs/ssl.md +49 -37
- data/docs/troubleshooting.md +22 -0
- data/gemfiles/rails_8.0.gemfile +3 -1
- data/gemfiles/rails_8.1.gemfile +2 -1
- data/lib/logstash-logger/buffer.rb +15 -4
- data/lib/logstash-logger/device/aws_stream.rb +96 -17
- data/lib/logstash-logger/device/connectable.rb +27 -1
- data/lib/logstash-logger/device/firehose.rb +6 -0
- data/lib/logstash-logger/device/http.rb +75 -4
- data/lib/logstash-logger/device/kafka.rb +27 -0
- data/lib/logstash-logger/device/kinesis.rb +10 -1
- data/lib/logstash-logger/device/tcp.rb +53 -7
- data/lib/logstash-logger/formatter/base.rb +44 -3
- data/lib/logstash-logger/formatter/cee_syslog.rb +8 -11
- data/lib/logstash-logger/version.rb +1 -1
- data/logstash-logger.gemspec +2 -6
- data/spec/device/aws_stream_limits_spec.rb +219 -0
- data/spec/device/connectable_spec.rb +51 -0
- data/spec/device/firehose_spec.rb +4 -1
- data/spec/device/http_spec.rb +166 -1
- data/spec/device/kafka_spec.rb +92 -0
- data/spec/device/kinesis_spec.rb +4 -1
- data/spec/device/tcp_spec.rb +94 -5
- data/spec/device/tcp_tls_integration_spec.rb +91 -0
- data/spec/formatter/base_spec.rb +42 -6
- data/spec/formatter/cee_syslog_spec.rb +71 -19
- data/spec/formatter/framing_spec.rb +58 -0
- data/spec/formatter/json_lines_spec.rb +18 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/tls_helper.rb +30 -0
- data/spec/support/unserializable_value.rb +10 -0
- metadata +19 -7
- data/gemfiles/rails_7.2.gemfile +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f311cb07a6b81dd87a40cd78b4bbfba8742575dcfc888f1cfa85e647b7512c4
|
|
4
|
+
data.tar.gz: ce6a47b4dce5299041b138e97b4923d67a7efaf7816352135a049ab044d90843
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c91bfa3f1b0e569c2744fdce29fbd6097021c0f552e743139aeb98af818ead41eae66bdc2290e353c8a6213b1ec7556f325deaf254f7fe92c3e3589305ee7a6b
|
|
7
|
+
data.tar.gz: db8c4ddee010de0876eb2eb9066d8faf0c2fc8604ae7bc1655526d9c7a86f2bd4fffea02267dfdaa3dd1acbd820479bf50cf3ed9e00b92fcbdb3c724e251881d
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -16,9 +16,10 @@ jobs:
|
|
|
16
16
|
strategy:
|
|
17
17
|
fail-fast: false
|
|
18
18
|
matrix:
|
|
19
|
-
ruby: ['3.
|
|
20
|
-
rails: ['
|
|
19
|
+
ruby: ['3.3', '3.4', '4.0', 'jruby-10']
|
|
20
|
+
rails: ['8.0', '8.1']
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 30
|
|
22
23
|
steps:
|
|
23
24
|
- uses: actions/checkout@v4
|
|
24
25
|
|
|
@@ -44,8 +45,9 @@ jobs:
|
|
|
44
45
|
bundle exec appraisal rails-${{ matrix.rails }} rake default
|
|
45
46
|
|
|
46
47
|
- name: Upload coverage to Codecov
|
|
47
|
-
if: matrix.ruby == '3.
|
|
48
|
+
if: matrix.ruby == '3.3' && matrix.rails == '8.1'
|
|
48
49
|
uses: codecov/codecov-action@v3
|
|
49
50
|
with:
|
|
50
51
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
52
|
+
files: ./coverage/coverage.xml
|
|
51
53
|
fail_ci_if_error: false
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## 1.1.0
|
|
2
|
+
|
|
3
|
+
### Security
|
|
4
|
+
- The TCP output with `ssl_enable: true` and no `ssl_context` accepted any server certificate, so anyone able to intercept the connection could read or alter the log stream. It now verifies the certificate and hostname. [GHSA-p7gr-ppfj-g45v](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-p7gr-ppfj-g45v)
|
|
5
|
+
- Logged content could break record framing: inject a line break into the `cee_syslog` header, or trigger an unframed `Failed to format log event` string that merged with the next record. [GHSA-88vf-w58m-gv7h](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-88vf-w58m-gv7h)
|
|
6
|
+
- One record that Kafka, Kinesis, Firehose or an HTTP collector always rejects could make the output hold back, duplicate or discard the other records buffered with it. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
7
|
+
|
|
8
|
+
### Breaking Changes
|
|
9
|
+
- Drop support for Ruby 3.2 and Rails 7.2, which are end of life. [#185](https://github.com/dwbutler/logstash-logger/pull/185)
|
|
10
|
+
- `ssl_enable: true` without an `ssl_context` now verifies the server certificate against the system trust store. For a self-signed or private-CA server, pass its certificate as `ssl_certificate`. Turning verification off requires an explicit `VERIFY_NONE` context. See [SSL Configuration](docs/ssl.md). [GHSA-p7gr-ppfj-g45v](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-p7gr-ppfj-g45v)
|
|
11
|
+
- `ssl_certificate` is now the certificate trusted for the server, as documented, and is checked when the logger is created. [GHSA-p7gr-ppfj-g45v](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-p7gr-ppfj-g45v)
|
|
12
|
+
- The `cee_syslog` header always uses this machine's hostname. A `host` set in logged content or in `customize_event` appears only in the JSON body. [GHSA-88vf-w58m-gv7h](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-88vf-w58m-gv7h)
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
- `format: :ndjson` option for the HTTP device, to send newline-delimited JSON instead of a JSON array, and a `content_type` option. [#182](https://github.com/dwbutler/logstash-logger/pull/182) [#180](https://github.com/dwbutler/logstash-logger/issues/180)
|
|
16
|
+
- `max_record_bytes` option to drop any formatted record over a size limit, for every buffered output. Off by default. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
17
|
+
- `aws_max_record_bytes` option for Kinesis streams configured for records larger than 1 MiB. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
18
|
+
- The TCP output sends SNI for TLS connections to DNS hostnames. [GHSA-p7gr-ppfj-g45v](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-p7gr-ppfj-g45v)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
- HTTP device raises on non-2xx responses, so failed uploads are retried instead of silently dropped. [#184](https://github.com/dwbutler/logstash-logger/pull/184) [#181](https://github.com/dwbutler/logstash-logger/issues/181)
|
|
22
|
+
- HTTP device drops only the records a collector rejects with 400, 413, 415 or 422. Other error statuses are retried. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
23
|
+
- Kinesis and Firehose enforce the service's record and request limits, retry failed requests instead of treating them as delivered, and requeue throttled records without losing the batch. Kinesis `InternalFailure` records are retried. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
24
|
+
- Kafka drops only records the broker always rejects, instead of retrying the whole batch until the buffer fills. [GHSA-qcm4-6phg-mhpf](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-qcm4-6phg-mhpf)
|
|
25
|
+
- The TCP output closes the underlying TCP socket when a TLS connection is closed or fails to set up. [GHSA-p7gr-ppfj-g45v](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-p7gr-ppfj-g45v)
|
|
26
|
+
- A record that cannot be formatted is replaced by a placeholder event tagged `_logstashlogger_format_failure`, framed like any other record. [GHSA-88vf-w58m-gv7h](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-88vf-w58m-gv7h)
|
|
27
|
+
- An `@timestamp` in logged content that is not a valid time no longer fails the record. [GHSA-88vf-w58m-gv7h](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-88vf-w58m-gv7h)
|
|
28
|
+
- The `cee_syslog` formatter no longer shares the program name between concurrent log calls. [GHSA-88vf-w58m-gv7h](https://github.com/dwbutler/logstash-logger/security/advisories/GHSA-88vf-w58m-gv7h)
|
|
29
|
+
|
|
30
|
+
### Other
|
|
31
|
+
- CI covers Ruby 3.3, 3.4, 4.0 and JRuby 10 with Rails 8.0 and 8.1, and uploads coverage to Codecov. [#183](https://github.com/dwbutler/logstash-logger/pull/183) [#185](https://github.com/dwbutler/logstash-logger/pull/185)
|
|
32
|
+
- Fix an order-dependent Kafka spec. [#186](https://github.com/dwbutler/logstash-logger/pull/186)
|
|
33
|
+
|
|
1
34
|
## 1.0.0
|
|
2
35
|
|
|
3
36
|
### Breaking Changes
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -35,10 +35,10 @@ Or install it yourself as:
|
|
|
35
35
|
|
|
36
36
|
Verified to work with:
|
|
37
37
|
|
|
38
|
-
* MRI Ruby 3.
|
|
38
|
+
* MRI Ruby 3.3, 3.4, 4.0
|
|
39
39
|
* JRuby 10.x
|
|
40
40
|
|
|
41
|
-
Ruby versions < 3.
|
|
41
|
+
Ruby versions < 3.3 are EOL'ed and no longer supported.
|
|
42
42
|
|
|
43
43
|
## Documentation
|
|
44
44
|
|
data/docs/buffering.md
CHANGED
|
@@ -18,6 +18,7 @@ You can configure buffering behavior by passing the following options to LogStas
|
|
|
18
18
|
* `:sync` - Flush buffer every time a message is received (blocking). Defaults to false.
|
|
19
19
|
* `:buffer_flush_at_exit` - Flush messages when exiting the program. Defaults to true.
|
|
20
20
|
* `:buffer_logger` - Logger to write buffer debug/error messages to. Defaults to none.
|
|
21
|
+
* `:max_record_bytes` - Drop any single formatted record larger than this many bytes, with a warning that gives its size. Off by default.
|
|
21
22
|
|
|
22
23
|
You can turn buffering off by setting `sync = true`.
|
|
23
24
|
|
|
@@ -54,6 +55,21 @@ If you don't want to lose messages when the buffer gets full, you can set
|
|
|
54
55
|
`drop_messages_on_full_buffer = false`. Note that if the buffer gets full, any
|
|
55
56
|
incoming log message will block, which could be undesirable.
|
|
56
57
|
|
|
58
|
+
## Records that can never be delivered
|
|
59
|
+
|
|
60
|
+
Some outputs can reject a record for a reason that will not go away on retry, such as its size.
|
|
61
|
+
Instead of retrying the whole batch until the buffer fills, these outputs drop only the rejected
|
|
62
|
+
records, and log their size and the reason, never their content:
|
|
63
|
+
|
|
64
|
+
* Kafka: records the broker or client rejects with `MessageSizeTooLarge`, `RecordListTooLarge`
|
|
65
|
+
or `BufferOverflow`. The rest of the batch is sent one record at a time.
|
|
66
|
+
* Kinesis and Firehose: records over the service's per-record limit (Kinesis 1 MiB by default,
|
|
67
|
+
Firehose 1,000 KiB). Batches are split to fit each service's request limits. If your Kinesis
|
|
68
|
+
stream is configured for larger records, raise the limit with `aws_max_record_bytes`, for
|
|
69
|
+
example `aws_max_record_bytes: 10 * 1024 * 1024`.
|
|
70
|
+
* HTTP: records the collector rejects with 400, 413, 415 or 422. The rest of the batch is sent
|
|
71
|
+
one record at a time. Other error statuses, such as 401, 429 or 5xx, are retried.
|
|
72
|
+
|
|
57
73
|
## Sync Mode
|
|
58
74
|
|
|
59
75
|
All logger outputs support a `sync` setting. This is analogous to the "sync mode" setting on Ruby IO
|
data/docs/outputs.md
CHANGED
|
@@ -39,4 +39,27 @@ LogStashLogger.new \
|
|
|
39
39
|
url: 'http://localhost:8080'
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
A batch that gets an error status is retried on the next flush. If the collector rejects the content
|
|
43
|
+
(400, 413, 415 or 422), the records are sent one at a time and only the rejected ones are dropped.
|
|
44
|
+
See [Records that can never be delivered](buffering.md#records-that-can-never-be-delivered).
|
|
45
|
+
|
|
42
46
|
Note the parameter is `url` and not `uri`. Relies on [Net:HTTP](https://ruby-doc.org/stdlib-2.7.1/libdoc/net/http/rdoc/Net/HTTP.html#class-Net::HTTP-label-HTTPS) to auto-detect SSL usage from the scheme.
|
|
47
|
+
|
|
48
|
+
By default, each batch is sent as a JSON array, which is what the Logstash HTTP Input expects. For endpoints that expect newline-delimited JSON (NDJSON), with one event per line, set `format: :ndjson`:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
LogStashLogger.new \
|
|
52
|
+
type: :http,
|
|
53
|
+
url: 'https://example.com:8071/?token=TOKEN',
|
|
54
|
+
format: :ndjson
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The `format` option accepts `:json_array` (the default) or `:ndjson`. With `:ndjson` the request is sent with `Content-Type: application/x-ndjson`; with `:json_array` it uses `application/json`. If your endpoint expects a different header, set it explicitly with `content_type`:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
LogStashLogger.new \
|
|
61
|
+
type: :http,
|
|
62
|
+
url: 'https://example.com:8071/?token=TOKEN',
|
|
63
|
+
format: :ndjson,
|
|
64
|
+
content_type: 'application/json'
|
|
65
|
+
```
|
data/docs/rails.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rails Integration
|
|
2
2
|
|
|
3
|
-
Supports Rails
|
|
3
|
+
Supports Rails 8.0 and 8.1.
|
|
4
4
|
|
|
5
5
|
By default, every Rails log message will be written to logstash in `LogStash::Event` JSON format.
|
|
6
6
|
|
|
@@ -74,10 +74,15 @@ config.logstash.port = 5228
|
|
|
74
74
|
# Required
|
|
75
75
|
config.logstash.type = :tcp
|
|
76
76
|
|
|
77
|
-
# Optional, enables SSL
|
|
77
|
+
# Optional, enables SSL and verifies the server certificate against the system trust store
|
|
78
78
|
config.logstash.ssl_enable = true
|
|
79
|
+
|
|
80
|
+
# Optional, trusts this certificate (for a self-signed or private-CA server) and enables SSL
|
|
81
|
+
# config.logstash.ssl_certificate = '/path/to/certificate.crt'
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
See [SSL Configuration](ssl.md) for other options.
|
|
85
|
+
|
|
81
86
|
### Unix Socket
|
|
82
87
|
|
|
83
88
|
```ruby
|
data/docs/ssl.md
CHANGED
|
@@ -1,30 +1,57 @@
|
|
|
1
1
|
# SSL Configuration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The TCP output can connect to Logstash over TLS. The server certificate is always
|
|
4
|
+
verified, and so is the hostname, unless you opt out explicitly.
|
|
5
|
+
|
|
6
|
+
Enable TLS and verify the server against the system trust store:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
LogStashLogger.new(type: :tcp, host: 'logstash.example.com', port: 5228, ssl_enable: true)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If your Logstash server uses a self-signed certificate or a private CA, pass that
|
|
13
|
+
certificate (or CA bundle) as `ssl_certificate`. It is trusted in addition to the
|
|
14
|
+
system trust store, and TLS is enabled implicitly:
|
|
4
15
|
|
|
5
16
|
```ruby
|
|
6
|
-
LogStashLogger.new(type: :tcp, port: 5228, ssl_certificate: "/path/to/certificate.crt")
|
|
17
|
+
LogStashLogger.new(type: :tcp, host: 'logstash.example.com', port: 5228, ssl_certificate: "/path/to/certificate.crt")
|
|
7
18
|
```
|
|
8
19
|
|
|
9
|
-
The
|
|
20
|
+
The file is checked when the logger is created, and an `ArgumentError` is raised
|
|
21
|
+
if it is missing or is not a certificate.
|
|
22
|
+
|
|
23
|
+
A self-signed certificate and key for the Logstash server can be generated with
|
|
10
24
|
|
|
11
|
-
openssl req -x509 -batch -nodes -newkey rsa:2048 -
|
|
25
|
+
openssl req -x509 -batch -nodes -newkey rsa:2048 -subj "/CN=logstash.example.com" \
|
|
26
|
+
-addext "subjectAltName=DNS:logstash.example.com" -keyout logstash.key -out logstash.crt
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
Use the name your clients connect to in the certificate.
|
|
29
|
+
|
|
30
|
+
Specify an SSL context for full control over the connection. For example, to
|
|
31
|
+
trust a private CA:
|
|
14
32
|
|
|
15
33
|
```ruby
|
|
16
|
-
|
|
34
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
35
|
+
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: '/path/to/ca.pem')
|
|
36
|
+
LogStashLogger.new(type: :tcp, host: 'logstash.example.com', port: 5228, ssl_context: ctx)
|
|
17
37
|
```
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
### Turning verification off
|
|
40
|
+
|
|
41
|
+
Verification can only be turned off with an explicit context. This exposes the
|
|
42
|
+
log stream to anyone who can intercept the connection, so use it only for local
|
|
43
|
+
testing:
|
|
21
44
|
|
|
22
45
|
```ruby
|
|
23
46
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
24
|
-
ctx.
|
|
25
|
-
LogStashLogger.new(type: :tcp, port: 5228, ssl_context: ctx)
|
|
47
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
48
|
+
LogStashLogger.new(type: :tcp, host: 'localhost', port: 5228, ssl_context: ctx, verify_hostname: false)
|
|
26
49
|
```
|
|
27
50
|
|
|
51
|
+
Before 1.1.0, `ssl_enable: true` without an `ssl_context` did not verify the
|
|
52
|
+
server certificate. If you relied on that, either pass `ssl_certificate` with the
|
|
53
|
+
server's certificate, or use the explicit context above.
|
|
54
|
+
|
|
28
55
|
## Logstash Configuration for SSL
|
|
29
56
|
|
|
30
57
|
The following Logstash configuration is required for SSL:
|
|
@@ -44,47 +71,32 @@ input {
|
|
|
44
71
|
|
|
45
72
|
## Hostname Verification
|
|
46
73
|
|
|
47
|
-
Hostname verification is enabled by default.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
If you don't pass an `:ssl_context` or pass a falsey value to the
|
|
52
|
-
`:verify_hostname` option, hostname verification will not occur.
|
|
74
|
+
Hostname verification is enabled by default whenever TLS is used. The hostname
|
|
75
|
+
supplied to `:host` is checked against the server's certificate, and it is also
|
|
76
|
+
sent as the TLS server name (SNI) unless it is an IP address.
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
**Verify a hostname different from the `:host` option**
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
This is useful when connecting by IP address:
|
|
57
81
|
|
|
58
82
|
```ruby
|
|
59
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
|
60
|
-
ctx.cert = '/path/to/cert.pem'
|
|
61
|
-
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
62
|
-
|
|
63
83
|
LogStashLogger.new \
|
|
64
84
|
type: :tcp,
|
|
65
|
-
host: '
|
|
85
|
+
host: '1.2.3.4',
|
|
66
86
|
port: 5228,
|
|
67
|
-
|
|
87
|
+
ssl_certificate: '/path/to/certificate.crt',
|
|
88
|
+
verify_hostname: 'logstash.example.com'
|
|
68
89
|
```
|
|
69
90
|
|
|
70
|
-
**
|
|
71
|
-
|
|
72
|
-
```ruby
|
|
73
|
-
LogStashLogger.new \
|
|
74
|
-
type: :tcp,
|
|
75
|
-
host: '1.2.3.4'
|
|
76
|
-
port: 5228,
|
|
77
|
-
ssl_context: ctx,
|
|
78
|
-
verify_hostname: 'server.example.com'
|
|
79
|
-
```
|
|
91
|
+
**Disable hostname verification**
|
|
80
92
|
|
|
81
|
-
|
|
93
|
+
The certificate chain is still verified:
|
|
82
94
|
|
|
83
95
|
```ruby
|
|
84
96
|
LogStashLogger.new \
|
|
85
97
|
type: :tcp,
|
|
86
|
-
host: '1.2.3.4'
|
|
98
|
+
host: '1.2.3.4',
|
|
87
99
|
port: 5228,
|
|
88
|
-
|
|
100
|
+
ssl_certificate: '/path/to/certificate.crt',
|
|
89
101
|
verify_hostname: false
|
|
90
102
|
```
|
data/docs/troubleshooting.md
CHANGED
|
@@ -15,6 +15,24 @@ Your application is probably attempting to log data that is not encoded in a val
|
|
|
15
15
|
standard JSON library will raise an exception. You may be able to overcome this by swapping out a different JSON encoder
|
|
16
16
|
such as Oj. Use the [oj_mimic_json](https://github.com/ohler55/oj_mimic_json) gem to use Oj for JSON generation.
|
|
17
17
|
|
|
18
|
+
## Records tagged `_logstashlogger_format_failure`
|
|
19
|
+
|
|
20
|
+
When a log call cannot be turned into a record, for example because a logged
|
|
21
|
+
value cannot be serialized to JSON, LogStashLogger writes a placeholder record
|
|
22
|
+
instead. It is framed like any other record, so the records around it are not
|
|
23
|
+
affected. The placeholder has the message `Failed to format log event`, the
|
|
24
|
+
tag `_logstashlogger_format_failure`, the logger's time and severity, and the
|
|
25
|
+
error class in an `error` field. The error itself is reported to the error
|
|
26
|
+
logger. Before 1.1.0 a bare `Failed to format log event` string was written
|
|
27
|
+
without a line ending, which could merge with the next record.
|
|
28
|
+
|
|
29
|
+
## The `cee_syslog` header shows the local hostname
|
|
30
|
+
|
|
31
|
+
The `cee_syslog` formatter writes a `hostname progname:@cee:` header before
|
|
32
|
+
each record. The hostname is always this machine's hostname, and control
|
|
33
|
+
characters in the header are replaced with spaces. A `host` field in the logged
|
|
34
|
+
content, or one set in `customize_event`, appears only in the JSON body.
|
|
35
|
+
|
|
18
36
|
## No logs getting sent on Heroku
|
|
19
37
|
|
|
20
38
|
Heroku recommends installing the [rails_12factor](https://github.com/heroku/rails_12factor) so that logs get sent to STDOUT.
|
|
@@ -50,6 +68,10 @@ for other fields.
|
|
|
50
68
|
|
|
51
69
|
# Breaking changes
|
|
52
70
|
|
|
71
|
+
## Version 1.1+
|
|
72
|
+
|
|
73
|
+
MRI Ruby < 3.3 and Rails < 8.0 are no longer supported, since they have been EOL'ed. If you are on Ruby 3.2 or Rails 7.2, you will need to use 1.0.x.
|
|
74
|
+
|
|
53
75
|
## Version 0.27+
|
|
54
76
|
|
|
55
77
|
MRI Ruby < 3.2 is no longer supported, since it has been EOL'ed. If you are on an older version of Ruby, you will need to use 0.26 or below.
|
data/gemfiles/rails_8.0.gemfile
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
source "https://rubygems.org"
|
|
4
4
|
|
|
5
|
-
gem "
|
|
5
|
+
gem "simplecov", ">= 0.22", group: :test, require: false
|
|
6
|
+
gem "simplecov-cobertura", group: :test, require: false
|
|
6
7
|
gem "rails", "~> 8.0.0"
|
|
8
|
+
gem "json", "< 3"
|
|
7
9
|
|
|
8
10
|
gemspec path: "../"
|
data/gemfiles/rails_8.1.gemfile
CHANGED
|
@@ -180,6 +180,16 @@ module LogStashLogger
|
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
# Put items back into the pending queue for the next flush, without
|
|
184
|
+
# applying the full-buffer policy. Safe to call from inside a flush.
|
|
185
|
+
def buffer_requeue(items, group=nil)
|
|
186
|
+
return if items.empty?
|
|
187
|
+
@buffer_state[:pending_mutex].synchronize do
|
|
188
|
+
@buffer_state[:pending_items][group].concat(items)
|
|
189
|
+
@buffer_state[:pending_count] += items.size
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
183
193
|
# Try to flush events.
|
|
184
194
|
#
|
|
185
195
|
# Returns immediately if flushing is not necessary/possible at the moment:
|
|
@@ -305,11 +315,12 @@ module LogStashLogger
|
|
|
305
315
|
end
|
|
306
316
|
end
|
|
307
317
|
|
|
318
|
+
# Never kills the timer from inside itself: called on the timer thread,
|
|
319
|
+
# this keeps the running timer, and reset_buffer reuses it.
|
|
308
320
|
def reset_flush_timer_thread
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
end
|
|
321
|
+
return if @flush_timer_thread.nil? || @flush_timer_thread == Thread.current
|
|
322
|
+
@flush_timer_thread.kill
|
|
323
|
+
@flush_timer_thread = nil
|
|
313
324
|
end
|
|
314
325
|
|
|
315
326
|
def buffer_clear_pending
|
|
@@ -18,6 +18,7 @@ module LogStashLogger
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
attr_accessor :aws_region, :stream
|
|
21
|
+
attr_reader :max_record_bytes
|
|
21
22
|
|
|
22
23
|
def initialize(opts)
|
|
23
24
|
super
|
|
@@ -25,6 +26,22 @@ module LogStashLogger
|
|
|
25
26
|
@secret_access_key = opts[:aws_secret_access_key]
|
|
26
27
|
@aws_region = opts[:aws_region]
|
|
27
28
|
@stream = opts[:stream] || DEFAULT_STREAM
|
|
29
|
+
# The largest record the stream accepts. Kinesis streams can be
|
|
30
|
+
# configured above the 1 MiB default, up to 10 MiB.
|
|
31
|
+
@max_record_bytes = Integer(opts[:aws_max_record_bytes] || self.class.max_record_bytes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Service limits, defined by each subclass
|
|
35
|
+
def self.max_records
|
|
36
|
+
fail NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.max_record_bytes
|
|
40
|
+
fail NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.max_request_bytes
|
|
44
|
+
fail NotImplementedError
|
|
28
45
|
end
|
|
29
46
|
|
|
30
47
|
def transform_message(message)
|
|
@@ -55,27 +72,32 @@ module LogStashLogger
|
|
|
55
72
|
yield
|
|
56
73
|
rescue => e
|
|
57
74
|
log_error(e)
|
|
58
|
-
log_warning("giving up")
|
|
59
75
|
close(flush: false)
|
|
76
|
+
raise
|
|
60
77
|
end
|
|
61
78
|
|
|
62
79
|
def write_batch(messages, _group = nil)
|
|
63
|
-
records =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
records = []
|
|
81
|
+
messages.each do |message|
|
|
82
|
+
record = transform_message(message.to_s)
|
|
83
|
+
size = record_size(record)
|
|
84
|
+
if size > max_record_bytes
|
|
85
|
+
log_warning("dropping a record of #{size} bytes: over the #{max_record_bytes}-byte limit of #{self.class.stream_class.name}")
|
|
86
|
+
else
|
|
87
|
+
records << record
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
slices = request_slices(records)
|
|
92
|
+
slices.each_with_index do |slice, index|
|
|
93
|
+
begin
|
|
94
|
+
with_connection { send_slice(slice) }
|
|
95
|
+
rescue StandardError
|
|
96
|
+
# Already logged by with_connection. Only records that were not
|
|
97
|
+
# sent go back; dropped oversized records never do.
|
|
98
|
+
unsent = slices[index..-1].flatten(1).map { |r| r[:data] }
|
|
99
|
+
give_back(unsent)
|
|
100
|
+
return
|
|
79
101
|
end
|
|
80
102
|
end
|
|
81
103
|
end
|
|
@@ -88,6 +110,63 @@ module LogStashLogger
|
|
|
88
110
|
@io = nil
|
|
89
111
|
end
|
|
90
112
|
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def send_slice(records)
|
|
116
|
+
resp = put_records(records)
|
|
117
|
+
return if is_successful_response(resp)
|
|
118
|
+
|
|
119
|
+
retries = []
|
|
120
|
+
retry_codes = []
|
|
121
|
+
get_response_records(resp).each_with_index do |record, index|
|
|
122
|
+
if self.class.recoverable_error_codes.include?(record.error_code)
|
|
123
|
+
retries << records[index][:data]
|
|
124
|
+
retry_codes << record.error_code
|
|
125
|
+
elsif !record.error_code.nil? && record.error_code != ''
|
|
126
|
+
log_warning("dropping a record that #{self.class.stream_class.name} rejected: #{record.error_code} #{record.error_message}")
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
return if retries.empty?
|
|
130
|
+
|
|
131
|
+
log_warning("#{retries.size} records will be retried on the next flush (#{retry_codes.tally.map { |c, n| "#{c}: #{n}" }.join(', ')})")
|
|
132
|
+
# This runs inside a flush, so requeue without the full-buffer check,
|
|
133
|
+
# which would count the batch being flushed against the limit.
|
|
134
|
+
buffer_requeue(retries, @buffer_group)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def record_size(record)
|
|
138
|
+
record[:data].bytesize + record.fetch(:partition_key, '').bytesize
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Groups records into requests that fit the service's record-count and
|
|
142
|
+
# request-size limits.
|
|
143
|
+
def request_slices(records)
|
|
144
|
+
slices = []
|
|
145
|
+
bytes = 0
|
|
146
|
+
records.each do |record|
|
|
147
|
+
size = record_size(record)
|
|
148
|
+
if slices.empty? || slices.last.size >= self.class.max_records || bytes + size > self.class.max_request_bytes
|
|
149
|
+
slices << []
|
|
150
|
+
bytes = 0
|
|
151
|
+
end
|
|
152
|
+
slices.last << record
|
|
153
|
+
bytes += size
|
|
154
|
+
end
|
|
155
|
+
slices
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Records from a request that failed: retried on the next flush, or
|
|
159
|
+
# dropped when drop_messages_on_flush_error is set.
|
|
160
|
+
def give_back(messages)
|
|
161
|
+
return if messages.empty?
|
|
162
|
+
if @drop_messages_on_flush_error
|
|
163
|
+
log_warning("dropping #{messages.size} records after a failed request")
|
|
164
|
+
else
|
|
165
|
+
log_warning("#{messages.size} records will be retried on the next flush")
|
|
166
|
+
buffer_requeue(messages, @buffer_group)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
91
170
|
end
|
|
92
171
|
end
|
|
93
172
|
end
|
|
@@ -36,6 +36,11 @@ module LogStashLogger
|
|
|
36
36
|
|
|
37
37
|
@buffer_logger = opts[:buffer_logger]
|
|
38
38
|
|
|
39
|
+
# Optional cap on the size of a single formatted record. Off by default.
|
|
40
|
+
# Deleted from opts so outputs that forward their options (Redis) never
|
|
41
|
+
# receive it.
|
|
42
|
+
@max_record_bytes = parse_max_record_bytes(opts.delete(:max_record_bytes))
|
|
43
|
+
|
|
39
44
|
buffer_initialize(
|
|
40
45
|
max_items: @buffer_max_items,
|
|
41
46
|
max_interval: @buffer_max_interval,
|
|
@@ -48,7 +53,15 @@ module LogStashLogger
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def write(message)
|
|
51
|
-
|
|
56
|
+
return if message.nil?
|
|
57
|
+
if @max_record_bytes
|
|
58
|
+
size = message.to_s.bytesize
|
|
59
|
+
if size > @max_record_bytes
|
|
60
|
+
log_warning("dropping a record of #{size} bytes: over max_record_bytes (#{@max_record_bytes})")
|
|
61
|
+
return
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
buffer_receive(message, @buffer_group)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
67
|
def flush(*args)
|
|
@@ -109,6 +122,19 @@ module LogStashLogger
|
|
|
109
122
|
connect
|
|
110
123
|
end
|
|
111
124
|
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def parse_max_record_bytes(value)
|
|
128
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
129
|
+
bytes = Integer(value)
|
|
130
|
+
raise ArgumentError, "max_record_bytes must be a positive number of bytes: #{value.inspect}" unless bytes > 0
|
|
131
|
+
bytes
|
|
132
|
+
rescue TypeError
|
|
133
|
+
raise ArgumentError, "max_record_bytes must be a positive number of bytes: #{value.inspect}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
public
|
|
137
|
+
|
|
112
138
|
# Ensure the block is executed with a valid connection
|
|
113
139
|
def with_connection()
|
|
114
140
|
connect unless connected?
|
|
@@ -16,6 +16,12 @@ module LogStashLogger
|
|
|
16
16
|
"ServiceUnavailableException"
|
|
17
17
|
].freeze
|
|
18
18
|
|
|
19
|
+
# https://docs.aws.amazon.com/firehose/latest/APIReference/API_PutRecordBatch.html
|
|
20
|
+
# 1,000 KiB per record and 4 MB per request; these cannot be changed.
|
|
21
|
+
def self.max_records; 500; end
|
|
22
|
+
def self.max_record_bytes; 1_024_000; end
|
|
23
|
+
def self.max_request_bytes; 4_000_000; end
|
|
24
|
+
|
|
19
25
|
def transform_message(message)
|
|
20
26
|
{
|
|
21
27
|
data: message
|