├── VERSION ├── .rspec ├── .document ├── .gitignore ├── email_templates └── message.erb ├── Gemfile ├── Rakefile ├── LICENSE.txt ├── Gemfile.lock ├── spec └── logmaster_spec.rb ├── logmaster.gemspec ├── README.md └── lib └── logmaster.rb /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | 3 | # jeweler generated 4 | pkg 5 | 6 | .DS_Store 7 | .ruby-version 8 | -------------------------------------------------------------------------------- /email_templates/message.erb: -------------------------------------------------------------------------------- 1 |

Logger message type: <%= type %>

2 |
3 |
<%= message %>
4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "pony" 4 | 5 | # Add dependencies to develop your gem here. 6 | # Include everything needed to run rake, tests, features, etc. 7 | group :development do 8 | gem "bundler", "~> 1.0" 9 | gem "jeweler", "~> 2.0.1" 10 | gem "github_api", "0.11.3" 11 | end 12 | 13 | group :test do 14 | gem 'rspec' 15 | end 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options 17 | gem.name = "logmaster" 18 | gem.homepage = "http://github.com/snitko/logmaster" 19 | gem.license = "MIT" 20 | gem.summary = %Q{A wrapper around ruby stdlib Logger with emailing capabilities} 21 | gem.description = %Q{A wrapper around ruby stdlib Logger with emailing capabilities} 22 | gem.email = "roman.snitko@gmail.com" 23 | gem.authors = ["Roman Snitko"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Roman Snitko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | addressable (2.3.6) 5 | builder (3.2.2) 6 | descendants_tracker (0.0.4) 7 | thread_safe (~> 0.3, >= 0.3.1) 8 | diff-lcs (1.2.5) 9 | faraday (0.9.0) 10 | multipart-post (>= 1.2, < 3) 11 | git (1.2.8) 12 | github_api (0.11.3) 13 | addressable (~> 2.3) 14 | descendants_tracker (~> 0.0.1) 15 | faraday (~> 0.8, < 0.10) 16 | hashie (>= 1.2) 17 | multi_json (>= 1.7.5, < 2.0) 18 | nokogiri (~> 1.6.0) 19 | oauth2 20 | hashie (3.3.1) 21 | highline (1.6.21) 22 | jeweler (2.0.1) 23 | builder 24 | bundler (>= 1.0) 25 | git (>= 1.2.5) 26 | github_api 27 | highline (>= 1.6.15) 28 | nokogiri (>= 1.5.10) 29 | rake 30 | rdoc 31 | json (1.8.1) 32 | jwt (1.0.0) 33 | mail (2.6.1) 34 | mime-types (>= 1.16, < 3) 35 | mime-types (2.3) 36 | mini_portile (0.6.0) 37 | multi_json (1.10.1) 38 | multi_xml (0.5.5) 39 | multipart-post (2.0.0) 40 | nokogiri (1.6.3.1) 41 | mini_portile (= 0.6.0) 42 | oauth2 (1.0.0) 43 | faraday (>= 0.8, < 0.10) 44 | jwt (~> 1.0) 45 | multi_json (~> 1.3) 46 | multi_xml (~> 0.5) 47 | rack (~> 1.2) 48 | pony (1.11) 49 | mail (>= 2.0) 50 | rack (1.5.2) 51 | rake (10.3.2) 52 | rdoc (4.1.2) 53 | json (~> 1.4) 54 | rspec (3.1.0) 55 | rspec-core (~> 3.1.0) 56 | rspec-expectations (~> 3.1.0) 57 | rspec-mocks (~> 3.1.0) 58 | rspec-core (3.1.4) 59 | rspec-support (~> 3.1.0) 60 | rspec-expectations (3.1.1) 61 | diff-lcs (>= 1.2.0, < 2.0) 62 | rspec-support (~> 3.1.0) 63 | rspec-mocks (3.1.1) 64 | rspec-support (~> 3.1.0) 65 | rspec-support (3.1.0) 66 | thread_safe (0.3.4) 67 | 68 | PLATFORMS 69 | ruby 70 | 71 | DEPENDENCIES 72 | bundler (~> 1.0) 73 | github_api (= 0.11.3) 74 | jeweler (~> 2.0.1) 75 | pony 76 | rspec 77 | -------------------------------------------------------------------------------- /spec/logmaster_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'logmaster' 3 | require 'fileutils' 4 | 5 | class Logger 6 | # Suppress log messages when testing 7 | def add(*args);end 8 | end 9 | 10 | describe Logmaster do 11 | 12 | LOGFILE = File.expand_path(File.dirname(__FILE__) + '/logfile') 13 | 14 | before(:each) do 15 | @logmaster = Logmaster.new(file: LOGFILE) 16 | end 17 | 18 | after(:each) do 19 | FileUtils.rm LOGFILE 20 | end 21 | 22 | it "creates two loggers (stdout and logfile)" do 23 | expect(@logmaster.loggers[0]).to be_kind_of(Logger) 24 | expect(@logmaster.loggers[1]).to be_kind_of(Logger) 25 | end 26 | 27 | it "sets email settings" do 28 | @logmaster.email_config = { to: 'your-email@here.com' } 29 | expect(@logmaster.email_config).to eq({ via: :sendmail, from: 'logmaster@localhost', subject: "Logmaster message", to: 'your-email@here.com' }) 30 | expect(@logmaster.instance_variable_get(:@email_log_level)).to eq(Logger::WARN) 31 | end 32 | 33 | it "sends log messages to each logger" do 34 | @logmaster.loggers.each do |logger| 35 | expect(logger).to receive(:warn).once 36 | end 37 | @logmaster.warn("WARNING bitches!") 38 | end 39 | 40 | it "sends emails when the log level of a log message is appropriate" do 41 | @logmaster.email_config = { to: 'your-email@here.com', log_level: :warn } 42 | 43 | expect(Pony).to receive(:mail).once 44 | 45 | @logmaster.warn("WARNING bitches!") # Should call Pony.mail 46 | @logmaster.email_log_level = Logger::FATAL 47 | @logmaster.warn("WARNING bitches!") # This time shouldn't, log_level is wrong 48 | end 49 | 50 | it "watches for exceptions" do 51 | @logmaster.loggers.each do |logger| 52 | expect(logger).to receive(:fatal).once 53 | end 54 | @logmaster.watch_exceptions do 55 | 1/0 56 | end 57 | end 58 | 59 | it "converts keys in the config settings hash into symbols (config.yml parsing makes them strings initially)" do 60 | @logmaster.email_config = { 'to' => 'your-email@here.com' } 61 | expect(@logmaster.email_config[:to]).not_to be_blank 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /logmaster.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: logmaster 0.2.0 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "logmaster" 9 | s.version = "0.2.0" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib"] 13 | s.authors = ["Roman Snitko"] 14 | s.date = "2015-09-07" 15 | s.description = "A wrapper around ruby stdlib Logger with emailing capabilities" 16 | s.email = "roman.snitko@gmail.com" 17 | s.extra_rdoc_files = [ 18 | "LICENSE.txt", 19 | "README.md" 20 | ] 21 | s.files = [ 22 | ".document", 23 | ".rspec", 24 | "Gemfile", 25 | "Gemfile.lock", 26 | "LICENSE.txt", 27 | "README.md", 28 | "Rakefile", 29 | "VERSION", 30 | "email_templates/message.erb", 31 | "lib/logmaster.rb", 32 | "logmaster.gemspec", 33 | "spec/logmaster_spec.rb" 34 | ] 35 | s.homepage = "http://github.com/snitko/logmaster" 36 | s.licenses = ["MIT"] 37 | s.rubygems_version = "2.4.5" 38 | s.summary = "A wrapper around ruby stdlib Logger with emailing capabilities" 39 | 40 | if s.respond_to? :specification_version then 41 | s.specification_version = 4 42 | 43 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 44 | s.add_runtime_dependency(%q, [">= 0"]) 45 | s.add_development_dependency(%q, ["~> 1.0"]) 46 | s.add_development_dependency(%q, ["~> 2.0.1"]) 47 | s.add_development_dependency(%q, ["= 0.11.3"]) 48 | else 49 | s.add_dependency(%q, [">= 0"]) 50 | s.add_dependency(%q, ["~> 1.0"]) 51 | s.add_dependency(%q, ["~> 2.0.1"]) 52 | s.add_dependency(%q, ["= 0.11.3"]) 53 | end 54 | else 55 | s.add_dependency(%q, [">= 0"]) 56 | s.add_dependency(%q, ["~> 1.0"]) 57 | s.add_dependency(%q, ["~> 2.0.1"]) 58 | s.add_dependency(%q, ["= 0.11.3"]) 59 | end 60 | end 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logmaster 2 | ========= 3 | 4 | An enhanced logger library that handles different types of loggers 5 | (at the moment STDOUT and FILE) and sends emails if email config is provided. 6 | 7 | It can also watch and rescue exceptions logging them as FATAL. 8 | 9 | Here's a usage example listing all of the possible options: 10 | 11 | 12 | logmaster = Logmaster.new( 13 | log_level: Logger::WARN, # Default is Logger::INFO 14 | file: '/var/log/myapp.log', # if nil, will not log into any file 15 | stdout: true, # if false, will not log into STDOUR 16 | raise_exception: false, # if true, will a raise an Exception after logging it 17 | email_config: nil, # see email config options below 18 | name: "Logmaster" # currently useful for the email subjects, 19 | # so you can see who's emailing you 20 | ) 21 | 22 | logmaster.warn("This is a warning message") # logs into file and into the STDOUT 23 | logmaster.info("This is an info message") # doesn't log anything, log level isn't sufficient 24 | 25 | 26 | Sending email notifications 27 | --------------------------- 28 | Logmaster can also send emails. It uses Pony (https://github.com/benprew/pony) to do that. Pony usually 29 | makes use of sendmail, but you can also specify SMTP options. See Pony docs to learn more. In our 30 | example I will use the sendmail (which is default): 31 | 32 | logmaster = Logmaster.new( email_config: { to: 'me@email.foo', from: "logmaster@yourapp.com", log_level: :warn }) 33 | logmaster.warn("Wow, this is another warning!") 34 | 35 | The second line will trigger sending an email to your address. `:log_level` option allows to set which types of 36 | log entries are sent in an email. For example, you may want to log on log_level INFO, but only send emails 37 | when a log entry is WARN or more critical. 38 | 39 | 40 | Wathcing the code for Exceptions 41 | -------------------------------- 42 | You can watch your code for exceptions and then also log them. After you created your logmaster 43 | instance, it's as simple as that: 44 | 45 | logmaster.watch_exceptions do 46 | 1/0 47 | end 48 | 49 | the exception will be rescued and logged as FATAL. If you also wish to actually raise it after it is 50 | logged, don't forget to set `Logmaster#raise_exception` to `true`. 51 | -------------------------------------------------------------------------------- /lib/logmaster.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | class Logmaster 4 | 5 | attr_accessor :loggers, :log_level, :name, :email_config, :raise_exception, :email_log_level, :logstash_config 6 | 7 | def initialize( 8 | log_level: Logger::INFO, 9 | file: nil, # if nil, will not log into any file 10 | stdout: true, # if false, will not log into STDOUR 11 | raise_exception: false, # if true, will a raise an Exception after logging it 12 | email_config: nil, # see email config options for Pony gem 13 | logstash_config: nil, # see https://github.com/dwbutler/logstash-logger#basic-usage 14 | name: "Logmaster" 15 | ) 16 | 17 | @name = name 18 | @raise_exception = raise_exception 19 | @log_level = log_level 20 | @loggers = [] 21 | 22 | self.email_config = email_config if email_config 23 | @logstash_config = logstash_config 24 | 25 | @loggers << ::Logger.new(STDOUT) if stdout 26 | @loggers << ::Logger.new(file, 10, 1024000) if file 27 | if logstash_config 28 | require 'logstash-logger' 29 | @loggers << LogStashLogger.new(**logstash_config) 30 | end 31 | @loggers.each { |l| l.level = @log_level } 32 | 33 | end 34 | 35 | def email_config=(settings) 36 | 37 | require 'pony' 38 | require 'erb' 39 | 40 | @email_config = { via: :sendmail, from: 'logmaster@localhost', subject: "#{@name} message", log_level: :warn } 41 | 42 | # Convert string keys into symbol keys 43 | settings = Hash[settings.map{|(k,v)| [k.to_sym,v]}] 44 | 45 | @email_config.merge!(settings) 46 | if @email_config[:to].nil? 47 | raise "Please specify email addresses of email recipients using :to key in email_config attr (value should be array)" 48 | end 49 | 50 | # Because pony doesn't like some arbitrary options like log_level 51 | # and instead of ignorning them, throws an error. 52 | @email_log_level = Logger.const_get(@email_config[:log_level].to_s.upcase) 53 | @email_config.delete(:log_level) 54 | 55 | end 56 | 57 | def watch_exceptions 58 | raise "Please provide a block to this method" unless block_given? 59 | begin 60 | yield 61 | rescue Exception => e 62 | message = e.class.to_s 63 | message += ": " 64 | message += e.message 65 | message += "\n" 66 | message += "Backtrace:\n" 67 | e.backtrace.each { |l| message += " #{l}\n" } 68 | self.fatal(message) 69 | raise e if @raise_exception 70 | end 71 | end 72 | 73 | private 74 | 75 | def method_missing(name, *args) 76 | 77 | if [:unknown, :fatal, :error, :warn, :info, :debug].include?(name) 78 | 79 | if @email_config && @email_log_level <= Logger.const_get(name.to_s.upcase) 80 | send_email(type: name, message: args[0]) 81 | end 82 | 83 | @loggers.each do |logger| 84 | logger.send(name, *args) 85 | end 86 | 87 | end 88 | 89 | end 90 | 91 | def send_email(type:, message:) 92 | template = ERB.new(File.read(File.expand_path(File.dirname(__FILE__)) + 93 | "/../email_templates/message.erb") 94 | ).result(binding) 95 | 96 | Pony.mail(@email_config.merge({ html_body: template })) 97 | end 98 | 99 | 100 | end 101 | --------------------------------------------------------------------------------