airfoil 0.1.3 → 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: 147d3ae419196b330440e2a2c821668627eee5df6a7e9f4659fe2d399c944cf7
4
- data.tar.gz: 5ff9c18c639369e9ee451d6ce7d922ecf23cd174a687524eea518fb031221069
3
+ metadata.gz: 19253bd2e6d9a47232400ac4ee4d7a1fe7953c462af51d419ce6d6fbc941dc50
4
+ data.tar.gz: '08854bb418dbf76f5254d4132ffaf9f9ba496e7c2398b214e51b55658321c504'
5
5
  SHA512:
6
- metadata.gz: 86246b400bb9a6b42a956f76062af1392e93514e7492f28f5a29d169f5307dac3fb9ac2dddf56467066a160ee9fb309b0468005454e1f6c179c014e8862618f9
7
- data.tar.gz: 4f71ed71afdaef2b7d720140301e67af1eb6376319d5937eee8d1d3e000e4a0e9990961c46b94d8dc74e4343fd6fec66b1c8ef6b4595e0470870d45e3f825231
6
+ metadata.gz: '084ee1915eca4bfe74e7743d439b5f8fd8db205fed067073a5ad320be13b7164c91a2008b9ec72c394b9cff2189ea1df1835f876a3d11f0a1254f8f72544e1a2'
7
+ data.tar.gz: 3f03cfdfd2baf6554d344cd2f79dbb6a24c9b71eef66178643f74c3d3edd32369fe85382d9c01f2afb93345fdaf917f9f5b99f182b6fd5f2f027e901de61f5be
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Airfoil
2
+
2
3
  _Enough structure to get our Lambda handlers in the air._
3
4
 
4
5
  Airfoil is curated middleware stack that abstracts away common infrastructure
@@ -14,11 +15,11 @@ gem 'airfoil'
14
15
 
15
16
  And then execute:
16
17
 
17
- $ bundle install
18
+ bundle install
18
19
 
19
20
  Or install it yourself as:
20
21
 
21
- $ gem install airfoil
22
+ gem install airfoil
22
23
 
23
24
  ## Usage
24
25
 
@@ -54,10 +55,38 @@ Existing middleware include:
54
55
 
55
56
  - `FunctionName` - dispatch any calls made to a specific Lambda function (by name) to a specified handler class
56
57
  - `LogEvent` - log AWS events in a pretty format
57
- - `SentryCatcher` - catch exceptions and report them to Sentry, including context
58
- - `SentryMonitoring` - instrument your function code for Sentry's performance monitoring
59
58
  - `SetRequestId` - set the `AWS_REQUEST_ID` environment variable for your function code
60
59
 
60
+ ## Additional Middleware
61
+
62
+ There are additional middleware available as separate gems that provide specific functionality. They must be explicitly added to your middleware stack in `create_stack`.
63
+
64
+ ### Sentry
65
+
66
+ Add the `airfoil-sentry` gem to your Gemfile.This provides three middlewares:
67
+
68
+ - `SentryCatcher` - catch exceptions and report them to Sentry, including context:
69
+
70
+ ```ruby
71
+ b.use Airfoil::Middleware::SentryCatcher
72
+ ```
73
+
74
+ - `SentryMonitoring` - instrument your function code for Sentry's performance monitoring
75
+
76
+ ```ruby
77
+ b.use Airfoil::Middleware::SentryMonitoring
78
+ ```
79
+
80
+ ### ActiveRecord
81
+
82
+ Add the `airfoil-activerecord` gem to your Gemfile. This provides a single middleware:
83
+
84
+ - `DatabaseConnection` - Check a connection in/out and enable the query cache per handler
85
+
86
+ ```ruby
87
+ b.use Airfoil::Middleware::DatabaseConnection
88
+ ```
89
+
61
90
  ## Development
62
91
 
63
92
  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.
@@ -1,33 +1,8 @@
1
1
  require "airfoil/cloudwatch_formatter"
2
- require "datadog/lambda"
3
2
 
4
3
  module Airfoil
5
4
  class Railtie < Rails::Railtie
6
5
  # Format logs for consistent parsing by Cloudwatch
7
6
  config.log_formatter = Airfoil::CloudwatchFormatter.new
8
- config.datadog_enabled = ENV.fetch("DATADOG_ENABLED", Rails.env.production?).to_s == "true"
9
-
10
- initializer "airfoil.datadog" do
11
- if Rails.configuration.datadog_enabled
12
- require "ddtrace/auto_instrument"
13
-
14
- ::Datadog::Lambda.configure_apm do |c|
15
- c.env = ENV.fetch("SENTRY_ENVIRONMENT", Rails.env).dasherize
16
- # downscasing first ensures we don't attempt to snake case things that don't already have dashes
17
- c.service = ENV.fetch("AWS_LAMBDA_FUNCTION_NAME", "brokersuite").downcase.underscore
18
-
19
- # Set trace rate via DD_TRACE_SAMPLE_RATE
20
- c.tracing.enabled = true
21
- c.tracing.instrument :aws
22
- c.tracing.instrument :faraday
23
- c.tracing.instrument :rest_client
24
- c.tracing.instrument :httpclient
25
- c.tracing.instrument :http
26
- c.tracing.instrument :rails
27
- end
28
-
29
- Rails.logger.debug "=====DATADOG LOADED (RAILTIE)====="
30
- end
31
- end
32
7
  end
33
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Airfoil
4
- VERSION = '0.1.3'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/airfoil.rb CHANGED
@@ -3,38 +3,35 @@
3
3
  require "middleware"
4
4
 
5
5
  require_relative "airfoil/version"
6
- require_relative "airfoil/middleware/database"
7
- require_relative "airfoil/middleware/datadog"
8
6
  require_relative "airfoil/middleware/function_name"
9
7
  require_relative "airfoil/middleware/log_event"
10
8
  require_relative "airfoil/middleware/logger_tagging"
11
- require_relative "airfoil/middleware/sentry_catcher"
12
- require_relative "airfoil/middleware/sentry_monitoring"
13
9
  require_relative "airfoil/middleware/set_request_id"
14
- require_relative "airfoil/middleware/step_function"
15
10
  require_relative "airfoil/logger_patch"
16
11
  require_relative "airfoil/railtie" if defined?(Rails::Railtie)
17
12
 
18
13
  module Airfoil
19
14
  class << self
20
- def create_stack
15
+ def create_stack(logger)
21
16
  # ensure that STDOUT streams are synchronous so we don't lose logs
22
17
  $stdout.sync = true
23
18
 
24
19
  Signal.trap("TERM") do
25
20
  # We can't use the Rails logger here as the logger is not available in the trap context
26
- puts "Received SIGTERM, shutting down gracefully..." # rubocop:disable Rails/Output
21
+ puts "Received SIGTERM, shutting down gracefully..."
27
22
  end
28
23
 
24
+ logger ||= defined?(::Rails) ? Rails.logger : Logger.new($stdout, level: (ENV["LOG_LEVEL"] || :info).to_sym)
25
+
29
26
  ::Middleware::Builder.new { |b|
30
- b.use Middleware::LoggerTagging, Rails.logger
27
+ if defined?(::Rails)
28
+ b.use Middleware::LoggerTagging, logger
29
+ end
31
30
  b.use Middleware::SetRequestId
32
- b.use Middleware::Datadog
33
- b.use Middleware::SentryCatcher, Rails.logger
34
- b.use Middleware::SentryMonitoring
35
- b.use Middleware::LogEvent, Rails.logger
31
+ # This is causing infinite recursion for some reason
32
+ # b.use Middleware::LogEvent, logger
36
33
  yield b
37
- }.inject_logger(Rails.logger)
34
+ }.inject_logger(logger)
38
35
  end
39
36
  end
40
37
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airfoil
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Highwing Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-10 00:00:00.000000000 Z
11
+ date: 2025-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: datadog-lambda
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: dry-monads
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +30,14 @@ dependencies:
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: 0.4.2
33
+ version: 0.4.3
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: 0.4.2
40
+ version: 0.4.3
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: railties
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +52,6 @@ dependencies:
66
52
  - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: sentry-ruby
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
55
  - !ruby/object:Gem::Dependency
84
56
  name: aws_lambda_ric
85
57
  requirement: !ruby/object:Gem::Requirement
@@ -134,15 +106,10 @@ files:
134
106
  - lib/airfoil/cloudwatch_formatter.rb
135
107
  - lib/airfoil/logger_patch.rb
136
108
  - lib/airfoil/middleware/base.rb
137
- - lib/airfoil/middleware/database.rb
138
- - lib/airfoil/middleware/datadog.rb
139
109
  - lib/airfoil/middleware/function_name.rb
140
110
  - lib/airfoil/middleware/log_event.rb
141
111
  - lib/airfoil/middleware/logger_tagging.rb
142
- - lib/airfoil/middleware/sentry_catcher.rb
143
- - lib/airfoil/middleware/sentry_monitoring.rb
144
112
  - lib/airfoil/middleware/set_request_id.rb
145
- - lib/airfoil/middleware/step_function.rb
146
113
  - lib/airfoil/railtie.rb
147
114
  - lib/airfoil/version.rb
148
115
  homepage:
@@ -1,17 +0,0 @@
1
- require "airfoil/middleware/base"
2
-
3
- module Airfoil
4
- module Middleware
5
- class Database < Airfoil::Middleware::Base
6
- def call(env)
7
- # TODO: This explicitly makes a connection, which we may want to re-evaluate at some point
8
- ActiveRecord::Base.connection_pool.with_connection do |conn|
9
- conn.enable_query_cache!
10
- @app.call(env)
11
- ensure
12
- conn.disable_query_cache!
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,23 +0,0 @@
1
- require "datadog/lambda"
2
- require "datadog/tracing"
3
- require "airfoil/middleware/base"
4
-
5
- module Airfoil
6
- module Middleware
7
- class Datadog < Base
8
- # See the Airfoil railtie for config loaded at Rails load-time
9
- # Note: Rails loads prior to Airfoil because of the inclusion of `require_relative "config/environment"`
10
- # in the engine lambda handler
11
- def call(env)
12
- event, context = env.values_at(:event, :context)
13
-
14
- ::Datadog::Lambda.wrap(event, context) do
15
- @app.call(env)
16
- rescue => err
17
- ::Datadog::Tracing.active_span&.set_error(err)
18
- raise err
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,40 +0,0 @@
1
- require "sentry-ruby"
2
- require "airfoil/middleware/base"
3
-
4
- module Airfoil
5
- module Middleware
6
- class SentryCatcher < Base
7
- def initialize(app, logger)
8
- super(app)
9
- @logger = logger
10
- end
11
-
12
- def call(env)
13
- @app.call(env)
14
- rescue => err
15
- Sentry.with_scope do |scope|
16
- context = env[:context]
17
- event = env[:event]
18
-
19
- scope.set_extras(
20
- function_name: context.function_name,
21
- function_version: context.function_version,
22
- invoked_function_arn: context.invoked_function_arn,
23
- memory_limit_in_mb: context.memory_limit_in_mb,
24
- aws_request_id: context.aws_request_id,
25
- log_group_name: context.log_group_name,
26
- log_stream_name: context.log_stream_name,
27
- identity: context.identity,
28
- event: event
29
- )
30
-
31
- Sentry.capture_exception(err)
32
- @logger.error(err)
33
- raise err
34
- end
35
- end
36
- end
37
-
38
- class RequestError < StandardError; end
39
- end
40
- end
@@ -1,42 +0,0 @@
1
- require "sentry-ruby"
2
- require "airfoil/middleware/base"
3
-
4
- module Airfoil
5
- module Middleware
6
- class SentryMonitoring < Base
7
- def call(env)
8
- event, context = env.values_at(:event, :context)
9
- sentry_trace_id = get_first_instance(event, "sentry_trace_id")
10
- identity = get_first_instance(event, "identity")
11
-
12
- options = {name: context.function_name, op: "handler"}
13
- options[:transaction] = Sentry::Transaction.from_sentry_trace(sentry_trace_id, **options) if sentry_trace_id.present?
14
-
15
- Sentry.set_user(username: identity.dig("username"), ip_address: identity.dig("source_ip", 0)) if identity.present?
16
- transaction = Sentry.start_transaction(**options)
17
- # Add transaction to the global scope so it is accessible throughout the app
18
- Sentry.get_current_hub&.configure_scope do |scope|
19
- scope.set_span(transaction)
20
- end
21
-
22
- result = @app.call(env)
23
- transaction.finish if transaction.present?
24
-
25
- result
26
- end
27
-
28
- private
29
-
30
- def get_first_instance(event, key)
31
- case event
32
- in [e, *rest]
33
- e.dig(key)
34
- in Hash
35
- event.dig(key)
36
- else
37
- nil
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,37 +0,0 @@
1
- require_relative "function_name"
2
-
3
- module Airfoil
4
- module Middleware
5
- class StepFunction < FunctionName
6
- def initialize(app, handler_class, *function_names_to_match, retried_exceptions: [])
7
- super(app, handler_class, *function_names_to_match)
8
- @retried_exceptions = retried_exceptions.map(&:to_s)
9
- end
10
-
11
- def call(env)
12
- context = env[:context]
13
-
14
- ignore_exceptions(context) do
15
- super
16
- end
17
- end
18
-
19
- def ignore_exceptions(context)
20
- disable_exceptions
21
- result = yield
22
- enable_exceptions
23
-
24
- result
25
- end
26
-
27
- def disable_exceptions
28
- @before = Sentry.configuration.excluded_exceptions
29
- Sentry.configuration.excluded_exceptions += @retried_exceptions
30
- end
31
-
32
- def enable_exceptions
33
- Sentry.configuration.excluded_exceptions = @before
34
- end
35
- end
36
- end
37
- end