appydays 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eab73741b9b38b5e71b173859d4a21da866a0ed0d6ac2f2d336c535b60772cad
4
- data.tar.gz: c801ace1467194743e5c30c2a08b91f6f735fc61cde35cbc142cae35267eaef0
3
+ metadata.gz: 6f235dd9ffc485d78888e9cc325dc090dba1f40b8cf4a2cb9e24df2074149520
4
+ data.tar.gz: a34bed5f98cf79bad2401c94b254a41deb28f7165fb86e5378ec96bec8913b9d
5
5
  SHA512:
6
- metadata.gz: dad5bd7318ec9bfe7cf73011da874787a9b38dd60305944ea3024473bf3e4eca3aaba59ad0878b6bd37f9bd65c4ffb941e88219a3bfbbd029567d1edc33ebb2d
7
- data.tar.gz: 8cfb4768365e9e1c5b1b0052be6b42246bfac6379e712903ef7ff76cf5e882e7d593c65757ac34673704f3b78b0800a25adc6dff578f5e64b75c2a37d6a0daa6
6
+ metadata.gz: 4e62429beede3533e76c7f245ca3b653c5b702f5fbd64571f7381a49c6975043e8d1c8a37f60a47c3aed7422ec83a8408da66852a47ca3c28840852b2e3feac0
7
+ data.tar.gz: edb5ebdb0c6b073e8b6c8d4fdbd14d548592fbea901a6e6c0cfc7ef785231158ce94ff8bbbd5496637414c17239e47dd633e65db325c5316e8889d95d07fd564
@@ -132,6 +132,7 @@ module Appydays::Configurable
132
132
  return ->(v) { v.to_s } if default.nil? || default.is_a?(String)
133
133
  return ->(v) { v.to_i } if default.is_a?(Integer)
134
134
  return ->(v) { v.to_f } if default.is_a?(Float)
135
+ return ->(v) { v.to_sym } if default.is_a?(Symbol)
135
136
  return ->(v) { v.casecmp("true").zero? } if [TrueClass, FalseClass].include?(default.class)
136
137
  raise TypeError, "Uncoercable type %p" % [default.class]
137
138
  end
@@ -17,7 +17,11 @@ class Sequel::Database
17
17
  log_each(
18
18
  :info,
19
19
  proc { args ? "#{message}; #{args.inspect}" : message },
20
- proc { ["sequel_log", {message: message, args: args}] },
20
+ proc do
21
+ o = {message: message}
22
+ o[:args] = args unless args.nil?
23
+ ["sequel_log", o]
24
+ end,
21
25
  )
22
26
  end
23
27
 
@@ -28,7 +32,7 @@ class Sequel::Database
28
32
  log_each(
29
33
  lwd && (duration >= lwd) ? :warn : sql_log_level,
30
34
  proc { "(#{'%0.6fs' % duration}) #{message}" },
31
- proc { ["sequel_query", {duration: duration, query: message}] },
35
+ proc { ["sequel_query", {duration: duration * 1000, query: message}] },
32
36
  )
33
37
  end
34
38
 
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/version"
5
+ require "sidekiq/job_logger"
6
+
7
+ require "appydays/loggable"
8
+ require "appydays/configurable"
9
+
10
+ class Appydays::Loggable::SidekiqJobLogger < Sidekiq::JobLogger
11
+ include Appydays::Configurable
12
+ include Appydays::Loggable
13
+ begin
14
+ require "sidekiq/util"
15
+ include Sidekiq::Util
16
+ rescue LoadError
17
+ require "sidekiq/component"
18
+ include Sidekiq::Component
19
+ end
20
+
21
+ Sidekiq.logger = self.logger
22
+
23
+ def call(item, _queue, &block)
24
+ start = self.now
25
+ tags = {
26
+ job_class: item["class"],
27
+ job_id: item["jid"],
28
+ thread_id: self.tid,
29
+ }
30
+ self.with_log_tags(tags) do
31
+ self.call_inner(start, &block)
32
+ end
33
+ end
34
+
35
+ # Override this to customize when jobs are logged at warn vs. info.
36
+ # We suggest you subclass SidekiqJobLogger, override this method,
37
+ # and return a value that is configured.
38
+ protected def slow_job_seconds
39
+ return 5.0
40
+ end
41
+
42
+ protected def call_inner(start)
43
+ yield
44
+ duration = self.elapsed(start)
45
+ log_method = duration >= self.slow_job_seconds ? :warn : :info
46
+ self.logger.send(log_method, "job_done", duration: duration * 1000)
47
+ rescue StandardError
48
+ # Do not log the error since it is probably a sidekiq retry error
49
+ self.logger.error("job_fail", duration: self.elapsed(start) * 1000)
50
+ raise
51
+ end
52
+
53
+ protected def elapsed(start)
54
+ (self.now - start).round(3)
55
+ end
56
+
57
+ protected def now
58
+ return ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
59
+ end
60
+
61
+ def self.error_handler(ex, ctx)
62
+ # ctx looks like:
63
+ # {
64
+ # :context=>"Job raised exception",
65
+ # :job=>
66
+ # {"class"=>"App::Async::FailingJobTester",
67
+ # "args"=>
68
+ # [{"id"=>"e8e03571-9851-4daa-a801-a0b43282f317",
69
+ # "name"=>"app.test_failing_job",
70
+ # "payload"=>[true]}],
71
+ # "retry"=>true,
72
+ # "queue"=>"default",
73
+ # "jid"=>"cb00c4fe9b2f16b72797d35c",
74
+ # "created_at"=>1567811837.798969,
75
+ # "enqueued_at"=>1567811837.79901},
76
+ # :jobstr=>
77
+ # "{\"class\":\"App::Async::FailingJobTester\", <etc>"
78
+ # }
79
+ job = ctx[:job]
80
+ # If there was a connection error, you may end up with no job context.
81
+ # It's very difficult to test this usefully, so it's not tested.
82
+ unless job
83
+ self.logger.error("job_error_no_job", {}, ex)
84
+ return
85
+ end
86
+ self.logger.error(
87
+ "job_error",
88
+ {
89
+ job_class: job["class"],
90
+ job_args: job["args"],
91
+ job_retry: job["retry"],
92
+ job_queue: job["queue"],
93
+ job_id: job["jid"],
94
+ job_created_at: job["created_at"],
95
+ job_enqueued_at: job["enqueued_at"],
96
+ },
97
+ ex,
98
+ )
99
+ end
100
+
101
+ def self.death_handler(job, ex)
102
+ self.logger.error(
103
+ "job_retries_exhausted",
104
+ {
105
+ job_class: job["class"],
106
+ job_args: job["args"],
107
+ job_retry: job["retry"],
108
+ job_queue: job["queue"],
109
+ job_dead: job["dead"],
110
+ job_id: job["jid"],
111
+ job_created_at: job["created_at"],
112
+ job_enqueued_at: job["enqueued_at"],
113
+ job_error_message: job["error_message"],
114
+ job_error_class: job["error_class"],
115
+ job_failed_at: job["failed_at"],
116
+ job_retry_count: job["retry_count"],
117
+ },
118
+ ex,
119
+ )
120
+ end
121
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appydays
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,196 +1,207 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydays
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lithic Tech
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-02 00:00:00.000000000 Z
11
+ date: 2022-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.7'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: semantic_logger
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '4.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '4.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rack
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '3.10'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '3.10'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec-core
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '3.10'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '3.10'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec-json_expectations
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '2.2'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '2.2'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rubocop
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '1.11'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '1.11'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop-performance
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: '1.10'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: '1.10'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop-rake
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: '0.5'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '0.5'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-sequel
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
143
+ - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
145
+ version: '0.2'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ">="
150
+ - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
152
+ version: '0.2'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: sequel
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - ">="
157
+ - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: '0'
159
+ version: '5.0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - ">="
164
+ - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: '0'
167
- description: 'appydays provides support for logging and handling environment variables
166
+ version: '5.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: sidekiq
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '6.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '6.0'
181
+ description: 'appydays provides support for env-based configuration, and common structured
182
+ logging capabilities
168
183
 
169
- '
170
- email:
184
+ '
185
+ email:
171
186
  executables: []
172
187
  extensions: []
173
188
  extra_rdoc_files: []
174
189
  files:
175
- - README.md
176
- - lib/appydays.rb
177
190
  - lib/appydays/configurable.rb
178
191
  - lib/appydays/dotenviable.rb
179
192
  - lib/appydays/loggable.rb
180
193
  - lib/appydays/loggable/request_logger.rb
181
194
  - lib/appydays/loggable/sequel_logger.rb
195
+ - lib/appydays/loggable/sidekiq_job_logger.rb
182
196
  - lib/appydays/loggable/spec_helpers.rb
183
197
  - lib/appydays/spec_helpers.rb
184
198
  - lib/appydays/version.rb
185
- - spec/appydays/configurable_spec.rb
186
- - spec/appydays/dotenviable_spec.rb
187
- - spec/appydays/loggable_spec.rb
188
- - spec/spec_helper.rb
189
- homepage:
199
+ homepage: https://github.com/lithictech/appydays
190
200
  licenses:
191
201
  - MIT
192
- metadata: {}
193
- post_install_message:
202
+ metadata:
203
+ rubygems_mfa_required: 'true'
204
+ post_install_message:
194
205
  rdoc_options: []
195
206
  require_paths:
196
207
  - lib
@@ -198,15 +209,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
209
  requirements:
199
210
  - - ">="
200
211
  - !ruby/object:Gem::Version
201
- version: 2.7.2
212
+ version: 2.7.0
202
213
  required_rubygems_version: !ruby/object:Gem::Requirement
203
214
  requirements:
204
215
  - - ">="
205
216
  - !ruby/object:Gem::Version
206
217
  version: '0'
207
218
  requirements: []
208
- rubygems_version: 3.1.4
209
- signing_key:
219
+ rubygems_version: 3.1.6
220
+ signing_key:
210
221
  specification_version: 4
211
- summary: Provides support for development best practices
222
+ summary: Provides support for env-based configuration, and common structured logging
223
+ capabilities
212
224
  test_files: []
data/README.md DELETED
File without changes
data/lib/appydays.rb DELETED
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Appydays
4
- VERSION = "0.1.0"
5
- end
@@ -1,197 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Appydays::Configurable do
4
- describe "configurable" do
5
- it "raises if no block is given" do
6
- expect do
7
- Class.new do
8
- include Appydays::Configurable
9
- configurable(:hello)
10
- end
11
- end.to raise_error(LocalJumpError)
12
- end
13
-
14
- describe "setting" do
15
- it "creates an attr accessor with the given name and default value" do
16
- cls = Class.new do
17
- include Appydays::Configurable
18
- configurable(:hello) do
19
- setting :knob, "zero"
20
- end
21
- end
22
- expect(cls).to have_attributes(knob: "zero")
23
- end
24
-
25
- it "pulls the value from the environment" do
26
- ENV["ENVTEST_KNOB"] = "one"
27
- cls = Class.new do
28
- include Appydays::Configurable
29
- configurable(:envtest) do
30
- setting :knob, "zero"
31
- end
32
- end
33
- expect(cls).to have_attributes(knob: "one")
34
- end
35
-
36
- it "can use a custom environment key" do
37
- ENV["OTHER_KNOB"] = "two"
38
- cls = Class.new do
39
- include Appydays::Configurable
40
- configurable(:hello) do
41
- setting :knob, "zero", key: "OTHER_KNOB"
42
- end
43
- end
44
- expect(cls).to have_attributes(knob: "two")
45
- end
46
-
47
- it "can convert the value given the converter" do
48
- ENV["CONVTEST_KNOB"] = "0"
49
- cls = Class.new do
50
- include Appydays::Configurable
51
- configurable(:convtest) do
52
- setting :knob, "", convert: ->(v) { v + v }
53
- end
54
- end
55
- expect(cls).to have_attributes(knob: "00")
56
- end
57
-
58
- it "does not run the converter if the default is used" do
59
- cls = Class.new do
60
- include Appydays::Configurable
61
- configurable(:hello) do
62
- setting :knob, "0", convert: ->(v) { v + v }
63
- end
64
- end
65
- expect(cls).to have_attributes(knob: "0")
66
- end
67
-
68
- it "can use a nil default" do
69
- cls = Class.new do
70
- include Appydays::Configurable
71
- configurable(:hello) do
72
- setting :knob, nil
73
- end
74
- end
75
- expect(cls).to have_attributes(knob: nil)
76
- end
77
-
78
- it "converts strings to floats if the default is a float" do
79
- ENV["FLOATTEST_KNOB"] = "3.2"
80
- cls = Class.new do
81
- include Appydays::Configurable
82
- configurable(:floattest) do
83
- setting :knob, 1.5
84
- end
85
- end
86
- expect(cls).to have_attributes(knob: 3.2)
87
- end
88
-
89
- it "converts strings to integers if the default is an integer" do
90
- ENV["INTTEST_KNOB"] = "5"
91
- cls = Class.new do
92
- include Appydays::Configurable
93
- configurable(:inttest) do
94
- setting :knob, 2
95
- end
96
- end
97
- expect(cls).to have_attributes(knob: 5)
98
- end
99
-
100
- it "can coerce strings to booleans" do
101
- ENV["BOOLTEST_KNOB"] = "TRue"
102
- cls = Class.new do
103
- include Appydays::Configurable
104
- configurable(:booltest) do
105
- setting :knob, false
106
- end
107
- end
108
- expect(cls).to have_attributes(knob: true)
109
- end
110
-
111
- it "does not run the converter when using the accessor" do
112
- cls = Class.new do
113
- include Appydays::Configurable
114
- configurable(:booltest) do
115
- setting :knob, 5
116
- end
117
- end
118
- cls.knob = "5"
119
- expect(cls).to have_attributes(knob: "5")
120
- end
121
-
122
- it "coalesces an empty string to nil" do
123
- cls = Class.new do
124
- include Appydays::Configurable
125
- configurable(:hello) do
126
- setting :knob, ""
127
- end
128
- end
129
- expect(cls).to have_attributes(knob: nil)
130
- end
131
-
132
- it "errors if the default value is not a supported type" do
133
- expect do
134
- Class.new do
135
- include Appydays::Configurable
136
- configurable(:hello) do
137
- setting :knob, []
138
- end
139
- end
140
- end.to raise_error(TypeError)
141
- end
142
-
143
- it "runs a side effect" do
144
- side_effect = []
145
- Class.new do
146
- include Appydays::Configurable
147
- configurable(:hello) do
148
- setting :knob, "zero", side_effect: ->(s) { side_effect << s }
149
- end
150
- end
151
- expect(side_effect).to contain_exactly("zero")
152
- end
153
- end
154
-
155
- it "can reset settings" do
156
- cls = Class.new do
157
- include Appydays::Configurable
158
- configurable(:hello) do
159
- setting :knob, 1
160
- end
161
- end
162
- cls.knob = 5
163
- expect(cls).to have_attributes(knob: 5)
164
- cls.reset_configuration
165
- expect(cls).to have_attributes(knob: 1)
166
- end
167
-
168
- it "runs after_configure hooks after configuration" do
169
- side_effect = []
170
- Class.new do
171
- include Appydays::Configurable
172
- configurable(:hello) do
173
- setting :knob, 1
174
- after_configured do
175
- side_effect << self.knob
176
- end
177
- end
178
- end
179
- expect(side_effect).to contain_exactly(1)
180
- end
181
-
182
- it "can run after_configured hooks explicitly" do
183
- side_effect = []
184
- cls = Class.new do
185
- include Appydays::Configurable
186
- configurable(:hello) do
187
- setting :knob, 1
188
- after_configured do
189
- side_effect << self.knob
190
- end
191
- end
192
- end
193
- cls.run_after_configured_hooks
194
- expect(side_effect).to contain_exactly(1, 1)
195
- end
196
- end
197
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Appydays::Dotenviable do
4
- it "loads env files using RACK_ENV" do
5
- expect(Dotenv).to receive(:load).with(".env.foo.local", ".env.foo", ".env")
6
- described_class.load(env: {"RACK_ENV" => "foo"})
7
- end
8
-
9
- it "loads env files using the explicit env" do
10
- expect(Dotenv).to receive(:load).with(".env.bar.local", ".env.bar", ".env")
11
- described_class.load(rack_env: "bar")
12
- end
13
-
14
- it "loads env files with the given default env if no RACK_ENV is defined" do
15
- expect(Dotenv).to receive(:load).with(".env.bar.local", ".env.bar", ".env")
16
- described_class.load(default_rack_env: "bar", env: {})
17
- end
18
-
19
- it "loads env files with RACK_ENV rather than the default, if RACK_ENV is defined" do
20
- expect(Dotenv).to receive(:load).with(".env.bar.local", ".env.bar", ".env")
21
- described_class.load(default_rack_env: "foo", env: {"RACK_ENV" => "bar"})
22
- end
23
-
24
- it "reapplies the original port if one was not loaded" do
25
- env = {"PORT" => "123"}
26
- expect(Dotenv).to receive(:load)
27
- described_class.load(env: env)
28
- expect(env).to include("PORT" => "123")
29
- end
30
-
31
- it "does not reapply the original port if one was loaded" do
32
- env = {"PORT" => "123"}
33
- expect(Dotenv).to receive(:load) { env["PORT"] = "456" }
34
- described_class.load(env: env)
35
- expect(env).to include("PORT" => "456")
36
- end
37
- end
@@ -1,184 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "appydays/loggable/request_logger"
4
-
5
- RSpec.describe Appydays::Loggable do
6
- it "can set the default log level" do
7
- expect(SemanticLogger).to receive(:default_level=).with("trace")
8
- described_class.default_level = "trace"
9
- end
10
-
11
- it "can look up the logger for an object for a non-Loggable" do
12
- cls = Class.new
13
- cls_logger = described_class[cls]
14
- inst_logger = described_class[cls.new]
15
- expect(cls_logger).to be_a(SemanticLogger::Logger)
16
- expect(inst_logger).to be_a(SemanticLogger::Logger)
17
- expect(cls_logger.name).to eq(inst_logger.name)
18
- end
19
-
20
- it "can look up the logger for an object for a Loggable" do
21
- cls = Class.new do
22
- include Appydays::Loggable
23
- end
24
- cls_logger = described_class[cls]
25
- inst = cls.new
26
- inst_logger = described_class[inst]
27
- expect(cls_logger).to be(inst_logger)
28
- expect(cls.logger).to be(inst.logger)
29
- end
30
-
31
- it "adds logger methods" do
32
- cls = Class.new do
33
- include Appydays::Loggable
34
- end
35
- inst = cls.new
36
- expect(cls.logger).to be_a(SemanticLogger::Logger)
37
- expect(inst.logger).to be_a(SemanticLogger::Logger)
38
- end
39
-
40
- describe "custom formatting" do
41
- it "combines :payload, :tags, and :named_tags into :context" do
42
- logger1 = described_class["spec-helper-test"]
43
-
44
- lines = capture_logs_from(logger1, formatter: :json) do
45
- SemanticLogger.tagged("tag1", "tag2") do
46
- SemanticLogger.named_tagged(nt1: 1, nt2: 2) do
47
- logger1.error("hello", opt1: 1, opt2: 2)
48
- end
49
- end
50
- end
51
- j = JSON.parse(lines[0])
52
- expect(j).to include("context")
53
- expect(j["context"]).to eq(
54
- "_tags" => ["tag1", "tag2"],
55
- "nt1" => 1,
56
- "nt2" => 2,
57
- "opt1" => 1,
58
- "opt2" => 2,
59
- )
60
- end
61
- end
62
-
63
- describe "spec helpers" do
64
- logger1 = described_class["spec-helper-test"]
65
-
66
- it "can capture log lines to a logger" do
67
- lines = capture_logs_from(logger1) do
68
- logger1.error("hello there")
69
- end
70
- expect(lines).to have_a_line_matching(/hello there/)
71
- end
72
-
73
- it "can capture log lines to multiple loggers" do
74
- lines = capture_logs_from(logger1) do
75
- logger1.error("hello there")
76
- end
77
- expect(lines).to have_a_line_matching(/hello there/)
78
- end
79
-
80
- it "can filter logs below a level" do
81
- lines = capture_logs_from(logger1, level: :error) do
82
- logger1.warn("hello there")
83
- end
84
- expect(lines).to be_empty
85
- end
86
-
87
- it "can specify the formatter" do
88
- lines = capture_logs_from(logger1, formatter: :json) do
89
- logger1.warn("hello there")
90
- end
91
- expect(lines).to have_a_line_matching(/"message":"hello there"/)
92
-
93
- lines = capture_logs_from(logger1, formatter: :color) do
94
- logger1.warn("hello there")
95
- end
96
- expect(lines).to have_a_line_matching(/-- hello there/)
97
- end
98
-
99
- it "sets and restores the level of all appenders" do
100
- logger1.level = :info
101
- other_appender = SemanticLogger.add_appender(io: StringIO.new, level: :trace)
102
- capture_logs_from(logger1, level: :trace) do
103
- expect(logger1.level).to eq(:trace)
104
- expect(other_appender.level).to eq(:fatal)
105
- end
106
- expect(logger1.level).to eq(:info)
107
- expect(other_appender.level).to eq(:trace)
108
- SemanticLogger.remove_appender(other_appender)
109
- end
110
- end
111
-
112
- describe Appydays::Loggable::RequestLogger do
113
- def run_app(app, opts: {}, loggers: [], env: {}, cls: Appydays::Loggable::RequestLogger)
114
- rl = cls.new(app, **opts.merge(reraise: false))
115
- return capture_logs_from(loggers << rl.logger, formatter: :json) do
116
- _, _, body = rl.call(env)
117
- body&.close
118
- end
119
- end
120
-
121
- it "logs info about the request" do
122
- lines = run_app(proc { [200, {}, ""] })
123
- expect(lines).to have_a_line_matching(/"message":"request_finished".*"response_status":200/)
124
-
125
- lines = run_app(proc { [400, {}, ""] })
126
- expect(lines).to have_a_line_matching(/"message":"request_finished".*"response_status":400/)
127
- end
128
-
129
- it "logs at 599 (or configured value) if something errors" do
130
- lines = run_app(proc { raise "testing error" })
131
- expect(lines).to have_a_line_matching(/"level":"error".*"response_status":599/)
132
- expect(lines).to have_a_line_matching(/"message":"testing error"/)
133
- end
134
-
135
- it "logs slow queries at warn" do
136
- lines = run_app(proc { [200, {}, ""] }, opts: {slow_request_seconds: 0})
137
- expect(lines).to have_a_line_matching(/"level":"warn".*"response_status":200/)
138
- end
139
-
140
- it "logs errors at error" do
141
- lines = run_app(proc { [504, {}, ""] }, opts: {slow_request_seconds: 0})
142
- expect(lines).to have_a_line_matching(/"level":"error".*"response_status":504/)
143
- end
144
-
145
- it "adds tags around the execution of the request" do
146
- logger = SemanticLogger["testlogger"]
147
- lines = run_app(proc do
148
- logger.info("check for tags")
149
- [200, {}, ""]
150
- end,
151
- opts: {slow_request_seconds: 0}, loggers: [logger],)
152
- expect(lines).to have_a_line_matching(/"message":"check for tags".*"request_method":/)
153
- end
154
-
155
- it "adds subclass tags" do
156
- ReqLogger = Class.new(Appydays::Loggable::RequestLogger) do
157
- def request_tags(env)
158
- return {my_header_tag: env["HTTP_MY_HEADER"]}
159
- end
160
- end
161
- lines = run_app(proc { [200, {}, ""] }, env: {"HTTP_MY_HEADER" => "myval"}, cls: ReqLogger)
162
- expect(lines).to have_a_line_matching(/"my_header_tag":"myval"/)
163
- end
164
-
165
- it "adds a request id" do
166
- lines = run_app(proc { [200, {}, ""] })
167
- expect(lines).to have_a_line_matching(/"request_id":"[0-9a-z]{8}-/)
168
- end
169
-
170
- it "reads a trace id from headers" do
171
- lines = run_app(proc { [200, {}, ""] }, env: {"HTTP_TRACE_ID" => "123xyz"})
172
- expect(lines).to have_a_line_matching(/"trace_id":"123xyz"/)
173
- end
174
-
175
- it "sets the trace ID header if not set" do
176
- env = {}
177
- lines = run_app(proc do
178
- expect(env).to(include("HTTP_TRACE_ID"))
179
- [200, {}, ""]
180
- end, env: env,)
181
- expect(lines).to have_a_line_matching(/"trace_id":"[0-9a-z]{8}-/)
182
- end
183
- end
184
- end
data/spec/spec_helper.rb DELETED
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # See https://github.com/eliotsykes/rspec-rails-examples/blob/master/spec/spec_helper.rb
4
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
5
- #
6
- require "appydays/dotenviable"
7
- Appydays::Dotenviable.load(default_rack_env: "test")
8
-
9
- require "rspec"
10
- require "rspec/json_expectations"
11
- require "appydays/loggable/spec_helpers"
12
- require "appydays/spec_helpers"
13
- require "appydays/configurable"
14
-
15
- RSpec.configure do |config|
16
- # config.full_backtrace = true
17
-
18
- RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 600
19
-
20
- config.expect_with :rspec do |expectations|
21
- expectations.include_chain_clauses_in_custom_matcher_descriptions = true
22
- end
23
-
24
- config.mock_with :rspec do |mocks|
25
- mocks.verify_partial_doubles = true
26
- end
27
-
28
- config.order = :random
29
- Kernel.srand config.seed
30
-
31
- config.filter_run :focus
32
- config.run_all_when_everything_filtered = true
33
- config.disable_monkey_patching!
34
- config.default_formatter = "doc" if config.files_to_run.one?
35
-
36
- config.include(Appydays::Loggable::SpecHelpers)
37
- config.include(Appydays::SpecHelpers)
38
- end