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 +7 -0
- data/CHANGELOG.md +14 -0
- data/README.md +154 -0
- data/lib/logfmt/tagged_logger.rb +60 -0
- metadata +79 -0
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: []
|