├── Gemfile ├── LICENSE.md ├── README.md ├── Rakefile ├── example ├── .gitignore ├── Gemfile ├── Procfile ├── README.md └── config.ru ├── lib └── rack │ └── http-logger.rb └── rack-http-logger.gemspec /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 – 2019 Mattt (https://mat.tt/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rack::HTTPLogger 2 | 3 | `Rack::HTTPLogger` is Rack middleware 4 | that provides a logging endpoint for your application. 5 | HTTP request parameters are automatically formatted 6 | according to [l2met](https://github.com/ryandotsmith/l2met) 7 | and logged to a specified stream, 8 | such as `STDOUT`. 9 | 10 | This is designed for anyone using Heroku, 11 | which uses [Logplex](https://devcenter.heroku.com/articles/logplex) 12 | to aggregate messages for further monitoring and analytics. 13 | With `Rack::HTTPLogger` remote events, 14 | such as mobile device registrations, 15 | can be collected and processed into your common log stream. 16 | 17 | ## Installation 18 | 19 | ``` 20 | $ gem install rack-http-logger 21 | ``` 22 | 23 | ## Options 24 | 25 | - `stream`: Stream to which lines are logged. _Defaults to `$stdout`_. 26 | - `sync`: Print log lines to stream synchronously (not recommended for applications with high throughput). _Defaults to `true`_ 27 | - `method`: Matched HTTP Method. _Defaults to `LOG`_ 28 | - `path`: Matched URL path. _Defaults to `/`_ 29 | - `source`: Source attribute in log line. _Defaults to `rack-http-logger`_ 30 | 31 | ## Usage 32 | 33 | Rack::HTTPLogger can be run as Rack middleware. 34 | 35 | ### config.ru 36 | 37 | ```ruby 38 | require 'rack/http-logger' 39 | 40 | use Rack::HTTPLogger 41 | ``` 42 | 43 | ## Contact 44 | 45 | [Mattt](https://twitter.com/mattt) 46 | 47 | ## License 48 | 49 | Rack::HTTPLogger is available under the MIT license. 50 | See the LICENSE file for more info. 51 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler.setup 3 | 4 | gemspec = eval(File.read("rack-http-logger.gemspec")) 5 | 6 | task :build => "#{gemspec.full_name}.gem" 7 | 8 | file "#{gemspec.full_name}.gem" => gemspec.files + ["rack-http-logger.gemspec"] do 9 | system "gem build rack-http-logger.gemspec" 10 | end 11 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'rack-http-logger', require: 'rack/http-logger', path: File.join(__FILE__, "../..") 4 | 5 | gem 'thin' 6 | -------------------------------------------------------------------------------- /example/Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec thin start -p $PORT 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Rack::HTTPLogger Example 2 | 3 | ## Instructions 4 | 5 | ```sh 6 | $ cd example 7 | $ bundle 8 | $ foreman start 9 | ``` 10 | -------------------------------------------------------------------------------- /example/config.ru: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | $stdout.sync = true 5 | 6 | use Rack::HTTPLogger 7 | run lambda { [404, {'Content-Type' => 'text/html'}, ['Page Not Found']] } 8 | -------------------------------------------------------------------------------- /lib/rack/http-logger.rb: -------------------------------------------------------------------------------- 1 | module Rack #:nodoc: 2 | class HTTPLogger 3 | VERSION = '0.2.1' 4 | 5 | def initialize(app, options = {}) 6 | @app = app 7 | 8 | @stream = options[:stream] || $stdout 9 | @stream.sync = true unless options.fetch(:sync, true) 10 | @source = options[:source] || "rack-http-logger" 11 | 12 | @method = options[:method] ? "#{options[:method]}".upcase : "LOG" 13 | @path = options[:path] || "/" 14 | end 15 | 16 | def call(env) 17 | request = Rack::Request.new(env) 18 | 19 | return @app.call(env) unless request.request_method == @method and request.path == @path 20 | 21 | if request.media_type == "application/json" and (body = request.body.read).length.nonzero? 22 | log JSON.parse(body) 23 | else 24 | log request.params 25 | end 26 | 27 | [201, {"Content-Type" => "text/plain"}, []] 28 | end 29 | 30 | private 31 | 32 | def log(parameters) 33 | return if parameters.nil? or parameters.empty? 34 | 35 | measures = flatten(parameters).collect{|keys, value| "#{keys.collect(&:to_s).join('.')}=#{value}"} 36 | 37 | @stream.puts ["source=#{@source}", *measures].join(" ") 38 | end 39 | 40 | def flatten(hash, k = []) 41 | return {Array(k) => hash} unless hash.is_a?(Hash) 42 | hash.inject({}){ |h, v| h.merge! flatten(v[-1], k + [v[0]]) } 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /rack-http-logger.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "rack/http-logger" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "rack-http-logger" 7 | s.authors = ["Mattt"] 8 | s.email = "mattt@me.com" 9 | s.license = 'MIT' 10 | s.homepage = "https://mat.tt" 11 | s.version = Rack::HTTPLogger::VERSION 12 | s.platform = Gem::Platform::RUBY 13 | s.summary = "Rack::HTTPLogger" 14 | s.description = "Log arbitrary metrics from HTTP request parameters according to l2met conventions." 15 | 16 | s.add_dependency "rack", "~> 1.5" 17 | 18 | s.add_development_dependency "rake" 19 | 20 | s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/ } 21 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 22 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 23 | s.require_paths = ["lib"] 24 | end 25 | --------------------------------------------------------------------------------