loggerstash 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ Ever wanted to have your loggers send their data direct to logstash, without
2
+ going through a dozen intermediate processing steps? Do you worry your log
3
+ entries are being dropped because you're forced to use UDP for sending log
4
+ events? Would you like to have actionable metrics showing exactly how your
5
+ log forwarding system is working? Do you dream of being able to use modern
6
+ DNS record types, such as SRV (standardised in the year 2000!), to indicate
7
+ where your logstash servers are?
8
+
9
+ If so, you're in the right place.
10
+
11
+
12
+ # Installation
13
+
14
+ It's a gem:
15
+
16
+ gem install loggerstash
17
+
18
+ There's also the wonders of [the Gemfile](http://bundler.io):
19
+
20
+ gem 'loggerstash'
21
+
22
+ If you're the sturdy type that likes to run from git:
23
+
24
+ rake install
25
+
26
+ Or, if you've eschewed the convenience of Rubygems entirely, then you
27
+ presumably know what to do already.
28
+
29
+
30
+ # Logstash Configuration
31
+
32
+ In order for logstash to receive the events being written, it must have a
33
+ `json_lines` TCP input configured. Something like this will do the trick:
34
+
35
+ input {
36
+ tcp {
37
+ id => "json_lines"
38
+ port => 5151
39
+ codec => "json_lines"
40
+ }
41
+ }
42
+
43
+
44
+ # Usage
45
+
46
+ Start by including the necessary file:
47
+
48
+ require 'loggerstash'
49
+
50
+ next, create a new instance of `Loggerstash`, pointing to the logstash
51
+ server you wish to use:
52
+
53
+ ls = Loggerstash.new(logstash_server: "192.0.2.42:5151")
54
+
55
+ Anything that is a valid [logstash_writer
56
+ `server_name`](https://github.com/discourse/logstash_writer#usage) can be
57
+ specified as the `logstash_server`.
58
+
59
+ Once you have a Loggerstash, you just need to attach your `Loggerstash`
60
+ instance to either the `Logger` class (to have all loggers forward to
61
+ logstash), or to individual instances of Logger that you wish to forward:
62
+
63
+ # Forward just this logger's messages to logstash
64
+ l = Logger.new($stderr)
65
+ ls.attach(l)
66
+
67
+ # Forward all Logger instances' messages to logstash
68
+ ls.attach(Logger)
69
+
70
+ # Forward multiple loggers' messages to logstash
71
+ some_logger = Logger.new($stderr)
72
+ another_logger = Logger.new($stderr)
73
+
74
+ ls.attach(some_logger)
75
+ ls.attach(another_logger)
76
+
77
+ You can, in *theory*, attach Loggerstash to other things, but since it hooks
78
+ into the deep internals of Logger, it's unlikely to work very well on
79
+ anything else.
80
+
81
+ If you'd like to provide a richer event to logstash than the default (which
82
+ basically just maps progname, timestamp, severity, and message into a hash),
83
+ you can extend the default formatter:
84
+
85
+ ls.formatter = ->(s, t, p, m) do
86
+ default_formatter.call(s, t, p, m).merge(some_tag: "ohai!")
87
+ end
88
+
89
+ Of course, you can do entirely your own thing, and not call the default
90
+ formatter at all if you'd prefer.
91
+
92
+ You can also specify the formatter as a parameter to the constructor:
93
+
94
+ ls = Loggerstash.new(logstash_server: "...", formatter: ->(s, t, p, m) { ... }
95
+
96
+ If you want to expose the metrics maintained by the underlying
97
+ LogstashWriter instance, you can specify a `Prometheus::Client::Registry` to
98
+ register the metrics:
99
+
100
+ Loggerstash.new(logstash_server: "...", metrics_registry: @metrics_server.registry)
101
+
102
+ # ... OR ...
103
+ ls.metrics_registry = @metrics_server.registry
104
+
105
+ You can only pass a given metrics registry to one instance of `Loggerstash`,
106
+ because otherwise the registered metrics will conflict and everything will
107
+ be awful. If anyone ever comes up with a real-world use-case for needing
108
+ multiple Loggerstashes pointing to the same metrics registry, a PR would be
109
+ accepted to specify a prefix on the registered metrics.
110
+
111
+ Note that once you have attached a `Loggerstash` instance to a logger, you
112
+ can't change the `Loggerstash` configuration -- calls to any of the
113
+ configuration setters will raise an exception.
114
+
115
+
116
+ ## Prometheus Metrics
117
+
118
+ If you specify a metrics registry to use, a set of metrics with a
119
+ `logstash_writer_` prefix will be included and maintained. For the complete
120
+ set of metrics and their meanings, refer to [the `LogstashWriter`
121
+ documentation](https://github.com/discourse/logstash_writer#prometheus-metrics).
122
+
123
+
124
+ # Contributing
125
+
126
+ Patches can be sent as [a Github pull
127
+ request](https://github.com/discourse/loggerstash). This project is
128
+ intended to be a safe, welcoming space for collaboration, and contributors
129
+ are expected to adhere to the [Contributor Covenant code of
130
+ conduct](CODE_OF_CONDUCT.md).
131
+
132
+
133
+ # Licence
134
+
135
+ Unless otherwise stated, everything in this repo is covered by the following
136
+ copyright notice:
137
+
138
+ Copyright (C) 2018 Civilized Discourse Construction Kit, Inc.
139
+
140
+ This program is free software: you can redistribute it and/or modify it
141
+ under the terms of the GNU General Public License version 3, as
142
+ published by the Free Software Foundation.
143
+
144
+ This program is distributed in the hope that it will be useful,
145
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
146
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
147
+ GNU General Public License for more details.
148
+
149
+ You should have received a copy of the GNU General Public License
150
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,116 @@
1
+ require 'logstash_writer'
2
+ require 'thread'
3
+
4
+ # A sidecar class to augment a Logger with super-cow-logstash-forwarding
5
+ # powers.
6
+ #
7
+ class Loggerstash
8
+ # Base class of all Loggerstash errors
9
+ #
10
+ class Error < StandardError; end
11
+
12
+ # Raised if any configuration setter methods are called (`Loggerstash#<anything>=`)
13
+ # after the loggerstash instance has been attached to a logger.
14
+ #
15
+ class AlreadyRunningError < Error; end
16
+
17
+ attr_writer :formatter
18
+
19
+ def initialize(logstash_server:, metrics_registry: nil, formatter: nil)
20
+ @logstash_server = logstash_server
21
+ @metrics_registry = metrics_registry
22
+ @formatter = formatter
23
+
24
+ @op_mutex = Mutex.new
25
+ end
26
+
27
+ def attach(obj)
28
+ @op_mutex.synchronize do
29
+ obj.instance_variable_set(:@loggerstash, self)
30
+
31
+ if obj.is_a?(Module)
32
+ obj.prepend(Mixin)
33
+ else
34
+ obj.singleton_class.prepend(Mixin)
35
+ end
36
+
37
+ run_writer
38
+ end
39
+ end
40
+
41
+ %i{logstash_server metrics_registry}.each do |sym|
42
+ define_method(:"#{sym}=") do |v|
43
+ @op_mutex.synchronize do
44
+ if @logstash_writer
45
+ raise AlreadyRunningError,
46
+ "Cannot change #{sym} once writer is running"
47
+ end
48
+ instance_variable_set(:"@#{sym}", v)
49
+ end
50
+ end
51
+ end
52
+
53
+ def log_message(s, t, p, m)
54
+ @op_mutex.synchronize do
55
+ if @logstash_writer.nil?
56
+ run_writer
57
+ end
58
+
59
+ @logstash_writer.send_event((@formatter || default_formatter).call(s, t, p, m))
60
+ end
61
+ end
62
+ private
63
+
64
+ def run_writer
65
+ unless @op_mutex.owned?
66
+ raise RuntimeError,
67
+ "Must call run_writer while holding @op_mutex"
68
+ end
69
+
70
+ if @logstash_writer.nil?
71
+ {}.tap do |opts|
72
+ opts[:server_name] = @logstash_server
73
+ if @metrics_registry
74
+ opts[:metrics_registry] = @metrics_registry
75
+ end
76
+
77
+ @logstash_writer = LogstashWriter.new(**opts)
78
+ @logstash_writer.run
79
+ end
80
+ end
81
+ end
82
+
83
+ def default_formatter
84
+ @default_formatter ||= ->(s, t, p, m) do
85
+ {
86
+ "@timestamp" => t.utc.strftime("%FT%T.%NZ"),
87
+ message: m,
88
+ severity: s.downcase,
89
+ }.tap do |ev|
90
+ ev[:progname] = p if p
91
+ end
92
+ end
93
+ end
94
+
95
+ module Mixin
96
+ private
97
+
98
+ # Hooking into this specific method may seem... unorthodox, but
99
+ # it seemingly has an extremely stable interface and is the most
100
+ # appropriate place to inject ourselves.
101
+ def format_message(s, t, p, m)
102
+ loggerstash.log_message(s, t, p, m)
103
+
104
+ super
105
+ end
106
+
107
+ def loggerstash
108
+ ([self] + self.class.ancestors).find { |m| m.instance_variable_defined?(:@loggerstash) }.instance_variable_get(:@loggerstash).tap do |ls|
109
+ if ls.nil?
110
+ raise RuntimeError,
111
+ "Cannot find loggerstash instance. CAN'T HAPPEN."
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,44 @@
1
+ begin
2
+ require 'git-version-bump'
3
+ rescue LoadError
4
+ nil
5
+ end
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "loggerstash"
9
+
10
+ s.version = GVB.version rescue "0.0.0.1.NOGVB"
11
+ s.date = GVB.date rescue Time.now.strftime("%Y-%m-%d")
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.summary = "Monkeypatch Logger to send log entries to logstash"
16
+ s.description = <<~EOF
17
+ Provides a module you can prepend into any instance of Logger to cause
18
+ it to send all logged entries to a Logstash server, in addition to
19
+ however the message would have been handled otherwise.
20
+ EOF
21
+
22
+ s.authors = ["Matt Palmer"]
23
+ s.email = ["matt.palmer@discourse.org"]
24
+ s.homepage = "https://github.com/discourse/loggerstash"
25
+
26
+ s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
27
+
28
+ s.required_ruby_version = ">= 2.3.0"
29
+
30
+ s.add_runtime_dependency "logstash_writer", "~> 0.0"
31
+
32
+ s.add_development_dependency 'bundler'
33
+ s.add_development_dependency 'github-release'
34
+ s.add_development_dependency 'git-version-bump'
35
+ s.add_development_dependency 'guard-rspec'
36
+ s.add_development_dependency 'guard-rubocop'
37
+ s.add_development_dependency 'rack-test'
38
+ s.add_development_dependency 'rake', "~> 12.0"
39
+ s.add_development_dependency 'redcarpet'
40
+ s.add_development_dependency 'rspec'
41
+ s.add_development_dependency 'rubocop'
42
+ s.add_development_dependency 'simplecov'
43
+ s.add_development_dependency 'yard'
44
+ end
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loggerstash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Palmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash_writer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: github-release
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: git-version-bump
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-test
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '12.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '12.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: redcarpet
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: yard
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: |
196
+ Provides a module you can prepend into any instance of Logger to cause
197
+ it to send all logged entries to a Logstash server, in addition to
198
+ however the message would have been handled otherwise.
199
+ email:
200
+ - matt.palmer@discourse.org
201
+ executables: []
202
+ extensions: []
203
+ extra_rdoc_files: []
204
+ files:
205
+ - ".gitignore"
206
+ - ".rubocop.yml"
207
+ - ".travis.yml"
208
+ - ".yardopts"
209
+ - CODE_OF_CONDUCT.md
210
+ - CONTRIBUTING.md
211
+ - LICENCE
212
+ - README.md
213
+ - lib/loggerstash.rb
214
+ - loggerstash.gemspec
215
+ homepage: https://github.com/discourse/loggerstash
216
+ licenses: []
217
+ metadata: {}
218
+ post_install_message:
219
+ rdoc_options: []
220
+ require_paths:
221
+ - lib
222
+ required_ruby_version: !ruby/object:Gem::Requirement
223
+ requirements:
224
+ - - ">="
225
+ - !ruby/object:Gem::Version
226
+ version: 2.3.0
227
+ required_rubygems_version: !ruby/object:Gem::Requirement
228
+ requirements:
229
+ - - ">="
230
+ - !ruby/object:Gem::Version
231
+ version: '0'
232
+ requirements: []
233
+ rubyforge_project:
234
+ rubygems_version: 2.7.7
235
+ signing_key:
236
+ specification_version: 4
237
+ summary: Monkeypatch Logger to send log entries to logstash
238
+ test_files: []