loggerstash 0.0.1

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.
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: []