logfmt-tagged_logger 0.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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: []