Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: raven (sentry integration) #89

Merged
merged 4 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ shards:
git: https://github.com/crystal-ameba/ameba.git
version: 0.14.3

any_hash:
git: https://github.com/sija/any_hash.cr.git
version: 0.2.5

backtracer:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.1

bindata:
git: https://github.com/spider-gazelle/bindata.git
version: 1.9.0
Expand Down Expand Up @@ -127,19 +135,19 @@ shards:

placeos-driver:
git: https://github.com/placeos/driver.git
version: 5.0.8
version: 5.1.0

placeos-frontends:
git: https://github.com/placeos/frontends.git
version: 0.11.0
version: 0.11.1

placeos-log-backend:
git: https://github.com/place-labs/log-backend.git
version: 0.4.0

placeos-models:
git: https://github.com/placeos/models.git
version: 4.16.2
version: 4.18.1

placeos-resource:
git: https://github.com/place-labs/resource.git
Expand All @@ -157,6 +165,10 @@ shards:
git: https://github.com/spider-gazelle/promise.git
version: 2.2.2

raven:
git: https://github.com/sija/raven.cr.git
version: 1.9.1+git.commit.d53319dc4ce26fc44f13b9c1c6ffb8699e22899b

redis:
git: https://github.com/maiha/crystal-redis.git
version: 2.6.0
Expand Down
4 changes: 2 additions & 2 deletions shard.override.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dependencies:
retriable:
github: Sija/retriable.cr
neuroplastic:
github: place-labs/neuroplastic
retriable:
github: Sija/retriable.cr
secrets-env:
github: spider-gazelle/secrets-env
9 changes: 7 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: placeos-rest-api
version: 1.26.3
version: 1.27.0
crystal: ~> 1

targets:
Expand Down Expand Up @@ -50,7 +50,7 @@ dependencies:
# For driver state helpers
placeos-driver:
github: placeos/driver
version: ~> 5
version: ~> 5.1

# For frontends client
placeos-frontends:
Expand All @@ -71,6 +71,11 @@ dependencies:
github: spider-gazelle/promise
version: ~> 2.2

# Sentry integration
raven:
github: Sija/raven.cr
branch: master

# RethinkDB client
rethinkdb:
github: kingsleyh/crystal-rethinkdb
Expand Down
2 changes: 1 addition & 1 deletion spec/helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ end
# Yield an authenticated user, and a header with Authorization bearer set
def authentication(scope = ["public"] of String)
test_user_email = "[email protected]"
existing = PlaceOS::Model::User.get_all([test_user_email], index: :email).first?
existing = PlaceOS::Model::User.find_all([test_user_email], index: :email).first?

authenticated_user = if existing
existing
Expand Down
3 changes: 3 additions & 0 deletions src/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ class PlaceOS::Driver; end

# Application dependencies
require "action-controller"

# Application code
require "./logging"
require "./placeos-rest-api"

# Server required after application controllers
require "action-controller/server"

Expand All @@ -20,6 +22,7 @@ module PlaceOS::Api
# Add handlers that should run before your application
ActionController::Server.before(
ActionController::ErrorHandler.new(Api.production?, ["X-Request-ID"]),
Raven::ActionController::ErrorHandler.new,
ActionController::LogHandler.new(filters, ms: true)
)
end
32 changes: 23 additions & 9 deletions src/logging.cr
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
require "placeos-log-backend"
require "raven"
require "raven/integrations/action-controller"

module PlaceOS::Api
standard_sentry = Raven::LogBackend.new
comprehensive_sentry = Raven::LogBackend.new(capture_all: true)

# Logging configuration
log_backend = PlaceOS::LogBackend.log_backend
log_level = Api.production? ? ::Log::Severity::Info : ::Log::Severity::Debug
namespaces = ["action-controller.*", "place_os.*"]

::Log.setup "*", :warn, log_backend
::Log.setup do |config|
config.bind "*", :warn, log_backend

namespaces = ["action-controller.*", "place_os.*"]
namespaces.each do |namespace|
::Log.builder.bind(namespace, log_level, log_backend)
end
namespaces.each do |namespace|
config.bind namespace, log_level, log_backend

# Bind raven's backend
config.bind namespace, :info, standard_sentry
config.bind namespace, :warn, comprehensive_sentry
end

# Extra verbose coordination logging
if ENV["PLACE_VERBOSE_CLUSTERING"]?.presence
::Log.builder.bind "hound_dog.*", ::Log::Severity::Debug, log_backend
::Log.builder.bind "clustering.*", ::Log::Severity::Debug, log_backend
# Extra verbose coordination logging
if ENV["PLACE_VERBOSE_CLUSTERING"]?.presence
config.bind "hound_dog.*", ::Log::Severity::Trace, log_backend
config.bind "clustering.*", ::Log::Severity::Trace, log_backend
end
end

# Configure Sentry
Raven.configure &.async=(true)

PlaceOS::LogBackend.register_severity_switch_signals(
production: Api.production?,
namespaces: namespaces,
Expand Down
27 changes: 27 additions & 0 deletions src/placeos-rest-api.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,32 @@ require "./placeos-rest-api/controllers/*"
module PlaceOS::Api
Log = ::Log.for(self)

LOG_STDOUT = ActionController.default_backend
LOGSTASH_HOST = ENV["LOGSTASH_HOST"]?
LOGSTASH_PORT = ENV["LOGSTASH_PORT"]?

def self.log_backend
if !(logstash_host = LOGSTASH_HOST.presence).nil?
logstash_port = LOGSTASH_PORT.try(&.to_i?) || abort("LOGSTASH_PORT is either malformed or not present in environment")

# Logstash UDP Input
logstash = UDPSocket.new
logstash.connect logstash_host, logstash_port
logstash.sync = false

# debug at the broadcast backend level, however this will be filtered
# by the bindings
backend = ::Log::BroadcastBackend.new
backend.append(LOG_STDOUT, :trace)
backend.append(ActionController.default_backend(
io: logstash,
formatter: ActionController.json_formatter
), :trace)
backend
else
LOG_STDOUT
end
end

class_getter? production : Bool = PROD
end
1 change: 1 addition & 0 deletions src/placeos-rest-api/controllers/application.cr
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ module PlaceOS::Api
client_ip: client_ip,
request_id: request_id
)

response.headers["X-Request-ID"] = request_id
end

Expand Down
4 changes: 2 additions & 2 deletions src/placeos-rest-api/controllers/systems.cr
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ module PlaceOS::Api
# Finds all the systems with the specified email address
get "/with_emails", :find_by_email do
emails = params["in"].split(',').map(&.strip).reject(&.empty?).uniq!
systems = Model::ControlSystem.get_all(emails, index: :email).to_a
systems = Model::ControlSystem.find_all(emails, index: :email).to_a
set_collection_headers(systems.size, Model::ControlSystem.table_name)
render json: systems
end
Expand Down Expand Up @@ -184,7 +184,7 @@ module PlaceOS::Api
documents = if current_control_system.zones.empty?
[] of Model::Zone
else
Model::Zone.get_all(current_control_system.zones).to_a
Model::Zone.find_all(current_control_system.zones).to_a
end

set_collection_headers(documents.size, Model::Zone.table_name)
Expand Down
21 changes: 8 additions & 13 deletions src/placeos-rest-api/utilities/current-user.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ require "placeos-models/user_jwt"
module PlaceOS::Api
# Helper to grab user and authority from a request
module Utils::CurrentUser
@user_token : Model::UserJWT?
@current_user : Model::User?
@current_authority : Model::Authority?

# Parses, and validates JWT if present.
# Throws Error::MissingBearer and JWT::Error.
def authorize!
return if @user_token
def authorize! : Model::UserJWT
unless (token = @user_token).nil?
return token
end

token = acquire_token

Expand All @@ -38,9 +36,10 @@ module PlaceOS::Api
token_domain_host = URI.parse(user_token.domain).host
authority_domain_host = URI.parse(authority.domain.as(String)).host
unless token_domain_host == authority_domain_host
Log.warn { {message: "authority domain does not match token's", action: "authorize!", token_domain: user_token.aud, authority_domain: authority.domain} }
Log.warn { {message: "authority domain does not match token's", action: "authorize!", token_domain: user_token.domain, authority_domain: authority.domain} }
raise Error::Unauthorized.new "authority domain does not match token's"
end
user_token
rescue e
# ensure that the user token is nil if this function ever errors.
@user_token = nil
Expand All @@ -50,7 +49,7 @@ module PlaceOS::Api
def check_oauth_scope
utoken = user_token
unless utoken.scope.includes?("public")
Log.warn { {message: "unknown scope #{utoken.scope}", action: "authorize!", host: request.hostname, sub: utoken.sub} }
Log.warn { {message: "unknown scope #{utoken.scope}", action: "authorize!", host: request.hostname, id: utoken.id} }
raise Error::Unauthorized.new "public scope required for access"
end
end
Expand All @@ -62,11 +61,7 @@ module PlaceOS::Api
getter current_authority : Model::Authority? { Model::Authority.find_by_domain(request.hostname.as(String)) }

# Getter for user_token
def user_token : Model::UserJWT
# FIXME: Remove when action-controller respects the ordering of route callbacks
authorize! unless @user_token
@user_token.as(Model::UserJWT)
end
getter user_token : Model::UserJWT { authorize! }

# Read admin status from supplied request JWT
def check_admin
Expand Down