json_tagged_logger 0.1.0

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: 9d9b6a0f1e36ce3cc9e4bc89ea942c6ecea3868d240f58230604c30c954232a2
4
+ data.tar.gz: 10acc0b50c43dd4d938920a315e11aad21b860022397f4e36273e98f3f4d9192
5
+ SHA512:
6
+ metadata.gz: 19ee0ae1fa6341aafb6443c21d805e6e787e7bcd5f8e7447a42a38b428925f47a88b101025e3beef34e2db1b74c968ce2ff819374897c8d2d848253cbb9c27c3
7
+ data.tar.gz: c5697f900381e3d33862b1ff750385ea4feb7a8e05723f719c1b3853a09f7d1336ae7a332caf3fa8b9684c3482019b2bfb319a4cd7ef9b35a5b9a1737addc9f9
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2023 Sean Santry
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # JsonTaggedLogger
2
+
3
+ JsonTaggedLogger provides a log formatter that works in conjunction with ActiveSupport::TaggedLogging to product JSON-formatted log output.
4
+
5
+
6
+ ```ruby
7
+ # Gemfile
8
+ gem 'json_tagged_logger'
9
+
10
+ # config/environments/production.rb
11
+ Rails.application.configure do
12
+ # …
13
+ config.log_tags = JsonTaggedLogger::LogTagsConfig.generate(:request_id, :host, ->(request) { { example: "proc" }.to_json })
14
+ logger = ActiveSupport::Logger.new(STDOUT)
15
+ config.logger = JsonTaggedLogger::Logger.new(logger)
16
+ # …
17
+ end
18
+
19
+ ```
20
+
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
25
+
26
+ ```
27
+ [tag1] [tag2] : Foo bar
28
+ ```
29
+
30
+ This is fine as far as it goes, but if you wish to use tagged logging with something like [Lograge](https://github.com/roidrage/lograge) to format your logs as JSON, there's no good way to get the tags into that json output, specially if any of your tags are generated by procs.
31
+
32
+ ## How does it work?
33
+
34
+ First, JsonTaggedLogger::LogTags helps you generate an array lof log tags suitable for passing to Rails.application.config.log_tags to produce JSON logs. For example, given
35
+
36
+ ```
37
+ Rails.application.config.log_tags = JsonTaggedLogger::LogTags.generate_config(:request_id, :host)
38
+ ```
39
+
40
+ ActiveSupport::TaggedLogging will see the following log tags
41
+
42
+ ```
43
+ [
44
+ -> (request) { { request_id: request.send(:request_id) }.to_json },
45
+ -> (request) { { host: request.send(:host) }.to_json }
46
+ ]
47
+ ```
48
+
49
+ Why is this an improvement over `Rails.application.config.log_tags = [:request_id, :host]`? Well, with the JsonTaggedLogger version, your logs will now contain
50
+
51
+ ```
52
+ [{"request_id":"c31c3658-5b76-40cd-b583-d0fcbdee0710"}] [{"host":"127.0.0.1"}] log message
53
+ ```
54
+
55
+ which `JsonTaggedLogger::Formatter` will turn into
56
+
57
+ ```json
58
+ {"request_id":"c31c3658-5b76-40cd-b583-d0fcbdee0710","host":"127.0.0.1","msg":"log message"}
59
+ ```
60
+
61
+ Lovely, eh?
62
+
63
+ The real advantage comes when you have more complicated procs that generage your log tags. So long as your proc produces a JSON string, `JsonTaggedLogger::Formatter` will be able to extract it from the tag and add it to the log hash.
64
+
65
+ ```ruby
66
+ class UserInfoLogTag
67
+ def self.user_info
68
+ lambda do |request|
69
+ user_info = build_user_info_from_session(request) # build user info from request by inspecting session, etc
70
+ { user_info: user_info }.to_json
71
+ end
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
79
+ end
80
+
81
+ Rails.application.config.log_tags = [ :request_id, :host, UserInfoLogTag.user_info ]
82
+ ```
83
+
84
+ That will get you
85
+
86
+ ```
87
+ {"request_id":"c31c3658-5b76-40cd-b583-d0fcbdee0710","host":"127.0.0.1","user_info":{"user_id":"1234","user_email":"user@example.com"},"msg":"log message"}
88
+ ```
89
+
@@ -0,0 +1,64 @@
1
+ module JsonTaggedLogger
2
+ class Formatter
3
+ def call(severity, _time, _progname, message)
4
+ log = {
5
+ level: severity,
6
+ }
7
+
8
+ json_tags, text_tags = extract_tags
9
+
10
+ json_tags.each { |t| log.merge!(t) }
11
+
12
+ if text_tags.present?
13
+ log[:tags] = text_tags
14
+ end
15
+
16
+ bare_message = message_without_tags(message)
17
+
18
+ begin
19
+ parsed_message = JSON.parse(bare_message)
20
+ rescue JSON::ParserError
21
+ parsed_message = bare_message
22
+ ensure
23
+ if parsed_message.is_a?(Hash)
24
+ log.merge!(parsed_message.symbolize_keys)
25
+ else
26
+ log.merge!(msg: parsed_message.strip)
27
+ end
28
+ end
29
+
30
+ log.compact.to_json + "\n"
31
+ end
32
+
33
+ private
34
+
35
+ def extract_tags
36
+ json_tags = Set[]
37
+ text_tags = Set[]
38
+
39
+ current_tags.each do |t|
40
+ begin
41
+ tag = JSON.parse(t)
42
+ rescue JSON::ParserError
43
+ tag = t
44
+ ensure
45
+ if tag.is_a?(Hash)
46
+ json_tags << tag
47
+ else
48
+ text_tags << tag
49
+ end
50
+ end
51
+ end
52
+
53
+ [json_tags, text_tags]
54
+ end
55
+
56
+ def message_without_tags(message)
57
+ if tags_text.present?
58
+ message.gsub(tags_text.strip, '')
59
+ else
60
+ message
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,15 @@
1
+ module JsonTaggedLogger
2
+ class LogTagsConfig
3
+ def self.generate(*tags)
4
+ tags.map do |tag|
5
+ if tag.is_a?(Proc) && tag.arity == 1
6
+ tag
7
+ elsif tag.is_a?(Symbol) && ActionDispatch::Request.method_defined?(tag)
8
+ -> (request) { { tag => request.send(tag) }.to_json }
9
+ else
10
+ raise ArgumentError, "Only symbols that ActionDispatch::Request responds to or single-argument Procs allowed. You provided '#{tag.inspect}'."
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module JsonTaggedLogger
2
+ module Logger
3
+ def self.new(logger)
4
+ logger.formatter = Formatter.new
5
+ ActiveSupport::TaggedLogging.new(logger)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ module JsonTaggedLogger
2
+ module TagFromSession
3
+ def self.get(key)
4
+ lambda do |request|
5
+ { key => get_value_from_session(request, key) }.to_json
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ def self.get_value_from_session(request, key)
12
+ session_options = Rails.application.config.session_options
13
+ session_store = Rails.application.config.session_store.new(Rails.application, session_options)
14
+ session = ActionDispatch::Request::Session.create(session_store, request, session_options)
15
+
16
+ session[key]
17
+ ensure
18
+ # Clean up side effects from loading the session so it can be loaded as
19
+ # usual during the normal request cycle. Leaving these headers in place
20
+ # seems to break some RSpec specs that interact with the session.
21
+ request.delete_header("rack.session")
22
+ request.delete_header("rack.session.options")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module JsonTaggedLogger
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'json_tagged_logger/formatter'
2
+ require 'json_tagged_logger/log_tags_config'
3
+ require 'json_tagged_logger/logger'
4
+ require 'json_tagged_logger/tag_from_session'
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_tagged_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Santry
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Formatter for logging with ActiveSupport::TaggedLogging as JSON
14
+ email:
15
+ - sean@santry.us
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/json_tagged_logger.rb
23
+ - lib/json_tagged_logger/formatter.rb
24
+ - lib/json_tagged_logger/log_tags_config.rb
25
+ - lib/json_tagged_logger/logger.rb
26
+ - lib/json_tagged_logger/tag_from_session.rb
27
+ - lib/json_tagged_logger/version.rb
28
+ homepage: https://github.com/santry/json_tagged_logger
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.3.20
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: JSON Tagged Log Formatter
51
+ test_files: []