fluent-plugin-grafana-loki 1.2.9 → 1.2.15
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/README.md +69 -3
- data/lib/fluent/plugin/out_loki.rb +81 -41
- metadata +52 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9da0220850d940db558feefbf825cd3e1b5a73c73f0a2e35516c98a9bc86f4f8
|
4
|
+
data.tar.gz: 50f3c8cfda6747c98cf0335f6cf60705b828e34e44c5372ac9ff5c13f59caf96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64ee54f30a6cb6da3c5b2725eebe9f3a8b25e41ba51bcf8027d876d33080ffbd831371252ba05f39a3dced2fad6d94a9b35f918d4be060e25f3d690223005a06
|
7
|
+
data.tar.gz: f6c507d56f16056306e8f53688a5a509cb1425947b57dba7e143aa4b2496dcb438adc38be2a1b9bde4051ef8c522214f514149888bb93fcd38760f08db143968
|
data/README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# Fluentd output plugin
|
2
2
|
|
3
|
-
[Fluentd](https://fluentd.org/)
|
3
|
+
[Fluentd](https://fluentd.org/) is a data collector for unified logging layer, it can be configured with the Loki output plugin, provided in this folder, to ship logs to Loki.
|
4
|
+
|
5
|
+
See [docs/client/fluentd/README.md](../../docs/sources/clients/fluentd/_index.md) for detailed information.
|
4
6
|
|
5
7
|
## Development
|
6
8
|
|
7
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
9
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
8
10
|
|
9
11
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `fluent-plugin-grafana-loki.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
10
12
|
|
@@ -13,6 +15,70 @@ To create the gem: `gem build fluent-plugin-grafana-loki.gemspec`
|
|
13
15
|
Useful additions:
|
14
16
|
`gem install rubocop`
|
15
17
|
|
18
|
+
## Testing
|
19
|
+
|
20
|
+
Start Loki using:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
docker run -it -p 3100:3100 grafana/loki:latest
|
24
|
+
```
|
25
|
+
|
26
|
+
Verify that Loki accept and stores logs:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
curl -H "Content-Type: application/json" -XPOST -s "http://localhost:3100/loki/api/v1/push" --data-raw "{\"streams\": [{\"stream\": {\"job\": \"test\"}, \"values\": [[\"$(date +%s)000000000\", \"fizzbuzz\"]]}]}"
|
30
|
+
curl "http://localhost:3100/loki/api/v1/query_range" --data-urlencode 'query={job="test"}' --data-urlencode 'step=300' | jq .data.result
|
31
|
+
```
|
32
|
+
|
33
|
+
The expected output is:
|
34
|
+
|
35
|
+
```json
|
36
|
+
[
|
37
|
+
{
|
38
|
+
"stream": {
|
39
|
+
"job": "test"
|
40
|
+
},
|
41
|
+
"values": [
|
42
|
+
[
|
43
|
+
"1588337198000000000",
|
44
|
+
"fizzbuzz"
|
45
|
+
]
|
46
|
+
]
|
47
|
+
}
|
48
|
+
]
|
49
|
+
```
|
50
|
+
|
51
|
+
Start and send test logs with Fluentd using:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
LOKI_URL=http://{{ IP }}:3100 make fluentd-test
|
55
|
+
```
|
56
|
+
|
57
|
+
Verify that syslogs are being feeded into Loki:
|
58
|
+
|
59
|
+
```bash
|
60
|
+
curl "http://localhost:3100/loki/api/v1/query_range" --data-urlencode 'query={job="fluentd"}' --data-urlencode 'step=300' | jq .data.result
|
61
|
+
```
|
62
|
+
|
63
|
+
The expected output is:
|
64
|
+
|
65
|
+
```json
|
66
|
+
[
|
67
|
+
{
|
68
|
+
"stream": {
|
69
|
+
"job": "fluentd"
|
70
|
+
},
|
71
|
+
"values": [
|
72
|
+
[
|
73
|
+
"1588336950379591919",
|
74
|
+
"log=\"May 1 14:42:30 ibuprofen avahi-daemon[859]: New relevant interface vethb503225.IPv6 for mDNS.\""
|
75
|
+
],
|
76
|
+
...
|
77
|
+
]
|
78
|
+
}
|
79
|
+
]
|
80
|
+
```
|
81
|
+
|
16
82
|
## Copyright
|
17
83
|
|
18
84
|
* Copyright(c) 2018- Grafana Labs
|
@@ -15,6 +15,7 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
+
require 'fluent/env'
|
18
19
|
require 'fluent/plugin/output'
|
19
20
|
require 'net/http'
|
20
21
|
require 'yajl'
|
@@ -26,26 +27,34 @@ module Fluent
|
|
26
27
|
class LokiOutput < Fluent::Plugin::Output # rubocop:disable Metrics/ClassLength
|
27
28
|
Fluent::Plugin.register_output('loki', self)
|
28
29
|
|
30
|
+
class LogPostError < StandardError; end
|
31
|
+
|
29
32
|
helpers :compat_parameters, :record_accessor
|
30
33
|
|
31
34
|
attr_accessor :record_accessors
|
32
35
|
|
33
36
|
DEFAULT_BUFFER_TYPE = 'memory'
|
34
37
|
|
35
|
-
desc '
|
36
|
-
config_param :url, :string, default: 'https://logs-us-
|
38
|
+
desc 'Loki API base URL'
|
39
|
+
config_param :url, :string, default: 'https://logs-prod-us-central1.grafana.net'
|
37
40
|
|
38
|
-
desc '
|
41
|
+
desc 'Authentication: basic auth credentials'
|
39
42
|
config_param :username, :string, default: nil
|
40
43
|
config_param :password, :string, default: nil, secret: true
|
41
44
|
|
42
|
-
desc '
|
45
|
+
desc 'Authentication: Authorization header with Bearer token scheme'
|
46
|
+
config_param :bearer_token_file, :string, default: nil
|
47
|
+
|
48
|
+
desc 'TLS: parameters for presenting a client certificate'
|
43
49
|
config_param :cert, :string, default: nil
|
44
50
|
config_param :key, :string, default: nil
|
45
51
|
|
46
|
-
desc 'TLS'
|
52
|
+
desc 'TLS: CA certificate file for server certificate verification'
|
47
53
|
config_param :ca_cert, :string, default: nil
|
48
54
|
|
55
|
+
desc 'TLS: disable server certificate verification'
|
56
|
+
config_param :insecure_tls, :bool, default: false
|
57
|
+
|
49
58
|
desc 'Loki tenant id'
|
50
59
|
config_param :tenant, :string, default: nil
|
51
60
|
|
@@ -74,7 +83,7 @@ module Fluent
|
|
74
83
|
super
|
75
84
|
@uri = URI.parse(@url + '/loki/api/v1/push')
|
76
85
|
unless @uri.is_a?(URI::HTTP) || @uri.is_a?(URI::HTTPS)
|
77
|
-
raise Fluent::ConfigError, '
|
86
|
+
raise Fluent::ConfigError, 'URL parameter must have HTTP/HTTPS scheme'
|
78
87
|
end
|
79
88
|
|
80
89
|
@record_accessors = {}
|
@@ -90,24 +99,42 @@ module Fluent
|
|
90
99
|
@remove_keys_accessors.push(record_accessor_create(key))
|
91
100
|
end
|
92
101
|
|
93
|
-
|
94
|
-
|
95
|
-
|
102
|
+
# If configured, load and validate client certificate (and corresponding key)
|
103
|
+
if client_cert_configured?
|
104
|
+
load_client_cert
|
105
|
+
validate_client_cert_key
|
96
106
|
end
|
97
107
|
|
108
|
+
raise "bearer_token_file #{@bearer_token_file} not found" if !@bearer_token_file.nil? && !File.exist?(@bearer_token_file)
|
109
|
+
|
110
|
+
@auth_token_bearer = nil
|
111
|
+
if !@bearer_token_file.nil?
|
112
|
+
if !File.exist?(@bearer_token_file)
|
113
|
+
raise "bearer_token_file #{@bearer_token_file} not found"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Read the file once, assume long-lived authentication token.
|
117
|
+
@auth_token_bearer = File.read(@bearer_token_file)
|
118
|
+
if @auth_token_bearer.empty?
|
119
|
+
raise "bearer_token_file #{@bearer_token_file} is empty"
|
120
|
+
end
|
121
|
+
log.info "will use Bearer token from bearer_token_file #{@bearer_token_file} in Authorization header"
|
122
|
+
end
|
123
|
+
|
124
|
+
|
98
125
|
raise "CA certificate file #{@ca_cert} not found" if !@ca_cert.nil? && !File.exist?(@ca_cert)
|
99
126
|
end
|
100
127
|
|
101
|
-
def
|
128
|
+
def client_cert_configured?
|
102
129
|
!@key.nil? && !@cert.nil?
|
103
130
|
end
|
104
131
|
|
105
|
-
def
|
132
|
+
def load_client_cert
|
106
133
|
@cert = OpenSSL::X509::Certificate.new(File.read(@cert)) if @cert
|
107
134
|
@key = OpenSSL::PKey.read(File.read(@key)) if @key
|
108
135
|
end
|
109
136
|
|
110
|
-
def
|
137
|
+
def validate_client_cert_key
|
111
138
|
if !@key.is_a?(OpenSSL::PKey::RSA) && !@key.is_a?(OpenSSL::PKey::DSA)
|
112
139
|
raise "Unsupported private key type #{key.class}"
|
113
140
|
end
|
@@ -117,58 +144,52 @@ module Fluent
|
|
117
144
|
true
|
118
145
|
end
|
119
146
|
|
120
|
-
def http_opts(uri)
|
121
|
-
opts = {
|
122
|
-
use_ssl: uri.scheme == 'https'
|
123
|
-
}
|
124
|
-
opts
|
125
|
-
end
|
126
|
-
|
127
147
|
# flush a chunk to loki
|
128
148
|
def write(chunk)
|
129
149
|
# streams by label
|
130
150
|
payload = generic_to_loki(chunk)
|
131
151
|
body = { 'streams' => payload }
|
132
152
|
|
133
|
-
|
153
|
+
tenant = extract_placeholders(@tenant, chunk) if @tenant
|
134
154
|
|
135
|
-
|
136
|
-
|
137
|
-
)
|
138
|
-
req.add_field('Content-Type', 'application/json')
|
139
|
-
req.add_field('X-Scope-OrgID', @tenant) if @tenant
|
140
|
-
req.body = Yajl.dump(body)
|
141
|
-
req.basic_auth(@username, @password) if @username
|
155
|
+
# add ingest path to loki url
|
156
|
+
res = loki_http_request(body, tenant)
|
142
157
|
|
143
|
-
|
158
|
+
if res.is_a?(Net::HTTPSuccess)
|
159
|
+
log.debug "POST request was responded to with status code #{res.code}"
|
160
|
+
return
|
161
|
+
end
|
144
162
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
res_summary = if res
|
149
|
-
"#{res.code} #{res.message} #{res.body}"
|
150
|
-
else
|
151
|
-
'res=nil'
|
152
|
-
end
|
153
|
-
log.warn "failed to #{req.method} #{@uri} (#{res_summary})"
|
154
|
-
log.warn Yajl.dump(body)
|
163
|
+
res_summary = "#{res.code} #{res.message} #{res.body}"
|
164
|
+
log.warn "failed to write post to #{@uri} (#{res_summary})"
|
165
|
+
log.debug Yajl.dump(body)
|
155
166
|
|
156
|
-
|
167
|
+
# Only retry 429 and 500s
|
168
|
+
raise(LogPostError, res_summary) if res.is_a?(Net::HTTPTooManyRequests) || res.is_a?(Net::HTTPServerError)
|
157
169
|
end
|
158
170
|
|
159
|
-
def
|
171
|
+
def http_request_opts(uri)
|
160
172
|
opts = {
|
161
173
|
use_ssl: uri.scheme == 'https'
|
162
174
|
}
|
163
175
|
|
176
|
+
# Optionally disable server server certificate verification.
|
177
|
+
if @insecure_tls
|
178
|
+
opts = opts.merge(
|
179
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Optionally present client certificate
|
164
184
|
if !@cert.nil? && !@key.nil?
|
165
185
|
opts = opts.merge(
|
166
|
-
verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
167
186
|
cert: @cert,
|
168
187
|
key: @key
|
169
188
|
)
|
170
189
|
end
|
171
190
|
|
191
|
+
# For server certificate verification: set custom CA bundle.
|
192
|
+
# Only takes effect when `insecure_tls` is not set.
|
172
193
|
unless @ca_cert.nil?
|
173
194
|
opts = opts.merge(
|
174
195
|
ca_file: @ca_cert
|
@@ -186,6 +207,25 @@ module Fluent
|
|
186
207
|
|
187
208
|
private
|
188
209
|
|
210
|
+
def loki_http_request(body, tenant)
|
211
|
+
req = Net::HTTP::Post.new(
|
212
|
+
@uri.request_uri
|
213
|
+
)
|
214
|
+
req.add_field('Content-Type', 'application/json')
|
215
|
+
req.add_field('Authorization', "Bearer #{@auth_token_bearer}") if !@auth_token_bearer.nil?
|
216
|
+
req.add_field('X-Scope-OrgID', tenant) if tenant
|
217
|
+
req.body = Yajl.dump(body)
|
218
|
+
req.basic_auth(@username, @password) if @username
|
219
|
+
|
220
|
+
opts = http_request_opts(@uri)
|
221
|
+
|
222
|
+
msg = "sending #{req.body.length} bytes to loki"
|
223
|
+
msg += " (tenant: \"#{tenant}\")" if tenant
|
224
|
+
log.debug msg
|
225
|
+
|
226
|
+
Net::HTTP.start(@uri.host, @uri.port, **opts) { |http| http.request(req) }
|
227
|
+
end
|
228
|
+
|
189
229
|
def numeric?(val)
|
190
230
|
!Float(val).nil?
|
191
231
|
rescue StandardError
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-grafana-loki
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- woodsaj
|
@@ -10,8 +10,28 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-
|
13
|
+
date: 2020-10-16 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: fluentd
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.9.3
|
22
|
+
- - "<"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '2'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.9.3
|
32
|
+
- - "<"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2'
|
15
35
|
- !ruby/object:Gem::Dependency
|
16
36
|
name: bundler
|
17
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -55,25 +75,47 @@ dependencies:
|
|
55
75
|
- !ruby/object:Gem::Version
|
56
76
|
version: '3.0'
|
57
77
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
78
|
+
name: rubocop-rspec
|
59
79
|
requirement: !ruby/object:Gem::Requirement
|
60
80
|
requirements:
|
61
81
|
- - ">="
|
62
82
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
64
|
-
|
83
|
+
version: '0'
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
65
89
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
67
|
-
|
90
|
+
version: '0'
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: simplecov
|
93
|
+
requirement: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
type: :development
|
68
99
|
prerelease: false
|
69
100
|
version_requirements: !ruby/object:Gem::Requirement
|
70
101
|
requirements:
|
71
102
|
- - ">="
|
72
103
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
74
|
-
|
104
|
+
version: '0'
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: test-unit
|
107
|
+
requirement: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
75
110
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
111
|
+
version: '0'
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
77
119
|
description: Output plugin to ship logs to a Grafana Loki server
|
78
120
|
email:
|
79
121
|
- awoods@grafana.com
|