intake 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: febaaa600f3b7a5c8ed974a8e059a4d5ea12e61b0e5edf6fb0bf3c707f56027b
4
- data.tar.gz: 9bc82ce4ad485486a397e63c84c7a54445df78b2e3f285b1d0ec26d1cfddfdc8
3
+ metadata.gz: b845805181f81717bfc41734b8f35905d0adff760ba182e95c9ece95285d7a4e
4
+ data.tar.gz: 19e290811468e20994441c4ea2f2747b81cfbc36d257ae1d9eadd1a18713c705
5
5
  SHA512:
6
- metadata.gz: bf360302e2ece8d18034a1b34587d3fa6c8a267f8d13ec64535b7de212e7307235b58b76a28c5f429fed832ee1ef4a1aa05c57391ba5cd74f98d207aa2c71033
7
- data.tar.gz: d140b351c1171e9a5bce2e5e4d01926900300f60a8e18ce0c0c591cadcf54ac6ced6fa22b584cf3d8ad4dff754dfc31399613ba39a1b2a8a750869026c430ab1
6
+ metadata.gz: df4713a410191c85e398f66bea3aea0c97bcfaf5274472689c2889cf7a185cd9c36bee2ee6a9196d7fc27e7c2d5efeafb61045972302eaf4f5f199c62878594e
7
+ data.tar.gz: bc7a9a6e520c8de2d2c043ba999327e1f604bd8310f425ad5ce096c020b9b51d85ca6e8d3f69b4a96bb32002cda26a1cf7f3f2597b5217584ee619ec3c3eb1a8
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![.github/workflows/rsspec.yml](https://github.com/the-vk/intake/actions/workflows/rspec.yml/badge.svg)](https://github.com/the-vk/intake/actions/workflows/rspec.yml)
2
+
1
3
  # intake
2
4
 
3
5
  ## Description
@@ -15,6 +17,198 @@ The library is designed with multiple base principles:
15
17
  gem install intake
16
18
  ```
17
19
 
20
+ ## Design
21
+
22
+ **intake** has two primary components:
23
+
24
+ * Logger
25
+ * Sink
26
+
27
+ **Intake::Logger** is a component that captures a logging event and forwards that to **Intake::EventDrain** which is a single point to collect log events.
28
+
29
+ A logger may optionally filter events by filter to quickly discard events with level below threshold.
30
+
31
+ **Intake::Sink** is a component that receives event logs and writes to a permanent storage.
32
+
33
+ A sink may filter events by level, logger name, or any other event attributes.
34
+
35
+ ## Examples
36
+
37
+ This example sets up logging to write messages to STDOUT.
38
+ **intake** writes messages in Ruby Logger format.
39
+ ```ruby
40
+ require 'intake`
41
+
42
+ log = Intake[:root]
43
+ log.level = :info
44
+ Intake.add_sink Intake::IOSink.new($stdout)
45
+
46
+ log.debug 'debug message'
47
+ log.debug { 'proc debug message' }
48
+ # Logger methods can take a block to generate log message
49
+ # Blocks allow to delay expensive message evaluation until and unless event is logged
50
+ log.debug do
51
+ print("you don't see me!")
52
+ 'expensive proc debug message'
53
+ end
54
+ log.info 'info message'
55
+ log.info { 'proc info message' }
56
+ log.warn 'warn message'
57
+ log.warn { 'proc warn message' }
58
+ log.error 'error message'
59
+ log.error { 'proc error message' }
60
+ log.fatal 'fatal message'
61
+ log.fatal { 'proc fatal message' }
62
+ ```
63
+
64
+ Output:
65
+
66
+ ```
67
+ I, [2022-10-09T15:57:58.377963 #12484] INFO -- : info message
68
+ I, [2022-10-09T15:57:58.378097 #12484] INFO -- : proc info message
69
+ W, [2022-10-09T15:57:58.378121 #12484] WARN -- : warn message
70
+ W, [2022-10-09T15:57:58.378133 #12484] WARN -- : proc warn message
71
+ E, [2022-10-09T15:57:58.378169 #12484] ERROR -- : error message
72
+ E, [2022-10-09T15:57:58.378212 #12484] ERROR -- : proc error message
73
+ F, [2022-10-09T15:57:58.378255 #12484] FATAL -- : fatal message
74
+ F, [2022-10-09T15:57:58.378297 #12484] FATAL -- : proc fatal message
75
+ ```
76
+
77
+ ### Sinks and filters
78
+
79
+ **intake** supports multuple target sinks with various filters.
80
+
81
+ ```ruby
82
+ log = Intake[:root]
83
+ log.level = :debug
84
+ io_sink = Intake::IOSink.new($stdout)
85
+ # filter is a proc-like object to make a decision on events
86
+ # event is accepted by sink if #call(event) returns true
87
+ io_sink.add_filter Intake::Filters::LevelFilter.new(:warn)
88
+ Intake.add_sink io_sink
89
+ file_sink = Intake::IOSink.new(File.new('/dev/null', 'a'))
90
+ file_sink.add_filter Intake::Filters::LevelFilter.new(:info)
91
+
92
+ log.debug 'debug message' # not logged
93
+ log.info 'info message' # logged to file
94
+ log.warn 'warn message' # logged to both stdout and file
95
+ log.error 'error message' # logged to both stdout and file
96
+ log.fatal 'fatal message' # logged to both stdout and file
97
+ ```
98
+
99
+ Output:
100
+
101
+ ```
102
+ W, [2022-10-09T16:08:48.752905 #14476] WARN -- : warn message
103
+ E, [2022-10-09T16:08:48.752983 #14476] ERROR -- : error message
104
+ F, [2022-10-09T16:08:48.753031 #14476] FATAL -- : fatal message
105
+ ```
106
+
107
+ ### MDC
108
+
109
+ Mapped diagnostic context (MDC) is a thread-local storage to attach extra information to log events, e.g. operation id or correlation id.
110
+
111
+ ```ruby
112
+ log = Intake[:root]
113
+ log.level = :info
114
+ sink = Intake::IOSink.new($stdout)
115
+ sink.formatter = ->(e) { "#{e.timestamp} [#{e[:correlation_id]}] - #{e.logger_name}: - #{e.message}\n" }
116
+ Intake.add_sink sink
117
+
118
+ log.info 'a message'
119
+
120
+ Intake::MDC[:correlation_id] = :abc
121
+
122
+ log.info 'message with MDC'
123
+
124
+ Intake::MDC.clear(:correlation_id)
125
+
126
+ log.info 'a message with no MDC'
127
+ ```
128
+
129
+ Output:
130
+
131
+ ```
132
+ 2022-10-09 16:13:23 -0700 [] - root: - a message
133
+ 2022-10-09 16:13:23 -0700 [abc] - root: - message with MDC
134
+ 2022-10-09 16:13:23 -0700 [] - root: - a message with no MDC
135
+ ```
136
+
137
+ ### Structured logging
138
+
139
+ **Intake::Logger** methods takes optional keyword argument **meta** with a Hash with extra details about log event.
140
+ Sink may write meta to output in structured format that allows to query logs.
141
+
142
+ ```ruby
143
+ log = Intake[:root]
144
+ log.level = :info
145
+ sink = Intake::IOSink.new($stdout)
146
+ sink.formatter = ->(e) { "#{e.timestamp} [#{e[:user_id]}] - #{e.logger_name}: - #{e.message}\n" }
147
+ Intake.add_sink sink
148
+
149
+ log.info 'a message', meta: { user_id: 'username' }
150
+ ```
151
+
152
+ Output:
153
+
154
+ ```
155
+ 2022-10-09 16:18:21 -0700 [username] - root: - a message
156
+ ```
157
+ ### Ruby Logger adapter
158
+
159
+ **intake** provides an adapter to Ruby Logger API. Adapter can be used as drop-in replacement of regular Ruby Logger.
160
+
161
+ ```ruby
162
+ require 'intake'
163
+
164
+ log = Intake[:root]
165
+ Intake.add_sink Intake::IOSink.new($stdout)
166
+ log.level = :debug
167
+ log = log.as_ruby_logger
168
+
169
+ log.add(Logger::Severity::FATAL, 'msg', 'sample')
170
+
171
+ log.debug 'debug'
172
+ log.info 'info'
173
+ log.warn 'warn'
174
+ log.error 'error'
175
+ log.fatal 'fatal'
176
+ log.unknown 'unknown'
177
+
178
+ log.warn { 'warn proc message' }
179
+
180
+ log = Intake[:root].as_ruby_logger(progname: 'sample')
181
+
182
+ log.debug 'debug'
183
+ log.info 'info'
184
+ log.warn 'warn'
185
+ log.error 'error'
186
+ log.fatal 'fatal'
187
+ log.unknown 'unknown'
188
+
189
+ log.warn { 'warn proc message' }
190
+ ```
191
+
192
+ Output:
193
+
194
+ ```
195
+ F, [2022-10-09T16:16:13.918872 #14918] FATAL -- sample: msg
196
+ D, [2022-10-09T16:16:13.918992 #14918] DEBUG -- : debug
197
+ I, [2022-10-09T16:16:13.919053 #14918] INFO -- : info
198
+ W, [2022-10-09T16:16:13.919114 #14918] WARN -- : warn
199
+ E, [2022-10-09T16:16:13.919143 #14918] ERROR -- : error
200
+ F, [2022-10-09T16:16:13.919191 #14918] FATAL -- : fatal
201
+ F, [2022-10-09T16:16:13.919219 #14918] FATAL -- : unknown
202
+ W, [2022-10-09T16:16:13.919267 #14918] WARN -- : warn proc message
203
+ D, [2022-10-09T16:16:13.919319 #14918] DEBUG -- sample: debug
204
+ I, [2022-10-09T16:16:13.919363 #14918] INFO -- sample: info
205
+ W, [2022-10-09T16:16:13.919417 #14918] WARN -- sample: warn
206
+ E, [2022-10-09T16:16:13.919442 #14918] ERROR -- sample: error
207
+ F, [2022-10-09T16:16:13.919491 #14918] FATAL -- sample: fatal
208
+ F, [2022-10-09T16:16:13.919542 #14918] FATAL -- sample: unknown
209
+ W, [2022-10-09T16:16:13.919594 #14918] WARN -- sample: warn proc message
210
+ ```
211
+
18
212
  ## License
19
213
 
20
214
  The MIT License. See the [LICENSE](/LICENSE) file for the full text.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Intake
4
+ # Base class that formats exception object to string
5
+ class ExceptionFormatter
6
+ attr_accessor :backtrace, :cause
7
+
8
+ def initialize
9
+ @backtrace = true
10
+ @cause = true
11
+ end
12
+
13
+ # Formats error to string
14
+ def call(err)
15
+ format(err, []).join("\n")
16
+ end
17
+
18
+ protected
19
+
20
+ def format(err, lines)
21
+ lines << format_title(err)
22
+ lines.concat(err.backtrace) if backtrace
23
+ format(err.cause, lines) if cause && !err.cause.nil?
24
+ lines
25
+ end
26
+
27
+ def format_title(err)
28
+ "Caused by: <#{err.class.name}> #{err.message}"
29
+ end
30
+
31
+ def format_backtrace(err, lines)
32
+ lines + err.backtrace
33
+ end
34
+ end
35
+ end
@@ -9,7 +9,7 @@ module Intake
9
9
 
10
10
  def call(event)
11
11
  # rubocop:disable Layout/LineLength
12
- "#{event.level.to_s[0]}, [#{event.timestamp.strftime(@timestamp_format)} ##{Process.pid}] #{event.level} -- #{event[:progname]}: #{event.message}\n"
12
+ "#{event.level.to_s[0]}, [#{event.timestamp.strftime(@timestamp_format)} ##{Process.pid}] #{event.level} -- #{event[:progname]}: #{event.message}"
13
13
  # rubocop:enable Layout/LineLength
14
14
  end
15
15
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'exception_formatter'
3
4
  require_relative 'formatter'
4
5
  require_relative 'sink'
5
6
 
@@ -10,12 +11,14 @@ module Intake
10
11
  super()
11
12
  @io = io
12
13
  @formatter = ::Intake::Formatter.new
14
+ @exception_formatter = ::Intake::ExceptionFormatter.new
13
15
  end
14
16
 
15
- attr_writer :formatter
17
+ attr_accessor :formatter, :exception_formatter
16
18
 
17
19
  def drain(event)
18
- txt = @formatter.call(event)
20
+ error_message = "\n#{@exception_formatter.call(event[:error])}" unless event[:error].nil?
21
+ txt = "#{@formatter.call(event)}#{error_message}\n"
19
22
  @io.write(txt)
20
23
  end
21
24
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Intake
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intake
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Maraev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-09 00:00:00.000000000 Z
11
+ date: 2022-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -148,6 +148,7 @@ files:
148
148
  - lib/intake.rb
149
149
  - lib/intake/async_sink.rb
150
150
  - lib/intake/event_drain.rb
151
+ - lib/intake/exception_formatter.rb
151
152
  - lib/intake/filter.rb
152
153
  - lib/intake/filters/level_filter.rb
153
154
  - lib/intake/filters/logger_name_prefix_filter.rb