green_log 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a3c301ce67ee757ddf2093b93fc973ff46ad005f2ea7e8da3e693ca01a6c7444
4
+ data.tar.gz: 0ec5551c9554ec465acfd815f4a95514f8dbf48529427a0187d51b02657e2671
5
+ SHA512:
6
+ metadata.gz: 889da34f831f8d7b95bb79343b6905f88d0ffde04a308773ec6775ed70f2b7ffd8668f7eb9c0c8b6ec5fc4d7a2ffbbc6ff59ccecbbdac34fec6ac598c35bba0b
7
+ data.tar.gz: f72a8e18d791b8d57a6a413d40c870c7dfb3b34414dc6e00862972acf927ef34d3520676cc973f2fdb7c29f0d92b9d15a69fed230d257b8f19c069b9f8523e7e
@@ -0,0 +1,194 @@
1
+ # GreenLog
2
+
3
+ ![](https://github.com/greensync/green_log/workflows/CI/badge.svg)
4
+
5
+ GreenLog is a logging library for Ruby applications. It:
6
+
7
+ - focuses on [structured logging](https://www.thoughtworks.com/radar/techniques/structured-logging) - treating log entries as data
8
+ - is optimised for use in modern "cloud-native" applications
9
+ - can be used in place of Ruby's stdlib `Logger`
10
+
11
+ ## Design approach
12
+
13
+ GreenLog:
14
+
15
+ - [avoids global state](doc/adr/0002-avoid-global-configuration.md)
16
+ - avoids mutable objects
17
+ - explicitly [decouples log entry generation and handling](doc/adr/0003-decouple-generation-and-handling.md)
18
+ - uses [an approach similar to Rack middleware](doc/adr/0004-use-stacked-handlers-to-solve-many-problems.md) for flexible log processing
19
+ - uses [lock-free IO](doc/adr/0006-use-lock-free-io.md) for performance
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'green_log'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ ## Usage
34
+
35
+ ### tl;dr
36
+
37
+ ```ruby
38
+ require 'green_log'
39
+
40
+ logger = GreenLog::Logger.build
41
+
42
+ logger.info("Stuff happened")
43
+ # outputs: I -- Stuff happened
44
+ ```
45
+
46
+ ### Basic logging
47
+
48
+ GreenLog implements all the expected logging shortcuts:
49
+
50
+ ```ruby
51
+ logger.debug("Nitty gritty detail")
52
+ # outputs: D -- Nitty gritty detail
53
+ logger.info("Huh, interesting.")
54
+ # outputs: I -- Huh, interesting.
55
+ logger.warn("Careful now.")
56
+ # outputs: W -- Careful now.
57
+ logger.error("Oh, that's really not good!")
58
+ # outputs: E -- Oh, that's really not good!
59
+ logger.fatal("Byeeee ...")
60
+ # outputs: F -- Byeeee ...
61
+ ```
62
+
63
+ ### Adding context
64
+
65
+ `Logger#with_context` adds detail about the _source_ of log messages.
66
+
67
+ ```ruby
68
+ logger = GreenLog::Logger.build.with_context(pid: Process.pid, thread: Thread.current.object_id)
69
+ logger.info("Hello")
70
+ # outputs: I [pid=13545 thread=70260187418160] -- Hello
71
+ ```
72
+
73
+ It can be chained to inject additional context:
74
+
75
+ ```ruby
76
+ logger.with_context(request: 16273).info("Handled")
77
+ # outputs: I [pid=13545 thread=70260187418160 request=16273] -- Handled
78
+ ```
79
+
80
+ Context can also be calculated dynamically, using a block:
81
+
82
+ ```ruby
83
+ logger = GreenLog::Logger.build.with_context do
84
+ {
85
+ request_id: Thread.current[:request_id]
86
+ }
87
+ end
88
+ # outputs: I [pid=13545 thread=70260187418160 request=16273] -- Handled
89
+ ```
90
+
91
+ ### Including data and exceptions
92
+
93
+ A Hash of data can be included along with the log message:
94
+
95
+ ```ruby
96
+ logger = GreenLog::Logger.build
97
+ logger.info("New widget", id: widget.id)
98
+ # outputs: I -- New widget [id=12345]
99
+ ```
100
+
101
+ And/or, you can attach an exception:
102
+
103
+ ```ruby
104
+ begin
105
+ Integer("abc")
106
+ rescue => e
107
+ logger.error("parse error", e)
108
+ end
109
+ # outputs: E -- parse error
110
+ # ! ArgumentError: invalid value for Integer(): "abc"
111
+ # (irb):50:in `Integer'
112
+ # (irb):50:in `irb_binding'
113
+ # ...
114
+ ```
115
+
116
+ ### Alternate output format
117
+
118
+ By default GreenLog logs with a human-readable format; specify an alternate `format`
119
+ class if you want a different serialisation format. It comes bundled with a JSON writer, e.g.
120
+
121
+ ```ruby
122
+ logger = GreenLog::Logger.build(format: GreenLog::JsonWriter)
123
+ # OR
124
+ logger = GreenLog::Logger.build(format: "json")
125
+
126
+ logger.info("Structured!", foo: "bar")
127
+ ```
128
+
129
+ outputs
130
+
131
+ ```json
132
+ {"severity":"INFO","message":"Structured!","data":{"foo":"bar"},"context":{}}
133
+ ```
134
+
135
+ ### Alternate output destination
136
+
137
+ Logs go to STDOUT by default; specify `dest` to override, e.g.
138
+
139
+ ```ruby
140
+ logger = GreenLog::Logger.build(dest: STDERR)
141
+ ```
142
+
143
+ ### Filtering by log severity
144
+
145
+ By default all log entries will result in output. You can add a severity-threshold to avoid emitting debug-level log messages, e.g.
146
+
147
+ ```ruby
148
+ logger = GreenLog::Logger.build(severity_threshold: :INFO)
149
+ # OR
150
+ logger = GreenLog::Logger.build.with_severity_threshold(:INFO)
151
+
152
+ log.debug("Whatever") # ignored
153
+ ```
154
+
155
+ ### Block form
156
+
157
+ Rather than passing arguments, you can provide a block to generate log messages:
158
+
159
+ ```ruby
160
+ logger.info do
161
+ "generated message"
162
+ end
163
+ # outputs: I -- generated message
164
+ ```
165
+
166
+ The block may be ignored, if a severity-threshold is in effect:
167
+
168
+ ```ruby
169
+ logger = GreenLog::Logger.build(severity_threshold: :INFO)
170
+
171
+ log.debug do
172
+ # not evaluated
173
+ end
174
+ ```
175
+
176
+ ### Compatibility with stdlib Logger
177
+
178
+ GreenLog includes a backward-compatibile adapter for code written to use Ruby's built-in [`Logger`](https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger.html):
179
+
180
+ ```ruby
181
+ require 'green_log/classic_logger'
182
+
183
+ legacy_logger = logger.to_classic_logger
184
+ legacy_logger.warn("Old skool")
185
+ # outputs: W -- Old skool
186
+ ```
187
+
188
+ ## Contributing
189
+
190
+ Bug reports and pull requests are welcome on GitHub at https://github.com/greensync/green_log.
191
+
192
+ ## License
193
+
194
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "green_log"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,19 @@
1
+ # 1. Record architecture decisions
2
+
3
+ Date: 2019-12-18
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ We need to record the architectural decisions made on this project.
12
+
13
+ ## Decision
14
+
15
+ We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
16
+
17
+ ## Consequences
18
+
19
+ See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
@@ -0,0 +1,35 @@
1
+ # 2. Avoid global configuration
2
+
3
+ Date: 2019-12-18
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ Applications require a way to configure their logging, e.g. what should be logged, where should logs be sent, and how should they be formatted.
12
+
13
+ Some Ruby logging libraries involve global configuration, and other state, e.g.
14
+
15
+ ```ruby
16
+ require 'some_logger'
17
+ SomeLogger.default_level = :trace
18
+ SomeLogger.add_appender(file_name: 'development.log', formatter: :color)
19
+ SomeLogger["MyClass"].info("Stuff happened")
20
+ ```
21
+
22
+ but global state can have unintended consequences, and make testing difficult.
23
+
24
+ ## Decision
25
+
26
+ GreenLog will avoid global configuration. `GreenLog::Logger` instances should be created and configured explicitly, and injected as dependencies where needed.
27
+
28
+ ## Consequences
29
+
30
+ - Testing of logging logic should be straightforward.
31
+ - Libraries and applications can make their own logging arrangements.
32
+
33
+ BUT
34
+
35
+ - Applications will need to determine their own conventions for configuration of logging.
@@ -0,0 +1,30 @@
1
+ # 3. Decouple log generation from handling
2
+
3
+ Date: 2019-12-19
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ We want the logging API used by applications to be consistent, while allowing for logs to be filed, forwarded, filtered and formatted in a variety of ways.
12
+
13
+ ## Decision
14
+
15
+ De-couple generation of log message/entries from how they are handled.
16
+
17
+ * A `logger` object provides an API that can be used to generate log entries.
18
+ * Log "entries" are strongly typed structures.
19
+ * Log entry "handlers" provide a simple, consistent interface.
20
+
21
+ ```mermaid
22
+ sequenceDiagram
23
+
24
+ App ->> Logger: info("Message")
25
+ Logger ->> Handler: <<(entry)
26
+ ```
27
+
28
+ ## Consequences
29
+
30
+ * We can plug in different "handlers" relatively easily.
@@ -0,0 +1,51 @@
1
+ # 4. Use stacked handlers to solve many problems
2
+
3
+ Date: 2019-12-19
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ GreenLog "loggers" push generated log "entries" to log "handlers".
12
+
13
+ Formatting/filing/forwarding logs in different ways is achieved by using a different handler.
14
+
15
+ However, there are some cross-cutting concerns that might apply regardless of the way log entries are formatted, stored or forwarded. For example:
16
+
17
+ - we may want to inject context into log entries (e.g. process id, thread name, trace id)
18
+ - we may want to "tee" log entries to multiple destinations
19
+ - we may want to buffer log entries in-memory and output/forward them asyncronously
20
+
21
+ ## Decision
22
+
23
+ We will favour "stacking" handlers to solve such problems. That is:
24
+
25
+ * the source 'logger' will push entries to an intermediate handler
26
+ * the intermediate handler can
27
+ - transform the input entries as required
28
+ - forward them to one or more "downstream" handlers, as required
29
+ * handler "stacks" of arbitrary depth can be created
30
+
31
+ ```mermaid
32
+ sequenceDiagram
33
+
34
+ App ->> Logger: info("Message")
35
+ Logger ->> Middleware: <<(entry)
36
+ Middleware ->> Writer: <<(entry)
37
+ ```
38
+
39
+ This approach is based on [Rack](https://github.com/rack/rack)'s "middleware" concept.
40
+
41
+ ### Alternatives considered
42
+
43
+ I considered supporting "filters", which could take an `Entry` and return a transformed `Entry`, but that would not allow for:
44
+
45
+ * "tee-ing" the log to multiple destinations
46
+ * buffering or filtering of logs
47
+
48
+ ## Consequences
49
+
50
+ * There will be many and various "middleware" handlers.
51
+ * Handler stacks could get deep.
@@ -0,0 +1,27 @@
1
+ # 5. Restrict data-types allowable in log entries
2
+
3
+ Date: 2019-12-21
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ GreenLog allows data to be attached to log entries, either as "context", or "data" associated with the logged event.
12
+
13
+ Log entries may be consumed by a variety of "handlers", which
14
+
15
+ ## Decision
16
+
17
+ To make the job of "handlers" easier, GreenLog will restrict the type of value usable as log "context" or "data" to:
18
+
19
+ - `true`/`false`
20
+ - `Numeric`
21
+ - `String`
22
+ - `Time`
23
+ - `Hash` (with `String` or `Symbol` keys)
24
+
25
+ ## Consequences
26
+
27
+ - Other data must be converted to one of these types before inclusion in log entries.
@@ -0,0 +1,29 @@
1
+ # 6. Use lock-free IO
2
+
3
+ Date: 2019-12-24
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ We want to be able to write log entries (to file, or STDOUT), without them being interleaved.
12
+
13
+ But also, we want logging to perform well.
14
+
15
+ ## Decision
16
+
17
+ _Unlike_ the Ruby standard `Logger`, GreenLog will use a [lock-free logging](https://www.jstorimer.com/blogs/workingwithcode/7982047-is-lock-free-logging-safe) approach. That is, we will:
18
+
19
+ - avoid using of mutexes to serialise output
20
+ - perform atomic writes to `IO` streams (using `<<`)
21
+
22
+ ## Consequences
23
+
24
+ - IO code is simple.
25
+ - Performance should be good.
26
+
27
+ But:
28
+
29
+ - We risk interleaving of log entries if the size of the (serialized) entries gets over `PIPE_BUF_MAX` (1Mb on Linux).
@@ -0,0 +1,21 @@
1
+ #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+
6
+ require "green_log/logger"
7
+ require "green_log/simple_writer"
8
+
9
+ logger = GreenLog::Logger.new(GreenLog::SimpleWriter.new(STDOUT))
10
+
11
+ threads = ("A".."Z").map do |label|
12
+ Thread.new do
13
+ 1.upto(100) do
14
+ delay = rand / 10
15
+ logger.info("Hi", thread: label, delay: delay)
16
+ sleep(delay)
17
+ end
18
+ end
19
+ end
20
+
21
+ threads.each(&:join)