diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b04a8c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..20e09ea --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +sudo: false +language: ruby +rvm: + - 2.3.3 +before_install: gem install bundler -v 1.16.1 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..24bb1f2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at m.ooba@ashita-team.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..beea53b --- /dev/null +++ b/Gemfile @@ -0,0 +1,11 @@ +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in lograge_activejob.gemspec +gemspec + +group :test do + gem 'pry' + gem 'activejob', '~> 5' +end diff --git a/README.md b/README.md index 7d93cf2..366f9b2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ -# lograge_activejob -Lograge for ActiveJobs. +# LogrageActivejob + +Add ActiveJobs log for Lograge + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'lograge' +gem 'lograge_activejob' +``` + +Please remember about [Lograge configuration](https://github.com/roidrage/lograge#installation). + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install lograge_activejob + +# Custom setup + +You can configure additional fields, which will be logged for every exception. + +```ruby +# config/initializers/lograge_activejob.rb +Rails.application.configure do + config.lograge_activejob.custom_options = lambda do |event| + { + event_time: event.time.iso8601, + status: event.payload[:exception_object].blank? ? 200 : 500 + } + end +end +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also 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`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/masaru-tech/lograge_activejob. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b7e9ed5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..2f01a7e --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "lograge_activejob" + +# 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(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/lograge_activejob.rb b/lib/lograge_activejob.rb new file mode 100644 index 0000000..0a210f9 --- /dev/null +++ b/lib/lograge_activejob.rb @@ -0,0 +1,24 @@ +require 'lograge_activejob/version' +require 'lograge_activejob/log_subscriber' + +module LogrageActivejob + module_function + + mattr_writer :custom_options + self.custom_options = nil + + def custom_options(event) + if @@custom_options.respond_to?(:call) + @@custom_options.call(event) + else + @@custom_options + end + end + + def setup(app) + LogrageActivejob.custom_options = app.config.lograge_activejob.custom_options + LogrageActivejob::LogSubscriber.attach_to :active_job + end +end + +require 'lograge_activejob/railtie' if defined?(Rails) diff --git a/lib/lograge_activejob/log_subscriber.rb b/lib/lograge_activejob/log_subscriber.rb new file mode 100644 index 0000000..e20c101 --- /dev/null +++ b/lib/lograge_activejob/log_subscriber.rb @@ -0,0 +1,43 @@ +require 'active_support/log_subscriber' + +module LogrageActivejob + class LogSubscriber < ActiveSupport::LogSubscriber + def perform(event) + data = initial_data(event) + data.merge!(custom_options(event)) + formatted_message = Lograge.formatter.call(data) + logger.send(Lograge.log_level, formatted_message) + end + + private + def logger + Lograge.logger.presence || super + end + + def initial_data(event) + payload = event.payload + job = payload[:job] + ex = payload[:exception_object] + + { + event_name: event.name, + job_class: job.class.name, + job_id: job.job_id, + adapter_class: event.payload[:adapter].class.name.demodulize, + queue_name: job.queue_name, + args: job.arguments, + duration: event.duration.round(2), # ms + message: ex&.message, + backtrace: ex&.backtrace, + }.compact + end + + def custom_options(event) + LogrageActivejob.custom_options(event) || {} + end + + def scheduled_at(job) + Time.at(job.scheduled_at).utc if job.scheduled_at + end + end +end diff --git a/lib/lograge_activejob/railtie.rb b/lib/lograge_activejob/railtie.rb new file mode 100644 index 0000000..03d2815 --- /dev/null +++ b/lib/lograge_activejob/railtie.rb @@ -0,0 +1,13 @@ +require 'rails/railtie' + +module LogrageActivejob + Config = Struct.new(:custom_options) + + class Railtie < Rails::Railtie + config.lograge_activejob = Config.new + + config.after_initialize do |app| + LogrageActivejob.setup(app) if app.config.lograge.enabled + end + end +end diff --git a/lib/lograge_activejob/version.rb b/lib/lograge_activejob/version.rb new file mode 100644 index 0000000..39a9baf --- /dev/null +++ b/lib/lograge_activejob/version.rb @@ -0,0 +1,3 @@ +module LogrageActivejob + VERSION = '0.1.0' +end diff --git a/lograge_activejob.gemspec b/lograge_activejob.gemspec new file mode 100644 index 0000000..5362383 --- /dev/null +++ b/lograge_activejob.gemspec @@ -0,0 +1,31 @@ + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'lograge_activejob/version' + +Gem::Specification.new do |spec| + spec.name = 'lograge_activejob' + spec.version = LogrageActivejob::VERSION + spec.authors = ['MasaruTech'] + spec.email = ['masaru.tech@gmail.com'] + + spec.summary = 'Lograge for ActiveJobs.' + spec.description = 'Lograge for ActiveJobs.' + spec.homepage = 'https://github.com/masaru-tech/lograge_activejob' + spec.license = 'MIT' + + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + spec.add_runtime_dependency 'activesupport', '>= 4', '< 5.3' + spec.add_runtime_dependency 'railties', '>= 4', '< 5.3' + spec.add_runtime_dependency 'lograge', '< 1.0' + + spec.add_development_dependency 'bundler', '~> 1.16' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'rspec', '~> 3.0' +end diff --git a/spec/lograge_activejob_log_subscriber_spec.rb b/spec/lograge_activejob_log_subscriber_spec.rb new file mode 100644 index 0000000..000dcc2 --- /dev/null +++ b/spec/lograge_activejob_log_subscriber_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' +require 'logger' +require 'active_job' +require 'lograge' + +RSpec.describe LogrageActivejob::LogSubscriber do + class TestJob < ActiveJob::Base; end + + let(:log_output) { StringIO.new } + let(:logger) do + Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } } + end + + let(:subscriber) { LogrageActivejob::LogSubscriber.new } + + let(:event) do + ActiveSupport::Notifications::Event.new( + 'perform.active_job', + Time.now, + Time.now + 1.second, + 1, + adapter: ActiveJob::QueueAdapters::AsyncAdapter.new, + job: TestJob.new + ) + end + + before do + Lograge.logger = logger + end + + context 'when perform an action with lograge output' do + before do + Lograge.formatter = Lograge::Formatters::KeyValue.new + end + + it 'includes the job_class' do + subscriber.perform(event) + expect(log_output.string).to include('job_class=TestJob ') + end + + it 'includes the event_name' do + subscriber.perform(event) + expect(log_output.string).to include('event_name=perform.active_job ') + end + + it 'includes the duration' do + subscriber.perform(event) + expect(log_output.string).to match(/duration=[\d\.]/) + end + + it 'includes the job_id' do + subscriber.perform(event) + expect(log_output.string).to match(/job_id=[a-z0-9\-]{1,} /) + end + + it 'includes the adapter_class' do + subscriber.perform(event) + expect(log_output.string).to include('adapter_class=AsyncAdapter ') + end + + it 'includes the queue_name' do + subscriber.perform(event) + expect(log_output.string).to include('queue_name=default ') + end + + it 'includes the args' do + subscriber.perform(event) + expect(log_output.string).to match(/args=\[.*\] /) + end + end + + context 'with custom_options configured for lograge output' do + before do + Lograge.formatter = Lograge::Formatters::KeyValue.new + end + + + it 'includes the event_time' do + LogrageActivejob.custom_options = ->(_event) { { data: 'value' } } + subscriber.perform(event) + expect(log_output.string).to include('data=value') + end + end +end diff --git a/spec/lograge_activejob_spec.rb b/spec/lograge_activejob_spec.rb new file mode 100644 index 0000000..37e6f2b --- /dev/null +++ b/spec/lograge_activejob_spec.rb @@ -0,0 +1,5 @@ +RSpec.describe LogrageActivejob do + it 'has a version number' do + expect(LogrageActivejob::VERSION).not_to be nil + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..aed61ec --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,14 @@ +require 'bundler/setup' +require 'lograge_activejob' + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end