pliny 0.23.0 → 0.24.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
  SHA1:
3
- metadata.gz: 683200fbb66f5d6b6961b584480ce759a6ed08d8
4
- data.tar.gz: 49cc9f605818c73a711c442bc3a5315dd3c75ea4
3
+ metadata.gz: 6b59272bbe834eaf737b9ffe3e4769e54f8897cf
4
+ data.tar.gz: 13bfa08975ac6f65c74a686e54cf22608bbab639
5
5
  SHA512:
6
- metadata.gz: 117170ba5de6e6bacc2c6d02c15dae9de1425fdb8be491bac7d60cf123161ce0a15fb77801b0eb83ed14067349adc59c8a4a6e84b8f5bc75e50de68870d55a7f
7
- data.tar.gz: 8e94bbf374d67232644e892ede8c2d9c919e58176172158b48be927a106d0502410c4bde61b3a1957327bffdda37796052148e5b36ce8fe927e405f4cefe701a
6
+ metadata.gz: 1d76b1d6abaf678044111133e3fcb4aa587b88e87c866d8e60adbc03543e77f6e1cb959609eea9b8bb75931ea6f206c49c8447852b4035ca00a6360bd4408e0c
7
+ data.tar.gz: 8a4e54331f8642b58176358fb3d57845cf623dc93a3d8e0bdd301ea853a84ee29db9c2d1dc5567b6db6cf3fc5f920d8348b419a09f0b3a4ae49593597110b4d6
data/lib/pliny.rb CHANGED
@@ -2,6 +2,7 @@ require "multi_json"
2
2
  require "sinatra/base"
3
3
 
4
4
  require_relative "pliny/version"
5
+ require_relative "pliny/canonical_log_line_helpers"
5
6
  require_relative "pliny/error_reporters"
6
7
  require_relative "pliny/errors"
7
8
  require_relative "pliny/helpers/encode"
@@ -14,6 +15,7 @@ require_relative "pliny/request_store"
14
15
  require_relative "pliny/rollbar_logger"
15
16
  require_relative "pliny/router"
16
17
  require_relative "pliny/utils"
18
+ require_relative "pliny/middleware/canonical_log_line"
17
19
  require_relative "pliny/middleware/cors"
18
20
  require_relative "pliny/middleware/instruments"
19
21
  require_relative "pliny/middleware/metrics"
@@ -0,0 +1,46 @@
1
+ module Pliny
2
+ # Helpers to produce a canonical log line. This mostly amounts to a set of
3
+ # accessors that do basic type checking combined with tracking an internal
4
+ # schema so that we can produce a hash of everything that's been set so far.
5
+ module CanonicalLogLineHelpers
6
+ module ClassMethods
7
+ def log_field(name, type)
8
+ unless name.is_a?(Symbol)
9
+ raise ArgumentError, "Expected first argument to be a symbol"
10
+ end
11
+
12
+ @fields ||= {}
13
+ @fields[name] = type
14
+ define_method(:"#{name}=") do |val|
15
+ set_field(name, val)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.included(klass)
21
+ klass.extend(ClassMethods)
22
+ end
23
+
24
+ def to_h
25
+ @values
26
+ end
27
+
28
+ private
29
+
30
+ def set_field(name, value)
31
+ type = self.class.instance_variable_get(:@fields)[name]
32
+
33
+ unless type
34
+ raise ArgumentError, "Field #{name} undefined"
35
+ end
36
+
37
+ if !value.nil? && !value.is_a?(type)
38
+ raise ArgumentError,
39
+ "Expected #{name} to be type #{type} (was #{value.class.name})"
40
+ end
41
+
42
+ @values ||= {}
43
+ @values[name] = value
44
+ end
45
+ end
46
+ end
data/lib/pliny/log.rb CHANGED
@@ -4,6 +4,10 @@ module Pliny
4
4
  log_to_stream(stdout || $stdout, merge_log_contexts(data), &block)
5
5
  end
6
6
 
7
+ def log_without_context(data, &block)
8
+ log_to_stream(stdout || $stdout, data, &block)
9
+ end
10
+
7
11
  def log_exception(e, data = {})
8
12
  exception_id = e.object_id
9
13
 
@@ -0,0 +1,125 @@
1
+ module Pliny::Middleware
2
+ # Emits a "canonical log line", i.e. a single log line that contains as much
3
+ # relevant information about a request as possible and which makes for a
4
+ # single convenient reference point to understand all the vitals of any
5
+ # single request.
6
+ #
7
+ # This default implementation contains some useful data to get a project
8
+ # started, but it's usually recommended to vendor this middleware into your
9
+ # project and start adding some custom fields. Some examples of those might
10
+ # be:
11
+ #
12
+ # * ID and email of an authenticated user.
13
+ # * ID of API key used, OAuth application and scope.
14
+ # * Remaining and total rate limits.
15
+ # * Name of the service, HEAD revision, release number.
16
+ # * Name of the internal system that initiated the request.
17
+ #
18
+ class CanonicalLogLine
19
+ # LogLine is a nested model that allows us to construct a canonical log
20
+ # line in a way that's reasonably well organized and somewhat type safe.
21
+ # It's responsible for hashifying its defined fields for emission into a
22
+ # log stream or elsewhere.
23
+ class LogLine
24
+ include Pliny::CanonicalLogLineHelpers
25
+
26
+ #
27
+ # error
28
+ #
29
+
30
+ log_field :error_class, String
31
+ log_field :error_id, String
32
+ log_field :error_message, String
33
+
34
+ #
35
+ # request
36
+ #
37
+
38
+ log_field :request_id, String
39
+ log_field :request_method, String
40
+ log_field :request_ip, String
41
+ log_field :request_path, String
42
+ log_field :request_route_signature, String
43
+ log_field :request_user_agent, String
44
+
45
+ #
46
+ # response
47
+ #
48
+
49
+ log_field :response_length, Integer
50
+ log_field :response_status, Integer
51
+
52
+ #
53
+ # timing
54
+ #
55
+
56
+ log_field :timing_total_elapsed, Float
57
+ end
58
+
59
+ def initialize(app, emitter:)
60
+ @app = app
61
+ @emitter = emitter
62
+ end
63
+
64
+ def call(env)
65
+ begin
66
+ start = Time.now
67
+ status, headers, response = @app.call(env)
68
+ ensure
69
+ begin
70
+ line = LogLine.new
71
+
72
+ #
73
+ # error
74
+ #
75
+
76
+ if error = env["pliny.error"]
77
+ line.error_class = error.class.name
78
+ line.error_message = error.message
79
+ if error.is_a?(Pliny::Errors::Error)
80
+ line.error_id = error.id.to_s
81
+ end
82
+ end
83
+
84
+ #
85
+ # request
86
+ #
87
+
88
+ request = Rack::Request.new(env)
89
+ line.request_id = env["REQUEST_ID"]
90
+ line.request_ip = request.ip
91
+ line.request_method = request.request_method
92
+ line.request_path = request.path_info
93
+ line.request_user_agent = request.user_agent
94
+ if route = env["sinatra.route"]
95
+ line.request_route_signature = route.split(" ").last
96
+ end
97
+
98
+ #
99
+ # response
100
+ #
101
+
102
+ if length = headers["Content-Length"]
103
+ line.response_length = length.to_i
104
+ end
105
+ line.response_status = status
106
+
107
+ #
108
+ # timing
109
+ #
110
+
111
+ line.timing_total_elapsed = (Time.now - start).to_f
112
+
113
+ @emitter.call(line.to_h)
114
+ rescue => e
115
+ # We hope that a canonical log line never fails, but in case it
116
+ # does, do not fail the request because it did.
117
+ Pliny.log(message: "Failed to emit canonical log line")
118
+ Pliny::ErrorReporters.notify(e, rack_env: env)
119
+ end
120
+ end
121
+
122
+ [status, headers, response]
123
+ end
124
+ end
125
+ end
@@ -9,12 +9,20 @@ module Pliny::Middleware
9
9
  def call(env)
10
10
  @app.call(env)
11
11
  rescue Pliny::Errors::Error => e
12
+ set_error_in_env(env, e)
12
13
  Pliny::Errors::Error.render(e)
13
14
  rescue => e
15
+ set_error_in_env(env, e)
14
16
  raise if @raise
15
17
 
16
18
  Pliny::ErrorReporters.notify(e, rack_env: env)
17
19
  Pliny::Errors::Error.render(Pliny::Errors::InternalServerError.new(@message))
18
20
  end
21
+
22
+ # Sets the error in a predefined env key for use by the upstream
23
+ # CanonicalLogLine middleware.
24
+ def set_error_in_env(env, e)
25
+ env["pliny.error"] = e
26
+ end
19
27
  end
20
28
  end
data/lib/pliny/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pliny
2
- VERSION = "0.23.0"
2
+ VERSION = "0.24.0"
3
3
  end
data/lib/template/Gemfile CHANGED
@@ -4,7 +4,7 @@ ruby "2.3.3"
4
4
  gem "multi_json"
5
5
  gem "oj"
6
6
  gem "pg"
7
- gem "pliny", "~> 0.23"
7
+ gem "pliny", "~> 0.24"
8
8
  gem "pry"
9
9
  gem "puma", "~> 3"
10
10
  gem "rack-ssl"
@@ -4,7 +4,10 @@ Routes = Rack::Builder.new do
4
4
  use Pliny::Middleware::RequestID
5
5
  use Pliny::Middleware::RequestStore::Seed, store: Pliny::RequestStore
6
6
  use Pliny::Middleware::Metrics
7
- use Pliny::Middleware::Instruments
7
+ use Pliny::Middleware::CanonicalLogLine,
8
+ emitter: -> (data) {
9
+ Pliny.log_without_context({ canonical_log_line: true }.merge(data))
10
+ }
8
11
  use Pliny::Middleware::RescueErrors, raise: Config.raise_errors?
9
12
  if Config.timeout.positive?
10
13
  use Rack::Timeout,
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ describe Pliny::CanonicalLogLineHelpers do
4
+ class TestCanonicalLogLine
5
+ include Pliny::CanonicalLogLineHelpers
6
+
7
+ log_field :field_float, Float
8
+ log_field :field_integer, Integer
9
+ log_field :field_string, String
10
+ end
11
+
12
+ it "allows a field to be set" do
13
+ line = TestCanonicalLogLine.new
14
+ line.field_string = "foo"
15
+ end
16
+
17
+ it "allows nils to be set" do
18
+ line = TestCanonicalLogLine.new
19
+ line.field_string = nil
20
+ end
21
+
22
+ it "rejects values that are of the wrong type" do
23
+ line = TestCanonicalLogLine.new
24
+ e = assert_raises ArgumentError do
25
+ line.field_string = true
26
+ end
27
+ assert_equal "Expected field_string to be type String (was TrueClass)",
28
+ e.message
29
+ end
30
+
31
+ it "produces a hash with #to_h" do
32
+ line = TestCanonicalLogLine.new
33
+ line.field_float = 3.14
34
+ line.field_integer = 42
35
+ line.field_string = "foo"
36
+ assert_equal({ field_float: 3.14, field_integer: 42, field_string: "foo" },
37
+ line.to_h)
38
+ end
39
+ end
data/spec/log_spec.rb CHANGED
@@ -38,6 +38,12 @@ describe Pliny::Log do
38
38
  Pliny.log(foo: "bar")
39
39
  end
40
40
 
41
+ it "logs without context" do
42
+ Pliny.default_context = { app: "pliny" }
43
+ expect(@io).to receive(:print).with("foo=bar\n")
44
+ Pliny.log_without_context(foo: "bar")
45
+ end
46
+
41
47
  it "merges context from RequestStore" do
42
48
  Pliny::RequestStore.store[:log_context] = { app: "pliny" }
43
49
  expect(@io).to receive(:print).with("app=pliny foo=bar\n")
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+
3
+ describe Pliny::Middleware::CanonicalLogLine do
4
+ def app
5
+ Rack::Builder.new do
6
+ run Sinatra.new {
7
+ use Pliny::Middleware::RequestID
8
+
9
+ use Pliny::Middleware::CanonicalLogLine,
10
+ emitter: -> (data) { Pliny.log_without_context(data) }
11
+
12
+ use Pliny::Middleware::RescueErrors, raise: false
13
+
14
+ get "/apps/:id" do
15
+ status 201
16
+ "hi"
17
+ end
18
+
19
+ get "/generic-error" do
20
+ raise ArgumentError, "argument error"
21
+ end
22
+
23
+ get "/pliny-error" do
24
+ raise Pliny::Errors::NotFound
25
+ end
26
+ }
27
+ end
28
+ end
29
+
30
+ it "emits on a successful request" do
31
+ data = {}
32
+ expect(Pliny).to receive(:log_without_context) { |d| data = d }
33
+
34
+ header "User-Agent", "rack-test"
35
+ get "/apps/123"
36
+
37
+ assert_match Pliny::Middleware::RequestID::UUID_PATTERN,
38
+ data[:request_id]
39
+ assert_equal "127.0.0.1", data[:request_ip]
40
+ assert_equal "GET", data[:request_method]
41
+ assert_equal "/apps/123", data[:request_path]
42
+ assert_equal "/apps/:id", data[:request_route_signature]
43
+ assert_equal "rack-test", data[:request_user_agent]
44
+
45
+ assert_equal 2, data[:response_length]
46
+ assert_equal 201, data[:response_status]
47
+ end
48
+
49
+ it "never fails a request on failure" do
50
+ expect(Pliny).to receive(:log).with(
51
+ message: "Failed to emit canonical log line")
52
+ expect(Pliny).to receive(:log_without_context) { |d| raise "bang!" }
53
+
54
+ get "/apps/123"
55
+ assert_equal 201, last_response.status
56
+ end
57
+
58
+ it "emits on generic error" do
59
+ data = {}
60
+ expect(Pliny).to receive(:log_without_context) { |d| data = d }
61
+ get "/generic-error"
62
+
63
+ assert_equal "ArgumentError", data[:error_class]
64
+ assert_equal "argument error", data[:error_message]
65
+
66
+ assert_equal "GET", data[:request_method]
67
+ assert_equal "/generic-error", data[:request_path]
68
+ assert_equal "/generic-error", data[:request_route_signature]
69
+
70
+ assert_equal 500, data[:response_status]
71
+ end
72
+
73
+ it "emits on Pliny error" do
74
+ data = {}
75
+ expect(Pliny).to receive(:log_without_context) { |d| data = d }
76
+ get "/pliny-error"
77
+
78
+ assert_equal "Pliny::Errors::NotFound", data[:error_class]
79
+ assert_equal "Not found.", data[:error_message]
80
+ assert_equal "not_found", data[:error_id]
81
+
82
+ assert_equal "GET", data[:request_method]
83
+ assert_equal "/pliny-error", data[:request_path]
84
+ assert_equal "/pliny-error", data[:request_route_signature]
85
+
86
+ assert_equal 404, data[:response_status]
87
+ end
88
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pliny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandur Leach
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-01-10 00:00:00.000000000 Z
12
+ date: 2017-02-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -17,20 +17,20 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '4.1'
20
+ version: '5.0'
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 4.1.0
23
+ version: 5.0.1
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - "~>"
29
29
  - !ruby/object:Gem::Version
30
- version: '4.1'
30
+ version: '5.0'
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 4.1.0
33
+ version: 5.0.1
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: multi_json
36
36
  requirement: !ruby/object:Gem::Requirement
@@ -376,6 +376,7 @@ files:
376
376
  - bin/pliny-new
377
377
  - bin/pliny-update
378
378
  - lib/pliny.rb
379
+ - lib/pliny/canonical_log_line_helpers.rb
379
380
  - lib/pliny/commands/creator.rb
380
381
  - lib/pliny/commands/generator.rb
381
382
  - lib/pliny/commands/generator/base.rb
@@ -397,6 +398,7 @@ files:
397
398
  - lib/pliny/log.rb
398
399
  - lib/pliny/metrics.rb
399
400
  - lib/pliny/metrics/backends/logger.rb
401
+ - lib/pliny/middleware/canonical_log_line.rb
400
402
  - lib/pliny/middleware/cors.rb
401
403
  - lib/pliny/middleware/instruments.rb
402
404
  - lib/pliny/middleware/metrics.rb
@@ -469,6 +471,7 @@ files:
469
471
  - lib/template/spec/spec_support/auto_define_rack_app.rb
470
472
  - lib/template/spec/spec_support/coverage.rb
471
473
  - lib/template/spec/spec_support/log.rb
474
+ - spec/canonical_log_line_helpers_spec.rb
472
475
  - spec/commands/creator_spec.rb
473
476
  - spec/commands/generator/base_spec.rb
474
477
  - spec/commands/generator/endpoint_spec.rb
@@ -490,6 +493,7 @@ files:
490
493
  - spec/log_spec.rb
491
494
  - spec/metrics/backends/logger_spec.rb
492
495
  - spec/metrics_spec.rb
496
+ - spec/middleware/canonical_log_line_spec.rb
493
497
  - spec/middleware/cors_spec.rb
494
498
  - spec/middleware/instruments_spec.rb
495
499
  - spec/middleware/metrics_spec.rb
@@ -524,7 +528,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
524
528
  version: '0'
525
529
  requirements: []
526
530
  rubyforge_project:
527
- rubygems_version: 2.5.2
531
+ rubygems_version: 2.5.1
528
532
  signing_key:
529
533
  specification_version: 4
530
534
  summary: Basic tooling to support API apps in Sinatra