logfmt-tagged_logger 0.1.0.beta.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a95519748d42cf63a14c82f759cc4e3d7b1e486d62de1dc7f0ac32b004c09679
4
+ data.tar.gz: 6839988ba2c4dbbc70cdf0247a308b696abeca96ffa19976d58f074fbd8d1cbb
5
+ SHA512:
6
+ metadata.gz: d69856eab735eb407db8002c079301eb866791a368bffeedec4590d66b998818ace0b97d40b25bfa7477d022056c2cf0463a09e39a8c7ba96253a0d6e8ac3124
7
+ data.tar.gz: 876ef90a4c8a1bad3b54d9e21e5c89bdcf425c44735ad193d0b671f8050d179707b43bd553628aebe75a9555dbc029f28b6c488b084506193778812583319e85
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
5
+
6
+ ## \[Unreleased\]
7
+
8
+ ## [0.0.10] 2022-04-30
9
+ ### Changed
10
+ - Autoload the `Logfmt::Parser` when it's used, in preparation for the coming `Logfmt::Logger` and friends.
11
+ Alternatively you can eager-load it into memory: `require "logfmt/parser"`.
12
+
13
+ ### Added
14
+ - This CHANGELOG file.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # Logfmt
2
+
3
+ Write and parse structured log lines in the [logfmt style][logfmt-blog].
4
+
5
+ ## Installation
6
+
7
+ Add this line to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "logfmt"
11
+ ```
12
+
13
+ And then install:
14
+
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself:
20
+
21
+ ```bash
22
+ $ gem install logfmt
23
+ ```
24
+
25
+ ### Versioning
26
+
27
+ This project adheres to [Semantic Versioning][semver].
28
+
29
+ ## Usage
30
+
31
+ `Logfmt` is composed to two parts: writing structured log lines in the `logfmt` style, and parsing `logfmt`-style log lines.
32
+
33
+ While writing and parsing `logfmt` are related, we've found that it's common to only need to do one or there other in a single application.
34
+ To support that usage, `Logfmt` leverages Ruby's `autoload` to lazily load the `Logfmt::Parser` or `Logfmt::Logger` (and associated code) into memory.
35
+ In the general case that looks something like:
36
+
37
+ ```ruby
38
+ require "logfmt"
39
+
40
+ Logfmt # This constant was already loaded, but neither Logfmt::Parser
41
+ # nor Logfmt::Logger constants are loaded. Yet.
42
+
43
+ Logfmt.parse("…")
44
+ # OR
45
+ Logfmt::Parser.parse("…")
46
+
47
+ # Either of the above will load the Logfmt::Parser constant.
48
+ # Similarly you can autoload the Logfmt::Logger via
49
+
50
+ Logfmt::Logger.new
51
+ ```
52
+
53
+ If you want to eagerly load the logger or parser, you can do that by requiring them directly
54
+
55
+ ### Parsing log lines
56
+
57
+ ```ruby
58
+ require "logfmt/parser"
59
+
60
+ Logfmt::Parser.parse('foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf')
61
+ #=> {"foo"=>"bar", "a"=>14, "baz"=>"hello kitty", "cool%story"=>"bro", "f"=>true, "%^asdf"=>true}
62
+ ```
63
+
64
+ ### Writing log lines
65
+
66
+ The `Logfmt::Logger` is built on the stdlib `::Logger` and adheres to its API.
67
+ The primary difference is that `Logfmt::Logger` defaults to a `logfmt`-style formatter.
68
+ Specifically, a `Logfmt::Logger::KeyValueFormatter`, which results in log lines something like this:
69
+
70
+ ```ruby
71
+ require "logfmt/logger"
72
+
73
+ logger = Logfmt::Logger.new($stdout)
74
+
75
+ logger.info(foo: "bar", a: 14, "baz" => "hello kitty", "cool%story" => "bro", f: true, "%^asdf" => true)
76
+ #=> time=2022-04-20T23:30:54.647403Z severity=INFO foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf
77
+
78
+ logger.debug("MADE IT HERE!")
79
+ #=> time=2022-04-20T23:33:44.912595Z severity=DEBUG msg="MADE IT HERE!"
80
+ ```
81
+
82
+ #### Tagged log lines
83
+
84
+ The `logfmt-tagged_logger` gem adds support for Rails-style [tagged logging][tagged-logger].
85
+ This gem adds a `Logfmt::TaggedLogger` which is built on `ActiveSupport::TaggedLogger`, but emits the tags in logfmt-style, as key/value pairs.
86
+ For example
87
+
88
+ ```ruby
89
+ logger = Logfmt::TaggedLogger.new($stdout)
90
+
91
+ logger.tagged(source: "api") do
92
+ logger.info(foo: "bar")
93
+ end
94
+
95
+ #=> time=2022-04-20T23:33:44.912595Z severity=info source=api foo=bar"
96
+ ```
97
+
98
+ You can also pass "bare" tags and they'll be collected and emitted under the `tags` key.
99
+
100
+ ```ruby
101
+ logger = Logfmt::TaggedLogger.new($stdout)
102
+
103
+ logger.tagged("API", "1.2.3.4") do
104
+ logger.info(foo: "bar")
105
+ end
106
+
107
+ #=> time=2022-04-20T23:33:44.912595Z severity=info tags="[API] [1.2.3.4]" foo=bar"
108
+ ```
109
+
110
+ It's likely more helpful and useful to use meaningful key/values for your tags, rather than bare tags.
111
+
112
+ #### Expected key/value transformations
113
+
114
+ When writing a log line with the `Logfmt::Logger::KeyValueFormatter` the keys and/or values will be transformed thusly:
115
+
116
+ * "Bare messages" (those with no key given when invoking the logger) will be wrapped in the `msg` key.
117
+
118
+ ```ruby
119
+ logger.info("here")
120
+ #=> time=2022-04-20T23:33:49.912997Z severity=INFO msg=here
121
+ ```
122
+
123
+ * Values, including bare messages, containing white space or control characters (spaces, tabs, newlines, emoji, etc…) will be wrapped in double quotes (`""`) and fully escaped.
124
+
125
+ ```ruby
126
+ logger.info("👻 Boo!")
127
+ #=> time=2022-04-20T23:33:35.912595Z severity=INFO msg="\u{1F47B} Boo!"
128
+
129
+ logger.info(number: 42, with_quotes: %{These "are" 'quotes', OK?})
130
+ #=> time=2022-04-20T23:33:36.412183Z severity=INFO number=42 with_quotes="These \"are\" 'quotes', OK?"
131
+ ```
132
+
133
+ * Floating point values are truncated to three digits.
134
+
135
+ * Time values are formatted as ISO8601 strings, with six digits sub-second precision.
136
+
137
+ * A value that is an Array is wrapped in square brackets, and then the above rules applied to each Array value.
138
+ This works well for arrays of simple values - like numbers, symbols, or simple strings.
139
+ But complex data structures will result in human mind-breaking escape sequences.
140
+ So don't do that.
141
+ Keep values simple.
142
+
143
+ ```ruby
144
+ logger.info(an_array: [1, "two", :three])
145
+ #=> time=2022-04-20T23:33:36.412183Z severity=INFO an_array="[1, two, three]"
146
+ ```
147
+
148
+ **NOTE**: it is **not** expected that log lines generated by `Logfmt` can be round-tripped by parsing the log line with `Logfmt`.
149
+ Specifically, this applies to Unicode and some control characters, as well as bare messages which will be wrapped in the `msg` key when writing.
150
+ Additionally, symbol keys will be parsed back into string keys.
151
+
152
+ [logfmt-blog]: https://brandur.org/logfmt "Structured log lines with key/value pairs"
153
+ [semver]: https://semver.org/spec/v2.0.0.html "Semantic Versioning 2.0.0"
154
+ [tagged-logger]: https://guides.rubyonrails.org/debugging_rails_applications.html#tagged-logging "Tagged Logging"
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "logger"
4
+ require "active_support"
5
+
6
+ module Logfmt
7
+ class TaggedLogger < ActiveSupport::Logger
8
+ include ActiveSupport::TaggedLogging
9
+
10
+ def initialize(*args, formatter: nil, **kwargs)
11
+ super
12
+ # There is a bug in ActiveSupport::Logger where it ignores the formatter:
13
+ # kwarg given as part of ::new and instead always sets the logger to an
14
+ # instance of ActiveSupport::Logger::SimpleFormatter.
15
+ # see: https://github.com/rails/rails/pull/44942
16
+ #
17
+ # When that's addressed we can change this line to:
18
+ #
19
+ # ```ruby
20
+ # self.formatter ||= KeyValueFormatter.new
21
+ # ```
22
+ self.formatter = formatter || Logger::KeyValueFormatter.new
23
+
24
+ # Wrap the base formatter in our tagging-aware formatter
25
+ self.formatter = Formatter.new(self.formatter)
26
+ end
27
+
28
+ class Formatter < SimpleDelegator
29
+ include ActiveSupport::TaggedLogging::Formatter
30
+
31
+ def call(severity, timestamp, progname, msg)
32
+ tag_pairs, bare_tags = current_tags.partition { |t| t.respond_to?(:to_hash) }
33
+
34
+ pairs = {tags: format_bare_tags(bare_tags)}
35
+ pairs = tag_pairs.map(&:to_hash)
36
+ .inject(pairs, :merge)
37
+ .merge(message_to_hash(msg))
38
+ .compact
39
+
40
+ __getobj__.call(severity, timestamp, progname, pairs)
41
+ end
42
+
43
+ private
44
+
45
+ def format_bare_tags(tags)
46
+ tags = Array(tags).compact
47
+
48
+ return nil if tags.empty?
49
+
50
+ tags.map { |tag| "[#{tag}]" }.join(" ")
51
+ end
52
+
53
+ def message_to_hash(message)
54
+ return message.to_hash if message.respond_to?(:to_hash)
55
+
56
+ {msg: message}
57
+ end
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logfmt-tagged_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta.1
5
+ platform: ruby
6
+ authors:
7
+ - Steven Harman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logfmt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0.beta.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.0.beta.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ description: Make logfmt aware of ActiveSupport::TaggedLogging to write logfmt-styled
42
+ log lines, with tags!
43
+ email:
44
+ - steven@harmanly.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - README.md
51
+ - lib/logfmt/tagged_logger.rb
52
+ homepage: https://github.com/cyberdelia/logfmt-ruby
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ bug_tracker_uri: https://github.com/cyberdelia/logfmt-ruby/issues
57
+ changelog_uri: https://github.com/cyberdelia/logfmt-ruby/blog/master/CHANGELOG.md
58
+ documentation_uri: https://github.com/cyberdelia/logfmt-ruby
59
+ source_code_uri: https://github.com/cyberdelia/logfmt-ruby
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.5.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">"
72
+ - !ruby/object:Gem::Version
73
+ version: 1.3.1
74
+ requirements: []
75
+ rubygems_version: 3.3.7
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: An ActiveSupport::TaggedLogging compatible logger for logfmt.
79
+ test_files: []