green_log 0.1.0

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.
@@ -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)