datadog-logging 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: 034c7b67db9ffcb8e23f438c92055f30d91a2538a327ebf816ac6c9dcf976c5d
4
+ data.tar.gz: f33eaff58f093ff02ecd054053ef80e61d110b517136c8533bba548e96dfe132
5
+ SHA512:
6
+ metadata.gz: 424f1e50ea656741dbfbec79826bcf50608d5f4b2b7d756178bb450000e5a166871946b653b7843dc4fec042f97674d972e4428b77e785b035ccee3ece84f55e
7
+ data.tar.gz: bfd7aa46628923d2d98449ed9adfb8fa9e1799087246f89c5f9441c4db705a6566c1e7406030951745f1fe9e53dd36a5aa4ddb8a75a0d9cad32f95a66ccfbc74
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,250 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+
12
+ inherit_mode:
13
+ merge:
14
+ - Exclude
15
+ - Include
16
+
17
+ AllCops:
18
+ # Enable new cops by default
19
+ NewCops: enable
20
+
21
+ # Excluding most directories with generated files and directories with configuration files.
22
+ Exclude:
23
+ - 'bin'
24
+ - '.github'
25
+ - '**/Gemfile'
26
+ - '**/Guardfile'
27
+ - '**/Rakefile'
28
+ - 'node_modules/**/*'
29
+ - 'spec/**/*'
30
+ - 'sig/**/*'
31
+
32
+ TargetRubyVersion: 2.7
33
+
34
+ # Checks if String literals are using single quotes when no interpolation is required
35
+ Style/StringLiterals:
36
+ Enabled: true
37
+ EnforcedStyle: single_quotes
38
+ ConsistentQuotesInMultiline: false
39
+
40
+ # Checks if the quotes used for quoted symbols are single quotes when no interpolation is required
41
+ Style/QuotedSymbols:
42
+ Enabled: true
43
+ EnforcedStyle: same_as_string_literals
44
+
45
+ # This cop checks for uses of literal strings converted to a symbol where a literal symbol could be used instead.
46
+ Lint/SymbolConversion:
47
+ Enabled: true
48
+ EnforcedStyle: strict
49
+
50
+ # Useless cop. It checks for unnecessary safe navigations.
51
+ # Example:
52
+ # obj&.a && obj.b
53
+ # Triggers rubocop error: it requires to add safe navigation for "obj.b" call => "obj&.b".
54
+ # but it is not necessary. obj&.a will return nil if obj is nil, and it will stop
55
+ # execution of the operation because `&&` right part executes only when left part is truthy.
56
+ Lint/SafeNavigationConsistency:
57
+ Enabled: false
58
+
59
+ # Checks for places where keyword arguments can be used instead of boolean arguments when defining methods.
60
+ # Disabled because moving from default arguments to keywords is not that easy.
61
+ Style/OptionalBooleanParameter:
62
+ Enabled: false
63
+
64
+ # Checks for use of the lambda.(args) syntax.
65
+ # Disabled while the Ruby team has not voted on this.
66
+ Style/LambdaCall:
67
+ Enabled: false
68
+ EnforcedStyle: braces
69
+
70
+ # Checks for presence or absence of braces around hash literal as a last array item depending on configuration.
71
+ # Disabled because it would break a lot of permitted_params definitions
72
+ Style/HashAsLastArrayItem:
73
+ Enabled: false
74
+
75
+ # Checks for grouping of accessors in class and module bodies.
76
+ # Useless.
77
+ Style/AccessorGrouping:
78
+ Enabled: false
79
+
80
+ # Makes our lives happier: we don't need to disable it in each case/when method
81
+ # with more than 5 "when"s.
82
+ Metrics/CyclomaticComplexity:
83
+ Max: 10
84
+
85
+ # Commonly used screens these days easily fit more than 80 characters.
86
+ Layout/LineLength:
87
+ Max: 120
88
+
89
+ # Too short methods lead to extraction of single-use methods, which can make
90
+ # the code easier to read (by naming things), but can also clutter the class
91
+ Metrics/MethodLength:
92
+ Max: 25
93
+
94
+ # The guiding principle of classes is SRP, SRP can't be accurately measured by LoC
95
+ Metrics/ClassLength:
96
+ Max: 1500
97
+
98
+ # No space makes the method definition shorter and differentiates
99
+ # from a regular assignment.
100
+ Layout/SpaceAroundEqualsInParameterDefault:
101
+ EnforcedStyle: no_space
102
+
103
+ # We do not need to support Ruby 1.9, so this is good to use.
104
+ Style/SymbolArray:
105
+ Enabled: true
106
+
107
+ # Most readable form.
108
+ Layout/HashAlignment:
109
+ EnforcedHashRocketStyle: table
110
+ EnforcedColonStyle: table
111
+
112
+ # Mixing the styles looks just silly.
113
+ Style/HashSyntax:
114
+ EnforcedStyle: ruby19_no_mixed_keys
115
+
116
+ # has_key? and has_value? are far more readable than key? and value?
117
+ Style/PreferredHashMethods:
118
+ Enabled: false
119
+
120
+ # String#% is by far the least verbose and only object oriented variant.
121
+ Style/FormatString:
122
+ EnforcedStyle: percent
123
+
124
+ # Annotated or template are too verbose and rarely needed.
125
+ Style/FormatStringToken:
126
+ EnforcedStyle: unannotated
127
+
128
+ Style/CollectionMethods:
129
+ Enabled: true
130
+ PreferredMethods:
131
+ # inject seems more common in the community.
132
+ reduce: "inject"
133
+
134
+ # Either allow this style or don't. Marking it as safe with parenthesis
135
+ # is silly. Let's try to live without them for now.
136
+ Style/ParenthesesAroundCondition:
137
+ AllowSafeAssignment: false
138
+ Lint/AssignmentInCondition:
139
+ AllowSafeAssignment: false
140
+
141
+ # A specialized exception class will take one or more arguments and construct the message from it.
142
+ # So both variants make sense.
143
+ Style/RaiseArgs:
144
+ Enabled: false
145
+
146
+ # Indenting the chained dots beneath each other is not supported by this cop,
147
+ # see https://github.com/bbatsov/rubocop/issues/1633
148
+ Layout/MultilineOperationIndentation:
149
+ Enabled: false
150
+
151
+ # Fail is an alias of raise. Avoid aliases, it's more cognitive load for no gain.
152
+ # The argument that fail should be used to abort the program is wrong too,
153
+ # there's Kernel#abort for that.
154
+ Style/SignalException:
155
+ EnforcedStyle: only_raise
156
+
157
+ # Suppressing exceptions can be perfectly fine, and be it to avoid to
158
+ # explicitly type nil into the rescue since that's what you want to return,
159
+ # or suppressing LoadError for optional dependencies
160
+ Lint/SuppressedException:
161
+ Enabled: false
162
+
163
+ # { ... } for multi-line blocks is okay, follow Weirichs rule instead:
164
+ # https://web.archive.org/web/20140221124509/http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc
165
+ Style/BlockDelimiters:
166
+ Enabled: false
167
+
168
+ # do / end blocks should be used for side effects,
169
+ # methods that run a block for side effects and have
170
+ # a useful return value are rare, assign the return
171
+ # value to a local variable for those cases.
172
+ Style/MethodCalledOnDoEndBlock:
173
+ Enabled: true
174
+
175
+ # Enforcing the names of variables? To single letter ones? Just no.
176
+ Style/SingleLineBlockParams:
177
+ Enabled: false
178
+
179
+ # Shadowing outer local variables with block parameters is often useful
180
+ # to not reinvent a new name for the same thing, it highlights the relation
181
+ # between the outer variable and the parameter. The cases where it's actually
182
+ # confusing are rare, and usually bad for other reasons already, for example
183
+ # because the method is too long.
184
+ Lint/ShadowingOuterLocalVariable:
185
+ Enabled: false
186
+
187
+ # Check with yard instead.
188
+ Style/Documentation:
189
+ Enabled: false
190
+
191
+ # This is just silly. Calling the argument `other` in all cases makes no sense.
192
+ Naming/BinaryOperatorParameterName:
193
+ Enabled: false
194
+
195
+ # Disable frozen string
196
+ Style/FrozenStringLiteralComment:
197
+ Enabled: false
198
+
199
+ # Disable No ASCII char in comments
200
+ Style/AsciiComments:
201
+ Enabled: false
202
+
203
+ # Disable ordered Gems By ascii
204
+ Bundler/OrderedGems:
205
+ Enabled: false
206
+
207
+ # Change ABC max value
208
+ Metrics/AbcSize:
209
+ Max: 35
210
+
211
+ # Disable empty method in one line
212
+ Style/EmptyMethod:
213
+ EnforcedStyle: expanded
214
+
215
+ # Disable max height block
216
+ Metrics/BlockLength:
217
+ Enabled: true
218
+ Exclude:
219
+ - 'app/admin/**/*'
220
+ - 'lib/tasks/**/*'
221
+
222
+ # Checks if empty lines around the bodies of classes match the configuration.
223
+ Layout/EmptyLinesAroundClassBody:
224
+ EnforcedStyle: empty_lines
225
+ # Checks if empty lines around the bodies of modules match the configuration.
226
+ Layout/EmptyLinesAroundModuleBody:
227
+ EnforcedStyle: empty_lines
228
+
229
+ # Enforces the consistent usage of %-literal delimiters.
230
+ Style/PercentLiteralDelimiters:
231
+ PreferredDelimiters:
232
+ default: '()'
233
+ '%i': '[]'
234
+ '%I': '[]'
235
+ '%r': '{}'
236
+ '%w': '[]'
237
+ '%W': '[]'
238
+
239
+ # Unnecessary cop. In what universe "A || B && C" or "A && B || C && D" is ambiguous? looks
240
+ # like a cop for those who can't in boolean.
241
+ Lint/AmbiguousOperatorPrecedence:
242
+ Enabled: false
243
+
244
+ # Checks for simple usages of parallel assignment.
245
+ Style/ParallelAssignment:
246
+ Enabled: false
247
+
248
+ # Checks the style of children definitions at classes and modules.
249
+ Style/ClassAndModuleChildren:
250
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-09-20
4
+
5
+ - Initial release
6
+ - Add `Datadog::Logging::Logger#http_request` and `Datadog::Logging::Logger::UserFormatter`
7
+ - Add `Datadog::Logging::Formatter::JsonFormatter`
8
+ - Add `Datadog::Logging::Middleware::LogExceptions`
9
+ - Add `Datadog::Logging::Railtie`
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Modulotech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # Datadog::Logging
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/datadog/logging`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add datadog-logging
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install datadog-logging
16
+
17
+ ## Usage
18
+
19
+ ### In a pure-Ruby context
20
+
21
+ ```ruby
22
+ # frozen_string_literal: true
23
+
24
+ require 'logger'
25
+ require 'net/http'
26
+ require 'json'
27
+ require 'datadog/logging'
28
+
29
+ class ApiRequest
30
+
31
+ HEADERS = {
32
+ 'Content-Type' => 'application/json',
33
+ 'Accept' => 'application/json'
34
+ }.freeze
35
+
36
+ def add_stuff
37
+ logger = Datadog::Logging::Logger.new(Logger.new(STDOUT))
38
+
39
+ params = [
40
+ {
41
+ kind: 'thing'
42
+ }
43
+ ].to_json
44
+ request_id = "add_stuff_#{Time.now.strftime('%Y-%m-%dT%H:%M:%S%:z')}"
45
+
46
+ uri = URI('https://www.google.com')
47
+ hostname = uri.hostname
48
+ uri.path = '/api/stuffs'
49
+ request = Net::HTTP::Post.new(uri, HEADERS)
50
+ request.body = params
51
+ response = Net::HTTP.start(hostname) do |http|
52
+ http.request(request)
53
+ end
54
+
55
+ logger.http_request(method: 'POST', path: uri.path, controller: self.class.name, action: __method__,
56
+ params: params, headers: HEADERS, response: response, request_id: request_id,
57
+ user: { id: 1, name: 'Test TEST', email: 'test@test.test', role: 'test' })
58
+ response
59
+ end
60
+
61
+ def add_stuff_with_format
62
+ logger = Datadog::Logging::Logger.new(Logger.new(STDOUT, formatter: Datadog::Logging::Formatter::JsonFormatter))
63
+
64
+ params = [
65
+ {
66
+ kind: 'thing'
67
+ }
68
+ ].to_json
69
+ request_id = "add_stuff_#{Time.now.strftime('%Y-%m-%dT%H:%M:%S%:z')}"
70
+
71
+ uri = URI('https://www.google.com')
72
+ hostname = uri.hostname
73
+ uri.path = '/api/stuffs'
74
+ request = Net::HTTP::Post.new(uri, HEADERS)
75
+ request.body = params
76
+ response = Net::HTTP.start(hostname) do |http|
77
+ http.request(request)
78
+ end
79
+
80
+ logger.http_request(method: 'POST', path: uri.path, controller: self.class.name, action: __method__,
81
+ params: params, headers: HEADERS, response: response, request_id: request_id,
82
+ user: { id: 1, name: 'Test TEST', email: 'test@test.test', role: 'test' })
83
+ response
84
+ end
85
+
86
+ end
87
+
88
+ ApiRequest.new.add_stuff # => I, [2023-09-20T12:30:20.490190 #273] INFO -- : {:method=>"POST", :path=>"/api/stuffs", :format=>"application/json", :controller=>"ApiRequest", :action=>:add_stuff, :status=>"404", :request_id=>"add_stuff_2023-09-20T12:30:20+00:00", :headers=>{"Content-Type"=>"application/json", "Accept"=>"application/json"}, :params=>[{"kind"=>"thing"}], :response=>"Response body", :user=>{:id=>1, :username=>"Test TEST", :email=>"test@test.test", :role=>"test"}}
89
+ ApiRequest.new.add_stuff_with_format # => {"time":"2023-09-20 12:41:39 +0000","method":"POST","path":"/api/stuffs","format":"application/json","controller":"ApiRequest","action":"add_stuff_with_format","status":"404","request_id":"add_stuff_2023-09-20T12:41:39+00:00","headers":{"Content-Type":"application/json","Accept":"application/json"},"params":[{"kind":"thing"}],"response":"Response body","user":{"id":1,"username":"Test TEST","email":"test@test.test","role":"test"}}
90
+ ```
91
+
92
+ ### In a Rails context
93
+
94
+ ```ruby
95
+ # frozen_string_literal: true
96
+
97
+ # Considering you added the gem to your Gemfile with auto-require
98
+
99
+ class ApiRequest
100
+
101
+ HEADERS = {
102
+ 'Content-Type' => 'application/json',
103
+ 'Accept' => 'application/json'
104
+ }.freeze
105
+
106
+ def add_stuff
107
+ logger = Datadog::Logging::Logger.new(Rails.logger)
108
+
109
+ params = [
110
+ {
111
+ kind: 'thing'
112
+ }
113
+ ].to_json
114
+ request_id = "add_stuff_#{Time.now.strftime('%Y-%m-%dT%H:%M:%S%:z')}"
115
+
116
+ uri = URI('https://www.google.com')
117
+ hostname = uri.hostname
118
+ uri.path = '/api/stuffs'
119
+ request = Net::HTTP::Post.new(uri, HEADERS)
120
+ request.body = params
121
+ response = Net::HTTP.start(hostname) do |http|
122
+ http.request(request)
123
+ end
124
+
125
+ logger.http_request(method: 'POST', path: uri.path, controller: self.class.name, action: __method__,
126
+ params: params, headers: HEADERS, response: response, request_id: request_id,
127
+ user: { id: 1, name: 'Test TEST', email: 'test@test.test', role: 'test' })
128
+ response
129
+ end
130
+
131
+ end
132
+
133
+ # The Datadog::Logging::Formatter::JsonFormatter is automatically configured as the default log_formatter for Rails.
134
+ ApiRequest.new.add_stuff # => {"time":"2023-09-20T13:20:08.392+00:00","method":"POST","path":"/api/stuffs","format":"application/json","controller":"ApiRequest","action":"add_stuff","status":"404","request_id":"add_stuff_2023-09-20T13:20:08+00:00","headers":{"Content-Type":"application/json","Accept":"application/json"},"params":[{"kind":"thing"}],"response":"Response body","user":{"id":1,"username":"Test TEST","email":"test@test.test","role":"test"}}
135
+ ```
136
+
137
+ When the gem is required in a Rails context, the `Datadog::Logging::Middleware::LogExceptions` middleware is inserted
138
+ after the `ActionDispatch::DebugExceptions` middleware.
139
+
140
+ When an exception is raised (and not catched) in your application, it will be logged
141
+ (with formatter `Datadog::Logging::Formatter::JsonFormatter`) as:
142
+
143
+ ```json
144
+ {
145
+ "time": "2023-09-20T13:22:17.553+00:00",
146
+ "error": {
147
+ "kind": "RuntimeError",
148
+ "message": "test",
149
+ "stack": [
150
+ "(irb):35:in `\u003cmain\u003e'",
151
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/workspace.rb:119:in `eval'",
152
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/workspace.rb:119:in `evaluate'",
153
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/context.rb:502:in `evaluate'",
154
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:590:in `block (2 levels) in eval_input'",
155
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:779:in `signal_status'",
156
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:569:in `block in eval_input'",
157
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/ruby-lex.rb:267:in `block (2 levels) in each_top_level_statement'",
158
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/ruby-lex.rb:249:in `loop'",
159
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/ruby-lex.rb:249:in `block in each_top_level_statement'",
160
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/ruby-lex.rb:248:in `catch'",
161
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb/ruby-lex.rb:248:in `each_top_level_statement'",
162
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:568:in `eval_input'",
163
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:502:in `block in run'",
164
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:501:in `catch'",
165
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:501:in `run'",
166
+ "/usr/local/bundle/gems/irb-1.5.0/lib/irb.rb:419:in `start'",
167
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/commands/console/console_command.rb:70:in `start'",
168
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/commands/console/console_command.rb:19:in `start'",
169
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/commands/console/console_command.rb:102:in `perform'",
170
+ "/usr/local/bundle/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'",
171
+ "/usr/local/bundle/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'",
172
+ "/usr/local/bundle/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'",
173
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/command/base.rb:87:in `perform'",
174
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/command.rb:48:in `invoke'",
175
+ "/usr/local/bundle/gems/railties-7.0.4/lib/rails/commands.rb:18:in `\u003cmain\u003e'",
176
+ "/usr/local/bundle/gems/bootsnap-1.14.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'",
177
+ "/usr/local/bundle/gems/bootsnap-1.14.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'",
178
+ "bin/rails:4:in `\u003cmain\u003e'"
179
+ ]
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Log HTTP requests
185
+
186
+ You can change the log level used by `Datadog::Logging::Logger`:
187
+ ```ruby
188
+ logger = Datadog::Logging::Logger.new(Logger.new(STDOUT), log_level: :debug)
189
+ logger.log_level = :unknown
190
+ ```
191
+
192
+ You can change the way `Datadog::Logging::Logger` format the user:
193
+ ```ruby
194
+ user_formatter = ->(user) {
195
+ user.nil? ? 'unknown user' : %Q(<user id="#{user.id}"></user>)
196
+ }
197
+
198
+ class MyCustomFormatter
199
+
200
+ def call(user)
201
+ user.hash
202
+ end
203
+
204
+ end
205
+
206
+ logger = Datadog::Logging::Logger.new(Logger.new(STDOUT), user_formatter: user_formatter)
207
+ # => I, [2023-09-20T14:27:08.108397 #63] INFO -- : {:method=>"POST", :path=>"/api/stuffs", :format=>"application/json", :controller=>"Object", :action=>nil, :status=>"404", :request_id=>"add_stuff_2023-09-20T14:25:57+00:00", :headers=>{"Content-Type"=>"application/json", "Accept"=>"application/json"}, :params=>[{"kind"=>"thing"}], :response=>"Response body", :user=>"<user id=\"1\"></user>"}
208
+ logger.user_formatter = MyCustomFormatter.new
209
+ # => I, [2023-09-20T14:27:08.108397 #63] INFO -- : {:method=>"POST", :path=>"/api/stuffs", :format=>"application/json", :controller=>"Object", :action=>nil, :status=>"404", :request_id=>"add_stuff_2023-09-20T14:25:57+00:00", :headers=>{"Content-Type"=>"application/json", "Accept"=>"application/json"}, :params=>[{"kind"=>"thing"}], :response=>"Response body", :user=>2724891290560623810}
210
+ ```
211
+
212
+ ## Development
213
+
214
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
215
+
216
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
217
+
218
+ ## Contributing
219
+
220
+ Bug reports and pull requests are welcome on GitHub at https://github.com/modulotech/datadog-logging. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/datadog-logging/blob/master/CODE_OF_CONDUCT.md).
221
+
222
+ ## License
223
+
224
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
225
+
226
+ ## Code of Conduct
227
+
228
+ Everyone interacting in the Datadog::Logging project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/datadog-logging/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+
5
+ module Logging
6
+
7
+ module Formatter
8
+
9
+ class JsonFormatter
10
+
11
+ # @param [String] severity The Severity of the log message: should correspond to a constant in Logger::Severity
12
+ # @param [Object] time A Time instance representing when the message was logged.
13
+ # @param [Object] progname The #progname configured, or passed to the logger method.
14
+ # @param [Object] msg The _Object_ the user passed to the log message; not necessarily a String.
15
+ # @return [String (frozen)]
16
+ # @see Logger::Severity
17
+ def self.call(severity, time, progname, msg)
18
+ new.call(severity, time, progname, msg)
19
+ end
20
+
21
+ # @param [String] _severity The Severity of the log message: should correspond to a constant in Logger::Severity
22
+ # @param [Object] time A Time instance representing when the message was logged.
23
+ # @param [Object] progname The #progname configured, or passed to the logger method.
24
+ # @param [Object] msg The _Object_ the user passed to the log message; not necessarily a String.
25
+ # @return [String (frozen)]
26
+ # @see Logger::Severity
27
+ def call(_severity, time, progname, msg)
28
+ base_message = { time: time }
29
+
30
+ base_message[:progname] = progname unless progname.nil? || progname.empty?
31
+
32
+ message = case msg
33
+ when String
34
+ base_message.merge(message: msg)
35
+ when Exception
36
+ base_message.merge(error: {
37
+ kind: msg.class.name, message: msg.message, stack: msg.backtrace
38
+ })
39
+ when Hash
40
+ base_message.merge(msg)
41
+ else
42
+ base_message.merge(message: msg.inspect)
43
+ end
44
+
45
+ "#{message.to_json}\n"
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+
5
+ module Logging
6
+
7
+ class Logger
8
+
9
+ class UserFormatter
10
+
11
+ attr_reader :user
12
+
13
+ def initialize(user)
14
+ @user = user
15
+ end
16
+
17
+ def call
18
+ base = {
19
+ id: id,
20
+ username: username,
21
+ email: email,
22
+ role: role
23
+ }
24
+
25
+ result = base.compact
26
+
27
+ result.empty? ? nil : result
28
+ end
29
+
30
+ def self.call(user)
31
+ new(user).call
32
+ end
33
+
34
+ private
35
+
36
+ ID_KEYS = ['id', 'user_id', :id, :user_id].freeze
37
+ USERNAME_KEYS = ['name', 'username', :name, :username].freeze
38
+ EMAIL_KEYS = ['email', 'mail', :email, :mail].freeze
39
+ ROLE_KEYS = [
40
+ 'role', 'roles', 'user_role', 'user_roles', 'userrole', 'userroles',
41
+ :role, :roles, :user_role, :user_roles, :userrole, :userroles
42
+ ].freeze
43
+
44
+ %w[id username email role].each do |field|
45
+ define_method(field) do
46
+ keys = self.class.const_get("#{field.upcase}_KEYS")
47
+
48
+ case user
49
+ when nil
50
+ nil
51
+ when Hash
52
+ key = keys.find { |key| user[key] }
53
+
54
+ key.nil? ? nil : user[key]
55
+ else
56
+ key = keys.find { |key| user.respond_to?(key) }
57
+
58
+ key.nil? ? nil : user.public_send(key)
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ attr_accessor :logger
66
+ attr_accessor :user_formatter
67
+
68
+ def initialize(logger, log_level: :info, user_formatter: UserFormatter)
69
+ @logger = logger
70
+ @log_level = validate_log_level(log_level)
71
+ @user_formatter = user_formatter || UserFormatter
72
+ end
73
+
74
+ # rubocop:disable Metrics/ParameterLists
75
+ def http_request(method:, path:, controller:, action:, response:, headers: {}, params: {},
76
+ request_id: SecureRandom.uuid, user: nil)
77
+ logger.send(@log_level, {
78
+ method: method.to_s.upcase,
79
+ path: path,
80
+ format: headers['Accept'] || '*/*',
81
+ controller: controller,
82
+ action: action,
83
+ status: response.code,
84
+ request_id: request_id,
85
+ headers: headers,
86
+ params: parsed_params(params, headers),
87
+ response: parsed_response(response),
88
+ user: format_user(user)
89
+ })
90
+ end
91
+ # rubocop:enable Metrics/ParameterLists
92
+
93
+ private
94
+
95
+ def validate_log_level(level)
96
+ ::Logger::Severity.constants.include?(level.to_s.upcase.to_sym) ? level : :info
97
+ end
98
+
99
+ def parsed_params(params, headers)
100
+ # If body is JSON, we want to get it as Hash
101
+ if headers['Content-Type'] == 'application/json'
102
+ JSON.parse(params)
103
+ else
104
+ params
105
+ end
106
+ end
107
+
108
+ def parsed_response(response)
109
+ return nil if response.body.empty?
110
+
111
+ # If response is JSON, we want to get it as Hash
112
+ if response.header['Content-Type']&.match(%r{application/json})
113
+ begin
114
+ JSON.parse(response.body)
115
+ rescue StandardError
116
+ nil
117
+ end
118
+ else
119
+ response.body
120
+ end
121
+ end
122
+
123
+ def format_user(user)
124
+ @user_formatter.call(user)
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+
5
+ module Logging
6
+
7
+ module Middleware
8
+
9
+ class LogExceptions < ActionDispatch::DebugExceptions
10
+
11
+ private
12
+
13
+ def log_error(request, wrapper)
14
+ logger = logger(request)
15
+
16
+ return unless logger
17
+ return if !log_rescued_responses?(request) && wrapper.rescue_response?
18
+
19
+ exception = wrapper.exception
20
+
21
+ logger.fatal({
22
+ error: {
23
+ kind: exception.class.name, message: exception.message, stack: exception.backtrace
24
+ }
25
+ })
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'middleware/log_exceptions'
4
+
5
+ module Datadog
6
+
7
+ module Logging
8
+
9
+ class Railtie < ::Rails::Railtie
10
+
11
+ # Update and add gems before we load the configuration
12
+ config.before_configuration do
13
+ Rails.configuration.middleware.insert_after(ActionDispatch::DebugExceptions, Middleware::LogExceptions)
14
+ Rails.configuration.log_formatter = Formatter::JsonFormatter
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+
5
+ module Logging
6
+
7
+ VERSION = '0.1.0'
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'logging/version'
4
+ require_relative 'logging/railtie' if defined?(Rails::Railtie)
5
+ require_relative 'logging/logger'
6
+ require_relative 'logging/formatter/json_formatter'
@@ -0,0 +1,11 @@
1
+ module Datadog
2
+ module Logging
3
+ module Formatter
4
+ class JsonFormatter
5
+ def self.call: (String severity, untyped time, untyped progname, untyped msg) -> String
6
+
7
+ def call: (String severity, untyped time, untyped progname, untyped msg) -> String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,40 @@
1
+ interface _Callable
2
+ def call: (untyped) -> untyped
3
+ end
4
+
5
+ module Datadog
6
+ module Logging
7
+ module Logger
8
+ class UserFormatter
9
+ EMAIL_KEYS: Array[String | Symbol]
10
+ ID_KEYS: Array[String | Symbol]
11
+ ROLE_KEYS: Array[String | Symbol]
12
+ USERNAME_KEYS: Array[String | Symbol]
13
+
14
+ def self.call: (untyped user) -> untyped
15
+
16
+ attr_reader user: untyped
17
+
18
+ def call: -> untyped
19
+ end
20
+
21
+ @log_level: String | Symbol
22
+
23
+ attr_accessor logger: ::Logger
24
+ attr_accessor user_formatter: _Callable
25
+
26
+ def http_request: (method: String | Symbol, path: String, controller: String, action: String, response: untyped,
27
+ ?headers: Hash[String, untyped], ?params: untyped, ?request_id: String, ?user: untyped) -> Integer
28
+
29
+ private
30
+
31
+ def format_user: (untyped user) -> untyped
32
+
33
+ def parsed_params: (untyped params, Hash[String, untyped] headers) -> untyped
34
+
35
+ def parsed_response: (untyped response) -> untyped
36
+
37
+ def validate_log_level: (String | Symbol level) -> (String | Symbol)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ module Datadog
2
+ module Logging
3
+ module Middleware
4
+ class LogExceptions < ActionDispatch::DebugExceptions
5
+ private
6
+
7
+ def log_error: (untyped request, untyped wrapper) -> Integer
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Datadog
2
+ module Logging
3
+ VERSION: String
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datadog-logging
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthieu CIAPPARA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-09-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - ciappa_m@modulotech.fr
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/datadog/logging.rb
28
+ - lib/datadog/logging/formatter/json_formatter.rb
29
+ - lib/datadog/logging/logger.rb
30
+ - lib/datadog/logging/middleware/log_exceptions.rb
31
+ - lib/datadog/logging/railtie.rb
32
+ - lib/datadog/logging/version.rb
33
+ - sig/datadog/logging.rbs
34
+ - sig/datadog/logging/formatter/json_formatter.rbs
35
+ - sig/datadog/logging/logger.rbs
36
+ - sig/datadog/logging/middleware/log_exceptions.rbs
37
+ homepage: https://github.com/moduloTech/datadog-logging
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ homepage_uri: https://github.com/moduloTech/datadog-logging
42
+ source_code_uri: https://github.com/moduloTech/datadog-logging
43
+ changelog_uri: https://github.com/moduloTech/datadog-logging/blob/master/CHANGELOG.md
44
+ rubygems_mfa_required: 'true'
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.7.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.4.10
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Methods, class and formatters to log efficiently in Datadog
64
+ test_files: []