pax_global_header00006660000000000000000000000064137214236050014515gustar00rootroot0000000000000052 comment=8afa893a698d2d9c17e72dadcae77c2f3551cde2 grape_logging-1.8.4/000077500000000000000000000000001372142360500143335ustar00rootroot00000000000000grape_logging-1.8.4/.gitignore000066400000000000000000000001361372142360500163230ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ .rspec grape_logging-1.8.4/.travis.yml000066400000000000000000000001121372142360500164360ustar00rootroot00000000000000language: ruby rvm: - 2.3.1 cache: bundler script: - bundle exec rspecgrape_logging-1.8.4/Gemfile000066400000000000000000000001421372142360500156230ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in grape_logging.gemspec gemspec grape_logging-1.8.4/LICENSE.txt000066400000000000000000000020631372142360500161570ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 aserafin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. grape_logging-1.8.4/README.md000066400000000000000000000140121372142360500156100ustar00rootroot00000000000000# grape_logging [![Code Climate](https://codeclimate.com/github/aserafin/grape_logging/badges/gpa.svg)](https://codeclimate.com/github/aserafin/grape_logging) [![Build Status](https://travis-ci.org/aserafin/grape_logging.svg?branch=master)](https://travis-ci.org/aserafin/grape_logging) ## Installation Add this line to your application's Gemfile: gem 'grape_logging' And then execute: $ bundle install Or install it yourself as: $ gem install grape_logging ## Basic Usage In your api file (somewhere on the top) ```ruby require 'grape_logging' logger.formatter = GrapeLogging::Formatters::Default.new use GrapeLogging::Middleware::RequestLogger, { logger: logger } ``` **ProTip:** If your logger doesn't support setting formatter you can remove this line - it's optional ## Features ### Log Format There are formatters provided for you, or you can provide your own. #### `GrapeLogging::Formatters::Default` [2015-04-16 12:52:12 +0200] INFO -- 200 -- total=2.06 db=0.36 -- PATCH /api/endpoint params={"some_param"=>{"value_1"=>"123", "value_2"=>"456"}} #### `GrapeLogging::Formatters::Json` ```json { "date": "2015-04-16 12:52:12+0200", "severity": "INFO", "data": { "status": 200, "time": { "total": 2.06, "db": 0.36, "view": 1.70 }, "method": "PATCH", "path": "/api/endpoint", "params": { "value_1": "123", "value_2": "456" }, "host": "localhost" } } ``` #### `GrapeLogging::Formatters::Lograge` severity="INFO", duration=2.06, db=0.36, view=1.70, datetime="2015-04-16 12:52:12+0200", status=200, method="PATCH", path="/api/endpoint", params={}, host="localhost" #### `GrapeLogging::Formatters::Logstash` ```json { "@timestamp": "2015-04-16 12:52:12+0200", "severity": "INFO", "status": 200, "time": { "total": 2.06, "db": 0.36, "view": 1.70 }, "method": "PATCH", "path": "/api/endpoint", "params": { "value_1": "123", "value_2": "456" }, "host": "localhost" } ``` #### `GrapeLogging::Formatters::Rails` Rails will print the "Started..." line: Started GET "/api/endpoint" for ::1 at 2015-04-16 12:52:12 +0200 User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ... The `Rails` formatter adds the last line of the request, like a standard Rails request: Completed 200 OK in 349ms (Views: 250.1ms | DB: 98.63ms) #### Custom You can provide your own class that implements the `call` method returning a `String`: ```ruby def call(severity, datetime, _, data) ... end ``` You can change the formatter like so ```ruby class MyAPI < Grape::API use GrapeLogging::Middleware::RequestLogger, logger: logger, formatter: MyFormatter.new end ``` If you prefer some other format I strongly encourage you to do pull request with new formatter class ;) ### Customising What Is Logged You can include logging of other parts of the request / response cycle by including subclasses of `GrapeLogging::Loggers::Base` ```ruby class MyAPI < Grape::API use GrapeLogging::Middleware::RequestLogger, logger: logger, include: [ GrapeLogging::Loggers::Response.new, GrapeLogging::Loggers::FilterParameters.new, GrapeLogging::Loggers::ClientEnv.new, GrapeLogging::Loggers::RequestHeaders.new ] end ``` #### FilterParameters The `FilterParameters` logger will filter out sensitive parameters from your logs. If mounted inside rails, will use the `Rails.application.config.filter_parameters` by default. Otherwise, you must specify a list of keys to filter out. #### ClientEnv The `ClientEnv` logger will add `ip` and user agent `ua` in your log. #### RequestHeaders The `RequestHeaders` logger will add `request headers` in your log. ### Logging to file and STDOUT You can log to file and STDOUT at the same time, you just need to assign new logger ```ruby log_file = File.open('path/to/your/logfile.log', 'a') log_file.sync = true logger Logger.new GrapeLogging::MultiIO.new(STDOUT, log_file) ``` ### Set the log level You can control the level used to log. The default is `info`. ```ruby class MyAPI < Grape::API use GrapeLogging::Middleware::RequestLogger, logger: logger, log_level: 'debug' end ``` ### Logging via Rails instrumentation You can choose to not pass the logger to ```grape_logging``` but instead send logs to Rails instrumentation in order to let Rails and its configured Logger do the log job, for example. First, config ```grape_logging```, like that: ```ruby class MyAPI < Grape::API use GrapeLogging::Middleware::RequestLogger, instrumentation_key: 'grape_key', include: [ GrapeLogging::Loggers::Response.new, GrapeLogging::Loggers::FilterParameters.new ] end ``` and then add an initializer in your Rails project: ```ruby # config/initializers/instrumentation.rb # Subscribe to grape request and log with Rails.logger ActiveSupport::Notifications.subscribe('grape_key') do |name, starts, ends, notification_id, payload| Rails.logger.info payload end ``` The idea come from here: https://gist.github.com/teamon/e8ae16ffb0cb447e5b49 ### Logging exceptions If you want to log exceptions you can do it like this ```ruby class MyAPI < Grape::API rescue_from :all do |e| MyAPI.logger.error e #do here whatever you originally planned to do :) end end ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing 1. Fork it ( https://github.com/aserafin/grape_logging/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request grape_logging-1.8.4/Rakefile000066400000000000000000000000351372142360500157760ustar00rootroot00000000000000require 'bundler/gem_tasks' grape_logging-1.8.4/bin/000077500000000000000000000000001372142360500151035ustar00rootroot00000000000000grape_logging-1.8.4/bin/console000066400000000000000000000005221372142360500164670ustar00rootroot00000000000000#!/usr/bin/env ruby require 'bundler/setup' require 'grape_logging' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require 'irb' IRB.start grape_logging-1.8.4/bin/setup000066400000000000000000000001631372142360500161660ustar00rootroot00000000000000#!/bin/bash set -euo pipefail IFS=$'\n\t' bundle install # Do any other automated setup that you need to do here grape_logging-1.8.4/grape_logging.gemspec000066400000000000000000000023301372142360500205020ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'grape_logging/version' Gem::Specification.new do |spec| spec.name = 'grape_logging' spec.version = GrapeLogging::VERSION spec.authors = ['aserafin'] spec.email = ['adrian@softmad.pl'] spec.summary = %q{Out of the box request logging for Grape!} spec.description = %q{This gem provides simple request logging for Grape with just few lines of code you have to put in your project! In return you will get response codes, paths, parameters and more!} spec.homepage = 'http://github.com/aserafin/grape_logging' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.add_dependency 'grape' spec.add_dependency 'rack' spec.add_development_dependency 'bundler', '~> 1.8' spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec', '~> 3.5' spec.add_development_dependency 'pry-byebug', '~> 3.4.2' end grape_logging-1.8.4/lib/000077500000000000000000000000001372142360500151015ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging.rb000066400000000000000000000013331372142360500202320ustar00rootroot00000000000000require 'grape_logging/multi_io' require 'grape_logging/version' require 'grape_logging/formatters/default' require 'grape_logging/formatters/json' require 'grape_logging/formatters/lograge' require 'grape_logging/formatters/logstash' require 'grape_logging/formatters/rails' require 'grape_logging/loggers/base' require 'grape_logging/loggers/response' require 'grape_logging/loggers/filter_parameters' require 'grape_logging/loggers/client_env' require 'grape_logging/loggers/request_headers' require 'grape_logging/reporters/active_support_reporter' require 'grape_logging/reporters/logger_reporter' require 'grape_logging/timings' require 'grape_logging/middleware/request_logger' require 'grape_logging/util/parameter_filter' grape_logging-1.8.4/lib/grape_logging/000077500000000000000000000000001372142360500177055ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/formatters/000077500000000000000000000000001372142360500220735ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/formatters/default.rb000066400000000000000000000015501372142360500240450ustar00rootroot00000000000000module GrapeLogging module Formatters class Default def call(severity, datetime, _, data) "[#{datetime}] #{severity} -- #{format(data)}\n" end def format(data) if data.is_a?(String) data elsif data.is_a?(Exception) format_exception(data) elsif data.is_a?(Hash) "#{data.delete(:status)} -- #{format_hash(data.delete(:time))} -- #{data.delete(:method)} #{data.delete(:path)} #{format_hash(data)}" else data.inspect end end private def format_hash(hash) hash.keys.sort.map { |key| "#{key}=#{hash[key]}" }.join(' ') end def format_exception(exception) backtrace_array = (exception.backtrace || []).map { |line| "\t#{line}" } "#{exception.message}\n#{backtrace_array.join("\n")}" end end end end grape_logging-1.8.4/lib/grape_logging/formatters/json.rb000066400000000000000000000011551372142360500233730ustar00rootroot00000000000000module GrapeLogging module Formatters class Json def call(severity, datetime, _, data) { date: datetime, severity: severity, data: format(data) }.to_json + "\n" end private def format(data) if data.is_a?(String) || data.is_a?(Hash) data elsif data.is_a?(Exception) format_exception(data) else data.inspect end end def format_exception(exception) { exception: { message: exception.message } } end end end end grape_logging-1.8.4/lib/grape_logging/formatters/lograge.rb000066400000000000000000000006371372142360500240460ustar00rootroot00000000000000module GrapeLogging module Formatters class Lograge def call(severity, datetime, _, data) time = data.delete :time attributes = { severity: severity, duration: time[:total], db: time[:db], view: time[:view], datetime: datetime.iso8601 }.merge(data) ::Lograge.formatter.call(attributes) + "\n" end end end end grape_logging-1.8.4/lib/grape_logging/formatters/logstash.rb000066400000000000000000000013201372142360500242400ustar00rootroot00000000000000module GrapeLogging module Formatters class Logstash def call(severity, datetime, _, data) { :'@timestamp' => datetime.iso8601, :'@version' => '1', :severity => severity }.merge!(format(data)).to_json + "\n" end private def format(data) if data.is_a?(Hash) data elsif data.is_a?(String) { message: data } elsif data.is_a?(Exception) format_exception(data) else { message: data.inspect } end end def format_exception(exception) { exception: { message: exception.message } } end end end end grape_logging-1.8.4/lib/grape_logging/formatters/rails.rb000066400000000000000000000036001372142360500235310ustar00rootroot00000000000000require 'rack/utils' module GrapeLogging module Formatters class Rails def call(severity, datetime, _, data) if data.is_a?(String) "#{severity[0..0]} [#{datetime}] #{severity} -- : #{data}\n" elsif data.is_a?(Exception) "#{severity[0..0]} [#{datetime}] #{severity} -- : #{format_exception(data)}\n" elsif data.is_a?(Hash) format_hash(data) else "#{data.inspect}\n" end end private def format_exception(exception) backtrace_array = (exception.backtrace || []).map { |line| "\t#{line}" } [ "#{exception.message} (#{exception.class})", backtrace_array.join("\n") ].reject{|line| line == ""}.join("\n") end def format_hash(hash) # Create Rails' single summary line at the end of every request, formatted like: # Completed 200 OK in 958ms (Views: 951.1ms | ActiveRecord: 3.8ms) # See: actionpack/lib/action_controller/log_subscriber.rb message = "" additions = [] status = hash.delete(:status) params = hash.delete(:params) total_time = hash[:time] && hash[:time][:total] && hash[:time][:total].round(2) view_time = hash[:time] && hash[:time][:view] && hash[:time][:view].round(2) db_time = hash[:time] && hash[:time][:db] && hash[:time][:db].round(2) additions << "Views: #{view_time}ms" if view_time additions << "DB: #{db_time}ms" if db_time message << " Parameters: #{params.inspect}\n" if params message << "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in #{total_time}ms" message << " (#{additions.join(" | ".freeze)})" if additions.size > 0 message << "\n" message << "\n" if defined?(::Rails.env) && ::Rails.env.development? message end end end end grape_logging-1.8.4/lib/grape_logging/loggers/000077500000000000000000000000001372142360500213475ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/loggers/base.rb000066400000000000000000000002031372142360500226010ustar00rootroot00000000000000module GrapeLogging module Loggers class Base def parameters(request, response) {} end end end end grape_logging-1.8.4/lib/grape_logging/loggers/client_env.rb000066400000000000000000000004121372142360500240170ustar00rootroot00000000000000module GrapeLogging module Loggers class ClientEnv < GrapeLogging::Loggers::Base def parameters(request, _) { ip: request.env["HTTP_X_FORWARDED_FOR"] || request.env["REMOTE_ADDR"], ua: request.env["HTTP_USER_AGENT"] } end end end end grape_logging-1.8.4/lib/grape_logging/loggers/filter_parameters.rb000066400000000000000000000021711372142360500254050ustar00rootroot00000000000000module GrapeLogging module Loggers class FilterParameters < GrapeLogging::Loggers::Base AD_PARAMS = 'action_dispatch.request.parameters'.freeze def initialize(filter_parameters = nil, replacement = nil, exceptions = %w(controller action format)) @filter_parameters = filter_parameters || (defined?(::Rails.application) ? ::Rails.application.config.filter_parameters : []) @replacement = replacement || '[FILTERED]' @exceptions = exceptions end def parameters(request, _) { params: safe_parameters(request) } end private def parameter_filter @parameter_filter ||= ParameterFilter.new(@replacement, @filter_parameters) end def safe_parameters(request) # Now this logger can work also over Rails requests if request.params.empty? clean_parameters(request.env[AD_PARAMS] || {}) else clean_parameters(request.params) end end def clean_parameters(parameters) parameter_filter.filter(parameters).reject{ |key, _value| @exceptions.include?(key) } end end end end grape_logging-1.8.4/lib/grape_logging/loggers/request_headers.rb000066400000000000000000000006661372142360500250670ustar00rootroot00000000000000module GrapeLogging module Loggers class RequestHeaders < GrapeLogging::Loggers::Base HTTP_PREFIX = 'HTTP_'.freeze def parameters(request, _) headers = {} request.env.each_pair do |k, v| next unless k.to_s.start_with? HTTP_PREFIX k = k[5..-1].split('_').each(&:capitalize!).join('-') headers[k] = v end { headers: headers } end end end end grape_logging-1.8.4/lib/grape_logging/loggers/response.rb000066400000000000000000000014771372142360500235430ustar00rootroot00000000000000module GrapeLogging module Loggers class Response < GrapeLogging::Loggers::Base def parameters(_, response) response ? { response: serialized_response_body(response) } : {} end private # In some cases, response.body is not parseable by JSON. # For example, if you POST on a PUT endpoint, response.body is egal to """". # It's strange but it's the Grape behavior... def serialized_response_body(response) if response.respond_to?(:body) # Rack responses begin response.body.map{ |body| JSON.parse(body.to_s) } rescue # No reason to have "=> e" here when we don't use it.. response.body end else # Error & Exception responses response end end end end end grape_logging-1.8.4/lib/grape_logging/middleware/000077500000000000000000000000001372142360500220225ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/middleware/request_logger.rb000066400000000000000000000072131372142360500254010ustar00rootroot00000000000000require 'grape/middleware/base' module GrapeLogging module Middleware class RequestLogger < Grape::Middleware::Base ActiveSupport::Notifications.subscribe('sql.active_record') do |*args| event = ActiveSupport::Notifications::Event.new(*args) GrapeLogging::Timings.append_db_runtime(event) end if defined?(ActiveRecord) # Persist response status & response (body) # to use int in parameters attr_accessor :response_status, :response_body def initialize(app, options = {}) super @included_loggers = @options[:include] || [] @reporter = if options[:instrumentation_key] Reporters::ActiveSupportReporter.new(@options[:instrumentation_key]) else Reporters::LoggerReporter.new(@options[:logger], @options[:formatter], @options[:log_level]) end end def before reset_db_runtime start_time invoke_included_loggers(:before) end def after(status, response) stop_time # Response status @response_status = status @response_body = response # Perform repotters @reporter.perform(collect_parameters) # Invoke loggers invoke_included_loggers(:after) nil end # Call stack and parse responses & status. # # @note Exceptions are logged as 500 status & re-raised. def call!(env) @env = env # Before hook before # Catch error error = catch(:error) do begin @app_response = @app.call(@env) rescue => e # Log as 500 + message after(e.respond_to?(:status) ? e.status : 500, e.message) # Re-raise exception raise e end nil end # Get status & response from app_response # when no error occures. if error # Call with error & response after(error[:status], error[:message]) # Throw again throw(:error, error) else status, _, resp = *@app_response # Call after hook properly after(status, resp) end # Otherwise return original response @app_response end protected def parameters { status: response_status, time: { total: total_runtime, db: db_runtime, view: view_runtime }, method: request.request_method, path: request.path, params: request.params, host: request.host } end private def request @request ||= ::Rack::Request.new(@env) end def total_runtime ((stop_time - start_time) * 1000).round(2) end def view_runtime total_runtime - db_runtime end def db_runtime GrapeLogging::Timings.db_runtime.round(2) end def reset_db_runtime GrapeLogging::Timings.reset_db_runtime end def start_time @start_time ||= Time.now end def stop_time @stop_time ||= Time.now end def collect_parameters parameters.tap do |params| @included_loggers.each do |logger| params.merge! logger.parameters(request, response_body) do |_, oldval, newval| oldval.respond_to?(:merge) ? oldval.merge(newval) : newval end end end end def invoke_included_loggers(method_name) @included_loggers.each do |logger| logger.send(method_name) if logger.respond_to?(method_name) end end end end end grape_logging-1.8.4/lib/grape_logging/multi_io.rb000066400000000000000000000003461372142360500220560ustar00rootroot00000000000000module GrapeLogging class MultiIO def initialize(*targets) @targets = targets end def write(*args) @targets.each {|t| t.write(*args)} end def close @targets.each(&:close) end end endgrape_logging-1.8.4/lib/grape_logging/reporters/000077500000000000000000000000001372142360500217325ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/reporters/active_support_reporter.rb000066400000000000000000000004051372142360500272470ustar00rootroot00000000000000module Reporters class ActiveSupportReporter def initialize(instrumentation_key) @instrumentation_key = instrumentation_key end def perform(params) ActiveSupport::Notifications.instrument @instrumentation_key, params end end endgrape_logging-1.8.4/lib/grape_logging/reporters/logger_reporter.rb000066400000000000000000000006361372142360500254650ustar00rootroot00000000000000module Reporters class LoggerReporter def initialize(logger, formatter, log_level) @logger = logger || Logger.new(STDOUT) @log_level = log_level || :info if @logger.respond_to?(:formatter=) @logger.formatter = formatter || @logger.formatter || GrapeLogging::Formatters::Default.new end end def perform(params) @logger.send(@log_level, params) end end end grape_logging-1.8.4/lib/grape_logging/timings.rb000066400000000000000000000005521372142360500217060ustar00rootroot00000000000000module GrapeLogging module Timings extend self def db_runtime=(value) Thread.current[:grape_db_runtime] = value end def db_runtime Thread.current[:grape_db_runtime] ||= 0 end def reset_db_runtime self.db_runtime = 0 end def append_db_runtime(event) self.db_runtime += event.duration end end endgrape_logging-1.8.4/lib/grape_logging/util/000077500000000000000000000000001372142360500206625ustar00rootroot00000000000000grape_logging-1.8.4/lib/grape_logging/util/parameter_filter.rb000066400000000000000000000057441372142360500245460ustar00rootroot00000000000000if defined?(::Rails.application) if Gem::Version.new(Rails.version) < Gem::Version.new('6.0.0') class ParameterFilter < ActionDispatch::Http::ParameterFilter def initialize(_replacement, filter_parameters) super(filter_parameters) end end else require "active_support/parameter_filter" class ParameterFilter < ActiveSupport::ParameterFilter def initialize(_replacement, filter_parameters) super(filter_parameters) end end end else # # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb # we could depend on Rails specifically, but that would us way to hefty! # class ParameterFilter def initialize(replacement, filters = []) @replacement = replacement @filters = filters end def filter(params) compiled_filter.call(params) end private def compiled_filter @compiled_filter ||= CompiledFilter.compile(@replacement, @filters) end class CompiledFilter # :nodoc: def self.compile(replacement, filters) return lambda { |params| params.dup } if filters.empty? strings, regexps, blocks = [], [], [] filters.each do |item| case item when Proc blocks << item when Regexp regexps << item else strings << Regexp.escape(item.to_s) end end deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) } deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) } regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty? deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty? new replacement, regexps, deep_regexps, blocks end attr_reader :regexps, :deep_regexps, :blocks def initialize(replacement, regexps, deep_regexps, blocks) @replacement = replacement @regexps = regexps @deep_regexps = deep_regexps.any? ? deep_regexps : nil @blocks = blocks end def call(original_params, parents = []) filtered_params = {} original_params.each do |key, value| parents.push(key) if deep_regexps if regexps.any? { |r| key =~ r } value = @replacement elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r } value = @replacement elsif value.is_a?(Hash) value = call(value, parents) elsif value.is_a?(Array) value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v } elsif blocks.any? key = key.dup if key.duplicable? value = value.dup if value.duplicable? blocks.each { |b| b.call(key, value) } end parents.pop if deep_regexps filtered_params[key] = value end filtered_params end end end end grape_logging-1.8.4/lib/grape_logging/version.rb000066400000000000000000000000541372142360500217160ustar00rootroot00000000000000module GrapeLogging VERSION = '1.8.4' end grape_logging-1.8.4/spec/000077500000000000000000000000001372142360500152655ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/000077500000000000000000000000001372142360500160335ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/grape_logging/000077500000000000000000000000001372142360500206375ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/grape_logging/formatters/000077500000000000000000000000001372142360500230255ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/grape_logging/formatters/rails_spec.rb000066400000000000000000000043211372142360500254760ustar00rootroot00000000000000require 'spec_helper' describe GrapeLogging::Formatters::Rails do let(:formatter) { described_class.new } let(:severity) { "INFO" } let(:datetime) { Time.new('2018', '03', '02', '10', '35', '04', '+13:00') } let(:exception_data) { ArgumentError.new('Message') } let(:hash_data) { { status: 200, time: { total: 272.4, db: 40.63, view: 231.76999999999998 }, method: "GET", path: "/api/endpoint", host: "localhost" } } describe '#call' do context 'string data' do it 'returns a formatted string' do message = formatter.call(severity, datetime, nil, 'value') expect(message).to eq "I [2018-03-02 10:35:04 +1300] INFO -- : value\n" end end context 'exception data' do it 'returns a string with a backtrace' do exception_data.set_backtrace(caller) message = formatter.call(severity, datetime, nil, exception_data) lines = message.split("\n") expect(lines[0]).to eq "I [2018-03-02 10:35:04 +1300] INFO -- : Message (ArgumentError)" expect(lines[1]).to include 'grape_logging' expect(lines.size).to be > 1 end end context 'hash data' do it 'returns a formatted string' do message = formatter.call(severity, datetime, nil, hash_data) expect(message).to eq "Completed 200 OK in 272.4ms (Views: 231.77ms | DB: 40.63ms)\n" end it 'includes params if included (from GrapeLogging::Loggers::FilterParameters)' do hash_data.merge!( params: { "some_param" => { value_1: "123", value_2: "456" } } ) message = formatter.call(severity, datetime, nil, hash_data) lines = message.split("\n") expect(lines.first).to eq ' Parameters: {"some_param"=>{:value_1=>"123", :value_2=>"456"}}' expect(lines.last).to eq "Completed 200 OK in 272.4ms (Views: 231.77ms | DB: 40.63ms)" end end context "unhandled data" do it 'returns the #inspect string representation' do message = formatter.call(severity, datetime, nil, [1, 2, 3]) expect(message).to eq "[1, 2, 3]\n" end end end end grape_logging-1.8.4/spec/lib/grape_logging/loggers/000077500000000000000000000000001372142360500223015ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/grape_logging/loggers/client_env_spec.rb000066400000000000000000000023411372142360500257660ustar00rootroot00000000000000require 'spec_helper' require 'ostruct' describe GrapeLogging::Loggers::ClientEnv do let(:ip) { '10.0.0.1' } let(:user_agent) { 'user agent' } let(:forwarded_for) { "forwarded for" } let(:remote_addr) { "remote address" } context 'forwarded for' do let(:mock_request) do OpenStruct.new(env: { "HTTP_X_FORWARDED_FOR" => forwarded_for }) end it 'sets the ip key' do expect(subject.parameters(mock_request, nil)).to eq(ip: forwarded_for, ua: nil) end it 'prefers the forwarded_for over the remote_addr' do mock_request.env['REMOTE_ADDR'] = remote_addr expect(subject.parameters(mock_request, nil)).to eq(ip: forwarded_for, ua: nil) end end context 'remote address' do let(:mock_request) do OpenStruct.new(env: { "REMOTE_ADDR" => remote_addr }) end it 'sets the ip key' do expect(subject.parameters(mock_request, nil)).to eq(ip: remote_addr, ua: nil) end end context 'user agent' do let(:mock_request) do OpenStruct.new(env: { "HTTP_USER_AGENT" => user_agent }) end it 'sets the ua key' do expect(subject.parameters(mock_request, nil)).to eq(ip: nil, ua: user_agent) end end end grape_logging-1.8.4/spec/lib/grape_logging/loggers/filter_parameters_spec.rb000066400000000000000000000042441372142360500273540ustar00rootroot00000000000000require 'spec_helper' require 'ostruct' describe GrapeLogging::Loggers::FilterParameters do let(:filtered_parameters) { %w[one four] } let(:mock_request) do OpenStruct.new(params: { this_one: 'this one', that_one: 'one', two: 'two', three: 'three', four: 'four' }) end let(:mock_request_with_deep_nesting) do deep_clone = lambda { Marshal.load Marshal.dump mock_request.params } OpenStruct.new( params: deep_clone.call.merge( five: deep_clone.call.merge( deep_clone.call.merge({six: {seven: 'seven', eight: 'eight', one: 'another one'}}) ) ) ) end let(:subject) do GrapeLogging::Loggers::FilterParameters.new filtered_parameters, replacement end let(:replacement) { nil } shared_examples 'filtering' do it 'filters out sensitive parameters' do expect(subject.parameters(mock_request, nil)).to eq(params: { this_one: subject.instance_variable_get('@replacement'), that_one: subject.instance_variable_get('@replacement'), two: 'two', three: 'three', four: subject.instance_variable_get('@replacement'), }) end it 'deeply filters out sensitive parameters' do expect(subject.parameters(mock_request_with_deep_nesting, nil)).to eq(params: { this_one: subject.instance_variable_get('@replacement'), that_one: subject.instance_variable_get('@replacement'), two: 'two', three: 'three', four: subject.instance_variable_get('@replacement'), five: { this_one: subject.instance_variable_get('@replacement'), that_one: subject.instance_variable_get('@replacement'), two: 'two', three: 'three', four: subject.instance_variable_get('@replacement'), six: { seven: 'seven', eight: 'eight', one: subject.instance_variable_get('@replacement'), }, }, }) end end context 'with default replacement' do it_behaves_like 'filtering' end context 'with custom replacement' do let(:replacement) { 'CUSTOM_REPLACEMENT' } it_behaves_like 'filtering' end end grape_logging-1.8.4/spec/lib/grape_logging/loggers/request_headers_spec.rb000066400000000000000000000021531372142360500270240ustar00rootroot00000000000000require 'spec_helper' require 'ostruct' describe GrapeLogging::Loggers::RequestHeaders do let(:mock_request) do OpenStruct.new(env: {HTTP_REFERER: 'http://example.com', HTTP_ACCEPT: 'text/plain'}) end let(:mock_request_with_unhandle_headers) do OpenStruct.new(env: { HTTP_REFERER: 'http://example.com', "PATH_INFO"=>"/api/v1/users" }) end let(:mock_request_with_long_headers) do OpenStruct.new(env: { HTTP_REFERER: 'http://example.com', HTTP_USER_AGENT: "Mozilla/5.0" }) end it 'strips HTTP_ from the parameter' do expect(subject.parameters(mock_request, nil)).to eq({ headers: {'Referer' => 'http://example.com', 'Accept' => 'text/plain'} }) end it 'only handle things which start with HTTP_' do expect(subject.parameters(mock_request_with_unhandle_headers, nil)).to eq({ headers: {'Referer' => 'http://example.com' } }) end it 'substitutes _ with -' do expect(subject.parameters(mock_request_with_long_headers, nil)).to eq({ headers: {'Referer' => 'http://example.com', 'User-Agent' => 'Mozilla/5.0' } }) end end grape_logging-1.8.4/spec/lib/grape_logging/loggers/response_spec.rb000066400000000000000000000012631372142360500255000ustar00rootroot00000000000000require 'spec_helper' require 'ostruct' describe GrapeLogging::Loggers::Response do context 'with a parseable JSON body' do let(:response) do OpenStruct.new(body: [%q{{"one": "two", "three": {"four": 5}}}]) end it 'returns an array of parseable JSON objects' do expect(subject.parameters(nil, response)).to eq({ response: [response.body.first.dup] }) end end context 'with a body that is not parseable JSON' do let(:response) do OpenStruct.new(body: "this is a body") end it 'just returns the body' do expect(subject.parameters(nil, response)).to eq({ response: response.body.dup }) end end end grape_logging-1.8.4/spec/lib/grape_logging/middleware/000077500000000000000000000000001372142360500227545ustar00rootroot00000000000000grape_logging-1.8.4/spec/lib/grape_logging/middleware/request_logger_spec.rb000066400000000000000000000062021372142360500273420ustar00rootroot00000000000000require 'spec_helper' require 'rack' describe GrapeLogging::Middleware::RequestLogger do let(:subject) { request.send(request_method, path) } let(:app) { proc{ [status, {} , ['response body']] } } let(:stack) { described_class.new app, options } let(:request) { Rack::MockRequest.new(stack) } let(:options) { {include: [], logger: logger} } let(:logger) { double('logger') } let(:path) { '/' } let(:request_method) { 'get' } let(:status) { 200 } it 'logs to the logger' do expect(logger).to receive('info') do |arguments| expect(arguments[:status]).to eq 200 expect(arguments[:method]).to eq 'GET' expect(arguments[:params]).to be_empty expect(arguments[:host]).to eq 'example.org' expect(arguments).to have_key :time expect(arguments[:time]).to have_key :total expect(arguments[:time]).to have_key :db expect(arguments[:time]).to have_key :view end subject end [301, 404, 500].each do |the_status| context "when the respnse status is #{the_status}" do let(:status) { the_status } it 'should log the correct status code' do expect(logger).to receive('info') do |arguments| expect(arguments[:status]).to eq the_status end subject end end end %w[info error debug].each do |level| context "with level #{level}" do it 'should log at correct level' do options[:log_level] = level expect(logger).to receive(level) subject end end end context 'with a nil response' do let(:app) { proc{ [500, {} , nil] } } it 'should log "fail" instead of a status' do expect(Rack::MockResponse).to receive(:new) { nil } expect(logger).to receive('info') do |arguments| expect(arguments[:status]).to eq 500 end subject end end context 'additional_loggers' do before do options[:include] << GrapeLogging::Loggers::RequestHeaders.new options[:include] << GrapeLogging::Loggers::ClientEnv.new options[:include] << GrapeLogging::Loggers::Response.new options[:include] << GrapeLogging::Loggers::FilterParameters.new(["replace_me"]) end %w[get put post delete options head patch].each do |the_method| let(:request_method) { the_method } context "with HTTP method[#{the_method}]" do it 'should include additional information in the log' do expect(logger).to receive('info') do |arguments| expect(arguments).to have_key :headers expect(arguments).to have_key :ip expect(arguments).to have_key :response end subject end end end it 'should filter parameters in the log' do expect(logger).to receive('info') do |arguments| expect(arguments[:params]).to eq( "replace_me" => '[FILTERED]', "replace_me_too" => '[FILTERED]', "cant_touch_this" => 'should see' ) end parameters = { 'replace_me' => 'should not see', 'replace_me_too' => 'should not see', 'cant_touch_this' => 'should see' } request.post path, params: parameters end end end grape_logging-1.8.4/spec/spec_helper.rb000066400000000000000000000077461372142360500201210ustar00rootroot00000000000000$:.unshift '.' require 'lib/grape_logging' RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # This option will default to `:apply_to_host_groups` in RSpec 4 (and will # have no way to turn it off -- the option exists only for backwards # compatibility in RSpec 3). It causes shared context metadata to be # inherited by the metadata hash of host groups and examples, rather than # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. =begin # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. config.filter_run_when_matching :focus # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. We recommend # you configure your source control system to ignore this file. config.example_status_persistence_file_path = "spec/examples.txt" # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = 'doc' end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed =end end