appydays 0.1.2 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eab73741b9b38b5e71b173859d4a21da866a0ed0d6ac2f2d336c535b60772cad
4
- data.tar.gz: c801ace1467194743e5c30c2a08b91f6f735fc61cde35cbc142cae35267eaef0
3
+ metadata.gz: 3ad0f0fbdd3901ecfa8fb3e8b6f265b07262ab16a3a8485bbfe91daa5ed31324
4
+ data.tar.gz: 788207faccc15f95a4564aa23832b36a8ca3232169d741f7a3daf838fb1ecccd
5
5
  SHA512:
6
- metadata.gz: dad5bd7318ec9bfe7cf73011da874787a9b38dd60305944ea3024473bf3e4eca3aaba59ad0878b6bd37f9bd65c4ffb941e88219a3bfbbd029567d1edc33ebb2d
7
- data.tar.gz: 8cfb4768365e9e1c5b1b0052be6b42246bfac6379e712903ef7ff76cf5e882e7d593c65757ac34673704f3b78b0800a25adc6dff578f5e64b75c2a37d6a0daa6
6
+ metadata.gz: b9e0bde21c13ff9681234285c8c996ac63ce3fe1d3e806a13aae86bc22095bc9713ac00af2a8e99e46143f1277e23e714e573c53f94fd9856cd23d8742075db6
7
+ data.tar.gz: 2e2fb67636db8b42ab6cb1dc0cf3c01da77ebc82f1641b32ba03e9e047fb18017214569c9ed65872f2abdb25cf3f6f3a020ca092a0528e763e62a47dda2f4f6f
@@ -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
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/job_logger"
5
+ require "sidekiq/util"
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
+ include Sidekiq::Util
14
+
15
+ Sidekiq.logger = self.logger
16
+
17
+ def call(item, _queue, &block)
18
+ start = self.now
19
+ tags = {
20
+ job_class: item["class"],
21
+ job_id: item["jid"],
22
+ thread_id: self.tid,
23
+ }
24
+ self.with_log_tags(tags) do
25
+ self.call_inner(start, &block)
26
+ end
27
+ end
28
+
29
+ # Override this to customize when jobs are logged at warn vs. info.
30
+ # We suggest you subclass SidekiqJobLogger, override this method,
31
+ # and return a value that is configured.
32
+ protected def slow_job_seconds
33
+ return 5.0
34
+ end
35
+
36
+ protected def call_inner(start)
37
+ yield
38
+ duration = self.elapsed(start)
39
+ log_method = duration >= self.slow_job_seconds ? :warn : :info
40
+ self.logger.send(log_method, "job_done", duration: duration)
41
+ rescue StandardError
42
+ # Do not log the error since it is probably a sidekiq retry error
43
+ self.logger.error("job_fail", duration: self.elapsed(start))
44
+ raise
45
+ end
46
+
47
+ protected def elapsed(start)
48
+ (self.now - start).round(3)
49
+ end
50
+
51
+ protected def now
52
+ return ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
53
+ end
54
+
55
+ def self.error_handler(ex, ctx)
56
+ # ctx looks like:
57
+ # {
58
+ # :context=>"Job raised exception",
59
+ # :job=>
60
+ # {"class"=>"App::Async::FailingJobTester",
61
+ # "args"=>
62
+ # [{"id"=>"e8e03571-9851-4daa-a801-a0b43282f317",
63
+ # "name"=>"app.test_failing_job",
64
+ # "payload"=>[true]}],
65
+ # "retry"=>true,
66
+ # "queue"=>"default",
67
+ # "jid"=>"cb00c4fe9b2f16b72797d35c",
68
+ # "created_at"=>1567811837.798969,
69
+ # "enqueued_at"=>1567811837.79901},
70
+ # :jobstr=>
71
+ # "{\"class\":\"App::Async::FailingJobTester\", <etc>"
72
+ # }
73
+ job = ctx[:job]
74
+ # If there was a connection error, you may end up with no job context.
75
+ # It's very difficult to test this usefully, so it's not tested.
76
+ unless job
77
+ self.logger.error("job_error_no_job", {}, ex)
78
+ return
79
+ end
80
+ self.logger.error(
81
+ "job_error",
82
+ {
83
+ job_class: job["class"],
84
+ job_args: job["args"],
85
+ job_retry: job["retry"],
86
+ job_queue: job["queue"],
87
+ job_id: job["jid"],
88
+ job_created_at: job["created_at"],
89
+ job_enqueued_at: job["enqueued_at"],
90
+ },
91
+ ex,
92
+ )
93
+ end
94
+
95
+ def self.death_handler(job, ex)
96
+ self.logger.error(
97
+ "job_retries_exhausted",
98
+ {
99
+ job_class: job["class"],
100
+ job_args: job["args"],
101
+ job_retry: job["retry"],
102
+ job_queue: job["queue"],
103
+ job_dead: job["dead"],
104
+ job_id: job["jid"],
105
+ job_created_at: job["created_at"],
106
+ job_enqueued_at: job["enqueued_at"],
107
+ job_error_message: job["error_message"],
108
+ job_error_class: job["error_class"],
109
+ job_failed_at: job["failed_at"],
110
+ job_retry_count: job["retry_count"],
111
+ },
112
+ ex,
113
+ )
114
+ end
115
+ 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.2.0"
5
5
  end
metadata CHANGED
@@ -1,170 +1,185 @@
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lithic Tech
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-02 00:00:00.000000000 Z
11
+ date: 2021-12-09 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
184
  '
170
185
  email:
@@ -172,21 +187,16 @@ 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
202
  metadata: {}
@@ -198,15 +208,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
208
  requirements:
199
209
  - - ">="
200
210
  - !ruby/object:Gem::Version
201
- version: 2.7.2
211
+ version: 2.7.0
202
212
  required_rubygems_version: !ruby/object:Gem::Requirement
203
213
  requirements:
204
214
  - - ">="
205
215
  - !ruby/object:Gem::Version
206
216
  version: '0'
207
217
  requirements: []
208
- rubygems_version: 3.1.4
218
+ rubygems_version: 3.1.6
209
219
  signing_key:
210
220
  specification_version: 4
211
- summary: Provides support for development best practices
221
+ summary: Provides support for env-based configuration, and common structured logging
222
+ capabilities
212
223
  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