├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .sublimelinterrc ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── docs └── images │ ├── plain.png │ └── shogged.png ├── lib ├── shog.rb └── shog │ ├── extensions.rb │ ├── formatter.rb │ ├── formatters.rb │ ├── formatters │ ├── defaults.rb │ └── requests.rb │ ├── rails.rb │ └── version.rb ├── shog.gemspec └── spec ├── lib └── shog │ └── formatter_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | .env -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format Fuubar -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/LineLength: 2 | Max: 120 3 | 4 | Style/AccessModifierIndentation: 5 | EnforceStyle: indent 6 | 7 | Style/IndentationConsistency: 8 | Enabled: false 9 | 10 | Style/TrailingBlankLines: 11 | Enabled: false 12 | 13 | Style/EmptyLinesAroundBody: 14 | Enabled: false 15 | 16 | Style/StringLiterals: 17 | Enabled: false 18 | 19 | Style/SpaceInsideParens: 20 | Enabled: false 21 | 22 | Style/SpaceBeforeBlockBraces: 23 | Enabled: false 24 | 25 | Style/SpaceInsideBlockBraces: 26 | Enabled: false 27 | 28 | Style/SpaceInsideBrackets: 29 | Enabled: false 30 | 31 | Style/SingleLineBlockParams: 32 | Enabled: false 33 | 34 | Style/SingleSpaceBeforeFirstArg: 35 | Enabled: false 36 | 37 | Style/TrailingComma: 38 | Enabled: false 39 | 40 | Style/SpaceAfterComma: 41 | Enabled: false 42 | 43 | Style/ClassAndModuleChildren: 44 | EnforcedStyle: nested 45 | 46 | Style/PercentLiteralDelimiters: 47 | PreferredDelimiters: 48 | '%': '{}' 49 | '%i': '{}' 50 | '%q': '{}' 51 | '%Q': '{}' 52 | '%r': '{}' 53 | '%s': '{}' 54 | '%w': '{}' 55 | '%W': '{}' 56 | '%x': '{}' 57 | 58 | Lint/AssignmentInCondition: 59 | Enabled: false 60 | 61 | Lint/AmbiguousRegexpLiteral: 62 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.4 2 | -------------------------------------------------------------------------------- /.sublimelinterrc: -------------------------------------------------------------------------------- 1 | { 2 | "source": "http://sublimelinter.readthedocs.org/en/latest/index.html", 3 | "linters": { 4 | "rubocop" : { 5 | "args" : [ 6 | "-R" 7 | ] 8 | }, 9 | "annotations" : { 10 | "warnings": ["TODO", "README"], 11 | "errors": ["FIXME", "HACK", "OMG!", "HERE BE DRAGONS" ] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --protected 2 | --no-private 3 | --no-cache 4 | --hide-void-return 5 | --output-dir /tmp/shog_docs 6 | --db /tmp/shog_yardoc 7 | --markup markdown -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | See https://github.com/phallguy/shog/releases -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in shog.gemspec 4 | gemspec 5 | 6 | 7 | gem 'guard', '~> 2.12.5' 8 | gem 'spring' 9 | gem 'byebug' 10 | gem 'pry-byebug' 11 | gem 'guard-rspec' 12 | gem 'fuubar' 13 | gem 'foreman' 14 | gem 'yard' 15 | gem 'redcarpet' 16 | gem 'simplecov', github: "colszowka/simplecov" 17 | 18 | gem "codeclimate-test-reporter", group: :test, require: nil -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :rspec, cmd: "bundle exec rspec" do 2 | require "guard/rspec/dsl" 3 | dsl = Guard::RSpec::Dsl.new(self) 4 | 5 | # Feel free to open issues for suggestions and improvements 6 | 7 | # RSpec files 8 | rspec = dsl.rspec 9 | watch(rspec.spec_helper) { rspec.spec_dir } 10 | watch(rspec.spec_support) { rspec.spec_dir } 11 | watch(rspec.spec_files) 12 | 13 | # Ruby files 14 | ruby = dsl.ruby 15 | dsl.watch_spec_files_for(ruby.lib_files) 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Paul Alexander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | yard: sh -c 'rm -rf /tmp/beakeared_com_yardoc && yard server --reload' 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shog 2 | 3 | [![Gem Version](https://badge.fury.io/rb/shog.svg)](http://badge.fury.io/rb/shog) 4 | [![Code Climate](https://codeclimate.com/github/phallguy/shog.png)](https://codeclimate.com/github/phallguy/shog) 5 | [![Circle CI](https://circleci.com/gh/phallguy/shog.svg?style=svg)](https://circleci.com/gh/phallguy/shog) 6 | 7 | (Also check [scorpion](http://github.com/phallguy/scorpion) for a light-weight IoC container) 8 | 9 | Simple colored logging for rails 4 and 5 apps. 10 | 11 | There are plenty of logging frameworks for making tags (like timestamp, log 12 | level, etc.) more colorful - but what about the details in the line of text? 13 | What about the HTTP method used to make the request? What about the render 14 | times? 15 | 16 | Shog adds colors to highlight context and make it easier to pick out the 17 | important parts of the message. Unformatted logs like this 18 | 19 | ![Plain Logs](docs/images/plain.png) 20 | 21 | become easy to ready and process logs like this 22 | 23 | ![Shogged Logs](docs/images/shogged.png) 24 | 25 | ## Using Shog 26 | 27 | Just add the following to your Gemfile to enable shiny output logs. 28 | 29 | ``` 30 | gem 'shog' 31 | ``` 32 | 33 | Shog comes built in with some sane defaults for rails apps. But you can 34 | customize the output to match additional log message by your app, or to use 35 | different options in production. 36 | 37 | To change the defaults, add `shog.rb` to your `config/initializers` folder 38 | 39 | ```ruby 40 | Shog.configure do 41 | if ::Rails.env.production? 42 | reset_config! 43 | timestamp 44 | end 45 | 46 | match /execution expired/ do |msg,matches| 47 | # Highlight timeout errors 48 | msg.red 49 | end 50 | end 51 | ``` 52 | 53 | See [Shog::Formatter#configure](lib/shog/formatter.rb) for more configuration options. 54 | 55 | ## Contributing 56 | 57 | 1. Fork it ( https://github.com/phallguy/shog/fork ) 58 | 2. Create your feature branch (`git checkout -b my-new-feature`) 59 | 3. Commit your changes (`git commit -am 'Add some feature'`) 60 | 4. Push to the branch (`git push origin my-new-feature`) 61 | 5. Create a new Pull Request 62 | 63 | 64 | # License 65 | 66 | [The MIT License (MIT)](http://opensource.org/licenses/MIT) 67 | 68 | Copyright (c) 2014 Paul Alexander 69 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /docs/images/plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phallguy/shog/294637aa2103d93a82e6f1df74ea35deb0dc04eb/docs/images/plain.png -------------------------------------------------------------------------------- /docs/images/shogged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phallguy/shog/294637aa2103d93a82e6f1df74ea35deb0dc04eb/docs/images/shogged.png -------------------------------------------------------------------------------- /lib/shog.rb: -------------------------------------------------------------------------------- 1 | require 'shog/version' 2 | require 'shog/formatter' 3 | require 'shog/formatters' 4 | require 'shog/rails' 5 | require 'shog/extensions' 6 | 7 | module Shog 8 | 9 | # Set up formatting options for the default rails logger. 10 | # @see Shog::Formatter#configure 11 | def self.configure(&block) 12 | formatter = ::Rails.logger.formatter 13 | unless formatter.is_a? Shog::Formatter 14 | formatter = ::Rails.logger.formatter = Shog::Formatter.new 15 | end 16 | formatter.configure &block 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/shog/extensions.rb: -------------------------------------------------------------------------------- 1 | module Shog 2 | module Extensions 3 | module Colored 4 | extend self 5 | 6 | # Removes any ASCII color encoding 7 | def uncolorize 8 | # http://refiddle.com/18rj 9 | gsub /\e\[\d+(;\d+)*m/, '' 10 | end 11 | 12 | 13 | end 14 | end 15 | end 16 | 17 | String.send :include, Shog::Extensions::Colored -------------------------------------------------------------------------------- /lib/shog/formatter.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/tagged_logging' 2 | require 'colorize' 3 | 4 | module Shog 5 | 6 | # A rails logger formatter that spices up the log message adding color to 7 | # and context to log messages. 8 | # 9 | # Shog automatically overrides the default formatter in your rails app. Use 10 | # {Shog.configure} to configure the default logger. 11 | class Formatter < ::ActiveSupport::Logger::SimpleFormatter 12 | 13 | include ActiveSupport::TaggedLogging::Formatter 14 | 15 | def initialize 16 | reset_config! 17 | end 18 | 19 | # Called by the logger to prepare a message for output. 20 | # @return [String] 21 | def call( severity, time, progname, msg ) 22 | return if msg.blank? || _silence?( msg ) 23 | 24 | msg = [ 25 | _tagged( time, :timestamp ), 26 | _tagged( progname, :progname ), 27 | formatted_severity_tag( severity ), 28 | formatted_message( severity, msg ) 29 | ].compact.join(" ") 30 | 31 | super severity, time, progname, msg 32 | end 33 | 34 | # Formats the message according to the configured {#match} blocks. 35 | # 36 | # @param [String] msg to format. 37 | # @return [String] the formatted message. 38 | def formatted_message( severity, msg ) 39 | msg = String === msg ? msg : msg.inspect 40 | 41 | if args = _matched( msg ) 42 | args.first.call msg, args.last 43 | elsif proc = configuration[:severities][severity] 44 | proc.call msg 45 | else 46 | msg 47 | end 48 | end 49 | 50 | # Formats the severity indicator prefixed before each line when writing to 51 | # the log. 52 | # 53 | # @param [String] the severity of the message (ex DEBUG, WARN, etc.) 54 | # @return [String] formatted version of the severity 55 | def formatted_severity_tag( severity ) 56 | length = configuration[:severity_tags][:_length] ||= begin 57 | configuration[:severity_tags].reduce(0){ |l,(k,_)| [k.length,l].max } 58 | end 59 | 60 | return if length == 0 61 | 62 | padded_severity = severity.ljust length 63 | 64 | formatted = if proc = configuration[:severity_tags][severity] 65 | proc.call padded_severity 66 | else 67 | padded_severity 68 | end 69 | _tagged formatted, :severity_tags 70 | end 71 | 72 | # Formats a time value expressed in ms, adding color to highlight times 73 | # outside the expected range. 74 | # 75 | # If `time` is more than `expected` it's highlighted yellow. If it's more 76 | # than double it's highlighted red. 77 | # 78 | # @param [String] time in ms. 79 | # @param [Float] expected maximum amount of time it should have taken. 80 | # @return [String] the formatted time. 81 | def format_time( time, expected = 30 ) 82 | timef = time.uncolorize.to_f 83 | case 84 | when timef > expected * 2 then time.to_s.uncolorize.red 85 | when timef > expected then time.to_s.uncolorize.yellow 86 | else time 87 | end 88 | end 89 | 90 | 91 | # ========================================================================== 92 | # @!group Configuration 93 | 94 | # Set up log message formatting for this formatter. 95 | # 96 | # @yield and executes the block where self is this formatter. 97 | # @return [Formatter] self. 98 | # 99 | # @example 100 | # Formatter.new.configure do 101 | # with :defaults 102 | # timestamp 103 | # severity(:error){ |msg| msg.red } 104 | # severity(:fatal){ |msg| "\b#{msg}".red } 105 | # end 106 | def configure( &block ) 107 | instance_eval( &block ) 108 | self 109 | end 110 | 111 | # Format the severity indicator tagged before each line. To format the 112 | # actual message itself use {#severity}. 113 | # 114 | # @overload severity_tag( level, proc ) 115 | # @param [String,Symbol] level to format. 116 | # @param [#call(level)] proc that receives the log level and returns the 117 | # reformatted level. 118 | # 119 | # @overload severity_tag( level ) 120 | # @param [String,Symbol] level to format. 121 | # @yieldparam level [String] the log level to reformat. 122 | # @yieldreturn [String] the reformatted level. 123 | # 124 | # @return [Formatter] self. 125 | # 126 | # @example 127 | # configure do 128 | # severity_tag(:warn){|level| level.yellow } 129 | # severity_tag(:error){|level| level.red } 130 | # end 131 | def severity_tag( level, proc = nil, &block ) 132 | proc ||= block 133 | configuration[:severity_tags][ level.to_s.upcase ] = proc 134 | self 135 | end 136 | 137 | # Provide default formatting for messages of the given severity when 138 | # a {#match} is not found. 139 | # 140 | # @overload severity( level, proc ) 141 | # @param [String,Symbol] level to format. 142 | # @param [#call(msg)] proc that receives the message and returns the 143 | # reformatted message. 144 | # @overload severity( level ) 145 | # @param [String,Symbol] level to format. 146 | # @yieldparam msg [String] the message to reformat. 147 | # @yieldreturn [String] the reformatted message. 148 | # 149 | # @return [Formatter] self. 150 | # 151 | # @example 152 | # configure do 153 | # severity(:fatal){ |msg| msg.white_on_red } 154 | # end 155 | def severity( level, proc = nil, &block ) 156 | proc ||= block 157 | configuration[:severities][ level.to_s.upcase ] = proc 158 | self 159 | end 160 | 161 | # Resets any previously configured formatting settings. 162 | # @return [Formatter] self. 163 | def reset_config! 164 | @configuration = { 165 | severity_tags: {}, 166 | severities: {}, 167 | matchers: {}, 168 | silencers: [] 169 | } 170 | self 171 | end 172 | 173 | # Re-format any log messages that match the given `pattern`. 174 | # 175 | # @overload match( pattern, proc) 176 | # @param [Regexp] pattern to match against the log message. 177 | # @param [#call(message,last_match)] proc a callable object that receives 178 | # the message and the last match and re-formats the message. 179 | # 180 | # @overload match( pattern ) 181 | # @param [Regexp] pattern to match against the log message. 182 | # @yieldparam message [String] the matched log message. 183 | # @yieldparam last_match [MatchData] the regex matches. 184 | # @yieldreturn [String] the re-formatted message. 185 | # 186 | # @example 187 | # configure do 188 | # match /GET (?
.*)/ do |message,last_match| 189 | # "GETTING -> #{last_match['address'].green}" 190 | # end 191 | # end 192 | # @return [Formatter] self. 193 | def match( pattern, proc = nil, &block ) 194 | proc ||= block 195 | configuration[:matchers][pattern] = proc 196 | self 197 | end 198 | 199 | # When a log message matches the given `pattern` don't log it. 200 | # 201 | # @param [Regexp] pattern to match. 202 | # 203 | # @return [Formatter] self. 204 | # 205 | # @example 206 | # configure do 207 | # silence /assets\/bootstrap/ 208 | # end 209 | def silence( pattern ) 210 | configuration[:silencers] << pattern 211 | self 212 | end 213 | 214 | # Use configuration defined in the given module. 215 | # 216 | # @param [Symobl,#configure] mod the name of the shog module to use or an 217 | # object that responds to `#configure`. 218 | # 219 | # @return [Formatter] self. 220 | # 221 | # When `mod` is a symobl, it loads one of the modules from 222 | # {Shog::Formatters} and uses any configuration options sepcified in that 223 | # module. 224 | # 225 | # Otherwise `mod` must respond to `#configure` taking a single argument - 226 | # this formatter. 227 | # 228 | # @example Built-in Formatters 229 | # configure do 230 | # with :defaults 231 | # with :requests 232 | # end 233 | # 234 | # @example Custom Shared Formatters 235 | # module MyFormatters 236 | # def self.configure( formatter ) 237 | # formatter.configure do 238 | # timestamp 239 | # end 240 | # end 241 | # end 242 | # 243 | # configure do 244 | # with MyFormatters 245 | # end 246 | def with( mod ) 247 | unless mod.is_a? Module 248 | mod = "Shog::Formatters::#{mod.to_s.camelize}".constantize 249 | end 250 | 251 | mod.configure self 252 | self 253 | end 254 | 255 | # Include timestamp in logged messages. 256 | # @param [Boolean] enable or disable timestamping of log messages. 257 | # @return [Formatter] self. 258 | def timestamp( enable = true ) 259 | configuration[:timestamp] = enable 260 | self 261 | end 262 | 263 | # Include the progname in logged messages. 264 | # @param [Boolean] enable or disable tagging with the prog name of log messages. 265 | # @return [Formatter] self. 266 | def progname( enable = true ) 267 | configuration[:progname] = enable 268 | self 269 | end 270 | 271 | # @!endgroup 272 | 273 | private 274 | 275 | attr_accessor :configuration 276 | 277 | def _matched( msg ) 278 | msg = msg.uncolorize 279 | if matched = configuration[:matchers].find do |pattern,_| 280 | pattern === msg 281 | end 282 | [matched.last, Regexp.last_match] 283 | end 284 | end 285 | 286 | def _tagged( val, config_key ) 287 | return unless configuration[config_key] 288 | "[#{val}]" 289 | end 290 | 291 | def _silence?( msg ) 292 | configuration[:silencers].any?{|p| p === msg } 293 | end 294 | end 295 | end 296 | -------------------------------------------------------------------------------- /lib/shog/formatters.rb: -------------------------------------------------------------------------------- 1 | require 'shog/formatters/defaults' 2 | require 'shog/formatters/requests' -------------------------------------------------------------------------------- /lib/shog/formatters/defaults.rb: -------------------------------------------------------------------------------- 1 | module Shog 2 | module Formatters 3 | 4 | # Provide common default log formatting options. 5 | module Defaults 6 | module_function 7 | 8 | # @see Shog::Formatter#configure 9 | # @see Shog::Formatter#with 10 | def configure( formatter ) 11 | formatter.configure do 12 | severity_tag( :debug ) { |msg| msg.black.bold } 13 | severity_tag( :warn ) { |msg| msg.yellow } 14 | severity_tag( :error ) { |msg| msg.red } 15 | severity_tag( :fatal ) { |msg| msg.white.on_red } 16 | 17 | severity( :error ){ |msg| msg.red } 18 | severity( :fatal ){ |msg| msg.red } 19 | end 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /lib/shog/formatters/requests.rb: -------------------------------------------------------------------------------- 1 | module Shog 2 | module Formatters 3 | # Provide common log formatting options for rails request logs such as 4 | # controller names views, and render times. 5 | module Requests 6 | module_function 7 | 8 | # @see Shog::Formatter#configure 9 | # @see Shog::Formatter#with 10 | def configure( formatter ) 11 | formatter.configure do 12 | 13 | # Highlight HTTP request methods 14 | match /Started\s+(?PUT|PATCH|GET|POST|DELETE)\s+(?"[^"]*")[^\d\.]+(?[\d\.]+)(?