json_tagged_logger 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +101 -49
- data/lib/json_tagged_logger/formatter.rb +11 -2
- data/lib/json_tagged_logger/log_tags_config.rb +6 -2
- data/lib/json_tagged_logger/logger.rb +3 -0
- data/lib/json_tagged_logger/tag_from_session.rb +2 -0
- data/lib/json_tagged_logger/version.rb +1 -1
- metadata +73 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fde9c968aab725f59b4d0002b2e318b286edb8e8b7790222d1002e51ce631e6e
|
4
|
+
data.tar.gz: 97e23c6b02ec7faa8869659c59ef8ba1f2bc200cd607b4e248d2e8a7e74cab5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c21e9f1e126a80a8160587f04d30ca7ccd83433471e78c017320734d570f3cb43935e78bf8bd0a858d114c98fd2fc2115b45363f9e0e283d4c3d6ba182dd94e
|
7
|
+
data.tar.gz: be70dcda430a3341613c09c85eebcc66f98c9361658192808ab6fb9b0bd63b498713e8ff907fad6c6ce116dead29c302acb11c8bb51bf91edd5e8a6b3df880ab
|
data/README.md
CHANGED
@@ -1,89 +1,141 @@
|
|
1
1
|
# JsonTaggedLogger
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://github.com/santry/json_tagged_logger/actions/workflows/ci.yml/badge.svg)](https://github.com/santry/json_tagged_logger/actions/workflows/ci.yml)
|
4
4
|
|
5
|
+
`JsonTaggedLogger` works in conjunction with [`ActiveSupport::TaggedLogging`](https://api.rubyonrails.org/classes/ActiveSupport/TaggedLogging.html) and (optionally) [Lograge](https://github.com/roidrage/lograge) to produce JSON-formatted log output. By itself, `ActiveSupport::TaggedLogging` supports simple tagging. With `JsonTaggedLogger`, you can compose key/value pairs, simple tags, and the log message itself into a single JSON document for easy consumption and parsing in log aggregators.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Given the following configuration,
|
5
10
|
|
6
11
|
```ruby
|
7
12
|
# Gemfile
|
8
13
|
gem 'json_tagged_logger'
|
14
|
+
gem 'lograge' # since you probably want all your request logs in JSON
|
9
15
|
|
10
16
|
# config/environments/production.rb
|
11
17
|
Rails.application.configure do
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
# …
|
19
|
+
config.log_tags = JsonTaggedLogger::LogTagsConfig.generate(
|
20
|
+
:request_id,
|
21
|
+
:host,
|
22
|
+
->(request) { { my_param: request.query_parameters["my_param"] }.to_json },
|
23
|
+
JsonTaggedLogger::TagFromSession.get(:user_id),
|
24
|
+
JsonTaggedLogger::TagFromHeaders.get(:my_custom_header, "X-Custom-Header"),
|
25
|
+
)
|
26
|
+
|
27
|
+
logger = ActiveSupport::Logger.new(STDOUT)
|
28
|
+
config.logger = JsonTaggedLogger::Logger.new(logger)
|
29
|
+
|
30
|
+
config.lograge.enabled = true
|
31
|
+
config.lograge.formatter = Lograge::Formatters::Json.new
|
32
|
+
# …
|
17
33
|
end
|
18
|
-
|
19
34
|
```
|
20
35
|
|
21
|
-
|
22
|
-
## Why?
|
23
|
-
|
24
|
-
On its own, ActiveSupport::TaggedLogging adds individual tags wrapped in squre brackets at the start of each line of log output, like so
|
36
|
+
A request like
|
25
37
|
|
26
38
|
```
|
27
|
-
|
39
|
+
curl -H "X-Custom-Header: some header value" 'http://127.0.0.1:3000/?my_param=param%20value'
|
28
40
|
```
|
29
41
|
|
30
|
-
|
42
|
+
will get you something like
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
44
|
+
```json
|
45
|
+
{
|
46
|
+
"level": "INFO",
|
47
|
+
"request_id": "914f6104-538d-4ddc-beef-37bfe06ca1c7",
|
48
|
+
"host": "127.0.0.1",
|
49
|
+
"my_param": "param value",
|
50
|
+
"user_id": "99",
|
51
|
+
"my_custom_header": "some header value",
|
52
|
+
"method": "GET",
|
53
|
+
"path": "/",
|
54
|
+
"format": "*/*",
|
55
|
+
"controller": "SessionsController",
|
56
|
+
"action": "new",
|
57
|
+
"status": 302,
|
58
|
+
"duration": 0.51,
|
59
|
+
"view": 0,
|
60
|
+
"db": 0,
|
61
|
+
"location": "http://127.0.0.1:3000/profile"
|
62
|
+
}
|
38
63
|
```
|
39
64
|
|
40
|
-
|
65
|
+
[Note: I've pretty-printed the output in these examples for easier reading. The actual log output will be on a single line without extra whitespace.]
|
41
66
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
67
|
+
Importantly, if the controller action (or any code it calls along the way) has an explicit call to `Rails.logger.tagged("TAG").info("tagged log message")`, you'll get the same key/value tags (`request_id`, `host`, `my_param`, &c.) in the JSON document along with a `tags` key:
|
68
|
+
|
69
|
+
```json
|
70
|
+
{
|
71
|
+
"level": "INFO",
|
72
|
+
"request_id": "914f6104-538d-4ddc-beef-37bfe06ca1c7",
|
73
|
+
"host": "127.0.0.1",
|
74
|
+
"my_param": "param value",
|
75
|
+
"user_id": "99",
|
76
|
+
"my_custom_header": "some header value",
|
77
|
+
"tags": [
|
78
|
+
"TAG"
|
79
|
+
],
|
80
|
+
"msg": "tagged log message"
|
81
|
+
}
|
47
82
|
```
|
48
83
|
|
49
|
-
|
84
|
+
If you have nested calls to the tagged logger, like
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Rails.logger.tagged("TAG") do
|
88
|
+
Rails.logger.tagged("NESTED").info("nested tagged log message")
|
89
|
+
end
|
50
90
|
|
51
|
-
```
|
52
|
-
[{"request_id":"c31c3658-5b76-40cd-b583-d0fcbdee0710"}] [{"host":"127.0.0.1"}] log message
|
53
91
|
```
|
54
92
|
|
55
|
-
|
93
|
+
those will be added to the `tags` key in the JSON document
|
56
94
|
|
57
95
|
```json
|
58
|
-
{
|
96
|
+
{
|
97
|
+
"level": "INFO",
|
98
|
+
"request_id": "914f6104-538d-4ddc-beef-37bfe06ca1c7",
|
99
|
+
"host": "127.0.0.1",
|
100
|
+
"my_param": "param value",
|
101
|
+
"user_id": "99",
|
102
|
+
"my_custom_header": "some header value",
|
103
|
+
"tags": [
|
104
|
+
"TAG",
|
105
|
+
"NESTED"
|
106
|
+
],
|
107
|
+
"msg": "nested tagged log message"
|
108
|
+
}
|
59
109
|
```
|
60
110
|
|
61
|
-
|
111
|
+
## Why?
|
62
112
|
|
63
|
-
|
113
|
+
On its own, `ActiveSupport::TaggedLogging` adds individual tags wrapped in squre brackets at the start of each line of log output. A configuration like
|
64
114
|
|
65
115
|
```ruby
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def self.build_user_info_from_session(request)
|
77
|
-
# left as an exercise for the reader
|
78
|
-
end
|
116
|
+
Rails.application.configure do
|
117
|
+
# …
|
118
|
+
config.log_tags = [ :request_id, :host, ->(request) { request.query_parameters["my_param"] } ]
|
119
|
+
logger = ActiveSupport::Logger.new(STDOUT)
|
120
|
+
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
121
|
+
# …
|
79
122
|
end
|
80
|
-
|
81
|
-
Rails.application.config.log_tags = [ :request_id, :host, UserInfoLogTag.user_info ]
|
82
123
|
```
|
83
124
|
|
84
|
-
|
125
|
+
will get you logs like
|
85
126
|
|
86
127
|
```
|
87
|
-
|
128
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] Started GET "/?my_param=param%20value" for 127.0.0.1 at 2023-01-15 00:13:21 -0500
|
129
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] Processing by SessionsController#new as */*
|
130
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] Parameters: {"my_param"=>"param value"}
|
131
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] [TAG] [NESTED] nested tagged log message
|
132
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] Redirected to http://127.0.0.1:3000/sign_in
|
133
|
+
[22a03298-5fd9-4b28-bbe2-1d4c0d7f74f0] [127.0.0.1] [param value] Completed 302 Found in 1ms (ActiveRecord: 0.0ms | Allocations: 1070)
|
88
134
|
```
|
89
135
|
|
136
|
+
This is fine as far as it goes, but if you use tagged logging with something like [Lograge](https://github.com/roidrage/lograge) to format your request logs as JSON, the list of square-bracketed tags will still just be prepended to the log line, followed by the JSON. Also, any calls to `Rails.logger.tagged` will be logged as plain text:
|
137
|
+
|
138
|
+
```
|
139
|
+
[d4fac896-f916-48d7-9d32-d35a39cfb7d8] [127.0.0.1] [param value] [TAG] [NESTED] nested tagged log message
|
140
|
+
[d4fac896-f916-48d7-9d32-d35a39cfb7d8] [127.0.0.1] [param value] {"method":"GET","path":"/","format":"*/*","controller":"SessionsController","action":"new","status":302,"duration":1.2,"view":0.0,"db":0.0,"location":"http://127.0.0.1:3000/sign_in"}
|
141
|
+
```
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
require 'json'
|
3
|
+
|
1
4
|
module JsonTaggedLogger
|
2
5
|
class Formatter
|
3
6
|
def call(severity, _time, _progname, message)
|
@@ -10,7 +13,7 @@ module JsonTaggedLogger
|
|
10
13
|
json_tags.each { |t| log.merge!(t) }
|
11
14
|
|
12
15
|
if text_tags.present?
|
13
|
-
log[:tags] = text_tags
|
16
|
+
log[:tags] = text_tags.to_a
|
14
17
|
end
|
15
18
|
|
16
19
|
bare_message = message_without_tags(message)
|
@@ -21,8 +24,14 @@ module JsonTaggedLogger
|
|
21
24
|
parsed_message = bare_message
|
22
25
|
ensure
|
23
26
|
if parsed_message.is_a?(Hash)
|
24
|
-
|
27
|
+
parsed_message.symbolize_keys!
|
28
|
+
if log.has_key?(:tags) && parsed_message.has_key?(:tags)
|
29
|
+
parsed_message[:tags] = parsed_message[:tags] + log[:tags]
|
30
|
+
end
|
31
|
+
|
32
|
+
log.merge!(parsed_message)
|
25
33
|
else
|
34
|
+
#binding.irb
|
26
35
|
log.merge!(msg: parsed_message.strip)
|
27
36
|
end
|
28
37
|
end
|
@@ -1,13 +1,17 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
|
1
3
|
module JsonTaggedLogger
|
2
4
|
class LogTagsConfig
|
3
5
|
def self.generate(*tags)
|
4
6
|
tags.map do |tag|
|
5
|
-
if tag.is_a?(
|
7
|
+
if tag.is_a?(String)
|
8
|
+
tag
|
9
|
+
elsif tag.is_a?(Proc) && tag.arity == 1
|
6
10
|
tag
|
7
11
|
elsif tag.is_a?(Symbol) && ActionDispatch::Request.method_defined?(tag)
|
8
12
|
-> (request) { { tag => request.send(tag) }.to_json }
|
9
13
|
else
|
10
|
-
raise ArgumentError, "Only symbols that ActionDispatch::Request responds to or single-argument Procs allowed. You provided '#{tag.inspect}'."
|
14
|
+
raise ArgumentError, "Only strings, symbols that ActionDispatch::Request responds to or single-argument Procs allowed. You provided '#{tag.inspect}'."
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_tagged_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Santry
|
@@ -9,7 +9,77 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 1980-01-01 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '6.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '6.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: actionpack
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '6.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '6.1'
|
13
83
|
description: Formatter for logging with ActiveSupport::TaggedLogging as JSON
|
14
84
|
email:
|
15
85
|
- sean@santry.us
|
@@ -38,7 +108,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
108
|
requirements:
|
39
109
|
- - ">="
|
40
110
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
111
|
+
version: '2.7'
|
42
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
113
|
requirements:
|
44
114
|
- - ">="
|