diff --git a/.github/workflows/lumberaxe.yml b/.github/workflows/lumberaxe.yml new file mode 100644 index 00000000..390254d2 --- /dev/null +++ b/.github/workflows/lumberaxe.yml @@ -0,0 +1,13 @@ +name: lumberaxe + +on: + push: + +jobs: + ruby: + uses: ./.github/workflows/_ruby-package.yml + with: + package: ${{ github.workflow }} + ruby: '["2.7.4", "3.1.2"]' + rails: '["7.0.3.1","6.1.6.1","6.0.5.1","5.2.8.1"]' + secrets: inherit diff --git a/docs/README.md b/docs/README.md index bfc2ef5e..a926ad75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,14 @@ power-tools currently contains the following packages (marked for release to rub When included in a Rails application, NitroConfig loads the configuration file at `config/config.yml` within the application directory and makes its values available at `NitroConfig.config`. Config values are loaded based on the Rails environment, permitting the specification of multiple environments' configurations in a single file. +[audit_tracker](https://github.com/powerhome/power-tools/blob/main/packages/audit_tracker/docs/README.md) 💎 + +AuditTracker helps you centralize data tracking configuration to be used across different models. + +[lumberaxe](https://github.com/powerhome/power-tools/blob/main/packages/lumberaxe/docs/README.md) 💎 + +Lumberaxe handles logging output formatting. + ## Installation 🛠 These packages are all meant to install inside of an application and aren't intended to stand alone; currently, they are all published to [RubyGems](https://rubygems.org/) and you can use standard Bundler methods to install them. diff --git a/packages/lumberaxe/.gitignore b/packages/lumberaxe/.gitignore new file mode 100644 index 00000000..2f3de776 --- /dev/null +++ b/packages/lumberaxe/.gitignore @@ -0,0 +1,14 @@ +/.bundle/ +/.yardoc +/_yardoc/ +coverage +pkg +/spec/reports/ +**/tmp/* +!**/tmp/.gitkeep +!tmp/.gitignore +vendor/bundle +*.log +*.sqlite +*.sqlite3 +Gemfile.lock diff --git a/packages/lumberaxe/.rubocop.yml b/packages/lumberaxe/.rubocop.yml new file mode 100644 index 00000000..945569d1 --- /dev/null +++ b/packages/lumberaxe/.rubocop.yml @@ -0,0 +1,10 @@ +inherit_from: .rubocop_todo.yml + +require: + - rubocop-powerhome + +AllCops: + TargetRubyVersion: 2.7 + +Rails: + Enabled: false diff --git a/packages/lumberaxe/.rubocop_todo.yml b/packages/lumberaxe/.rubocop_todo.yml new file mode 100644 index 00000000..3665469a --- /dev/null +++ b/packages/lumberaxe/.rubocop_todo.yml @@ -0,0 +1,7 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --exclude-limit 500` +# on 2022-08-11 19:45:17 UTC using RuboCop version 1.29.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. diff --git a/packages/lumberaxe/Gemfile b/packages/lumberaxe/Gemfile new file mode 100644 index 00000000..972f6bc5 --- /dev/null +++ b/packages/lumberaxe/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +gemspec + +rails_version = ENV.fetch("RAILS_VERSION", ">= 5.2.8.1") + +gem "rails", rails_version diff --git a/packages/lumberaxe/Rakefile b/packages/lumberaxe/Rakefile new file mode 100644 index 00000000..2a2dbf95 --- /dev/null +++ b/packages/lumberaxe/Rakefile @@ -0,0 +1,31 @@ +#!/usr/bin/env rake + +# frozen_string_literal: true + +require "rspec/core/rake_task" +require "rubocop/rake_task" + +begin + require "bundler/setup" +rescue LoadError + puts "You must `gem install bundler` and `bundle install` to run rake tasks" +end +begin + require "yard" + YARD::Rake::YardocTask.new do |t| + t.files = ["lib/**/*.rb"] + t.options = [ + "--no-private", + ] + end +rescue LoadError + warn "Could not require() YARD! Install with 'gem install yard' to get the 'yardoc' task" +end + +Bundler::GemHelper.install_tasks + +RSpec::Core::RakeTask.new(:spec) + +RuboCop::RakeTask.new(:rubocop) + +task default: %i[spec rubocop] diff --git a/packages/lumberaxe/config.ru b/packages/lumberaxe/config.ru new file mode 100644 index 00000000..1265db9f --- /dev/null +++ b/packages/lumberaxe/config.ru @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "rubygems" +require "bundler" + +Bundler.require :default, :development + +Combustion.initialize! :all +run Combustion::Application diff --git a/packages/lumberaxe/doc/dependency_decisions.yml b/packages/lumberaxe/doc/dependency_decisions.yml new file mode 100644 index 00000000..f734baa9 --- /dev/null +++ b/packages/lumberaxe/doc/dependency_decisions.yml @@ -0,0 +1,3 @@ +--- +- - :inherit_from + - https://raw.githubusercontent.com/powerhome/oss-guide/master/license_rules.yml diff --git a/packages/lumberaxe/docs/README.md b/packages/lumberaxe/docs/README.md new file mode 100644 index 00000000..b6db5ea0 --- /dev/null +++ b/packages/lumberaxe/docs/README.md @@ -0,0 +1,23 @@ +# Lumberaxe + +Lumberaxe handles logging output formatting. + +# Usage + +After installing the gem, require it as part of your application configuration. + +```ruby +# application.rb + +require "lumberaxe" +``` + +## Setting JSON logging + +To set up JSON logging on puma, add this to your puma config: + +```ruby +# puma.rb + +log_formatter(&Lumberaxe.puma_formatter) +``` diff --git a/packages/lumberaxe/lib/lumberaxe.rb b/packages/lumberaxe/lib/lumberaxe.rb new file mode 100644 index 00000000..36ce3797 --- /dev/null +++ b/packages/lumberaxe/lib/lumberaxe.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "logger" +require "active_support" +require "lumberaxe/railtie" if defined?(Rails) +require "lumberaxe/logger" +require "lumberaxe/json_formatter" + +module Lumberaxe + def self.puma_formatter(level: "INFO", progname: "puma") + ->(message) do + { + level: level, + time: Time.now, + progname: progname, + message: message, + }.to_json.concat("\r\n") + end + end +end diff --git a/packages/lumberaxe/lib/lumberaxe/json_formatter.rb b/packages/lumberaxe/lib/lumberaxe/json_formatter.rb new file mode 100644 index 00000000..cb690028 --- /dev/null +++ b/packages/lumberaxe/lib/lumberaxe/json_formatter.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "active_support/tagged_logging" + +module Lumberaxe + class JSONFormatter < ::Logger::Formatter + include ActiveSupport::TaggedLogging::Formatter + + def call(severity, time, progname, data) + data = data.is_a?(Hash) ? format_data(data) : { message: data.to_s } + + { + level: severity, + time: time, + progname: progname, + }.merge(data).to_json.concat("\r\n") + end + + def format_data(data) + data.merge!(current_tags.each_with_object({}) do |tag, hash| + if tag.include?("=") + key, value = tag.split("=") + hash[key] = value + else + hash[:tags] ||= [] + hash[:tags] << key + end + hash + end) + end + end +end diff --git a/packages/lumberaxe/lib/lumberaxe/logger.rb b/packages/lumberaxe/lib/lumberaxe/logger.rb new file mode 100644 index 00000000..c4806957 --- /dev/null +++ b/packages/lumberaxe/lib/lumberaxe/logger.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Lumberaxe + class Logger < ::ActiveSupport::Logger + cattr_accessor :log_level + + def initialize(output = $stdout, progname:, level: log_level) + super output + + self.progname = progname + self.level = level + + self.formatter = JSONFormatter.new + extend ActiveSupport::TaggedLogging + end + end +end diff --git a/packages/lumberaxe/lib/lumberaxe/railtie.rb b/packages/lumberaxe/lib/lumberaxe/railtie.rb new file mode 100644 index 00000000..792908d2 --- /dev/null +++ b/packages/lumberaxe/lib/lumberaxe/railtie.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "lograge" + +module Lumberaxe + class Railtie < Rails::Railtie + initializer "lumberaxe.configurations", before: :initialize_logger do |app| + Rails.logger = app.config.logger || Lumberaxe::Logger.new(progname: "app", level: app.config.log_level) + + app.config.log_tags = [ + ->(req) { "request_id=#{req.uuid}" }, + ->(req) { "IP=#{req.remote_ip}" }, + ] + + Lumberaxe::Logger.log_level = app.config.log_level + end + + initializer "lumberaxe.lograge" do + config.lograge.enabled = true + config.lograge.formatter = Lograge::Formatters::Raw.new + config.lograge.custom_options = ->(event) do + { "params" => event.payload[:params].without("controller", "action") } + end + end + end +end diff --git a/packages/lumberaxe/lib/lumberaxe/version.rb b/packages/lumberaxe/lib/lumberaxe/version.rb new file mode 100644 index 00000000..fc4054ac --- /dev/null +++ b/packages/lumberaxe/lib/lumberaxe/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Lumberaxe + VERSION = "0.1.0" +end diff --git a/packages/lumberaxe/lumberaxe.gemspec b/packages/lumberaxe/lumberaxe.gemspec new file mode 100644 index 00000000..151d75ff --- /dev/null +++ b/packages/lumberaxe/lumberaxe.gemspec @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative "lib/lumberaxe/version" + +$LOAD_PATH.push File.expand_path("lib", __dir__) + +Gem::Specification.new do |spec| + spec.name = "lumberaxe" + spec.version = Lumberaxe::VERSION + spec.authors = ["Carlos Palhares", "Jill Klang"] + spec.email = ["chjunior@gmail.com", "jillian.emilie@gmail.com"] + + spec.summary = "Power-ful logging wrapper" + spec.description = "Lumberaxe handles logging output formatting." + spec.homepage = "https://github.com/powerhome/power-tools" + spec.license = "MIT" + spec.required_ruby_version = ">= 2.7" + + spec.metadata["rubygems_mfa_required"] = "true" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/packages/lumberaxe/docs/CHANGELOG.md" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) + end + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency "activesupport", ">= 5.2.8.1" + spec.add_dependency "lograge", "0.10.0" + + spec.add_development_dependency "bundler", "~> 2.1" + spec.add_development_dependency "combustion", "~> 1.3" + spec.add_development_dependency "license_finder", ">= 7.0" + spec.add_development_dependency "parser", ">= 2.5", "!= 2.5.1.1" + spec.add_development_dependency "pry-byebug", "3.9.0" + spec.add_development_dependency "rails", ">= 5.2.8.1" + spec.add_development_dependency "rainbow", "2.2.2" + spec.add_development_dependency "rake", "~> 13.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "rspec-rails", "~> 5.1.2" + spec.add_development_dependency "rubocop-powerhome", "0.5.0" + spec.add_development_dependency "simplecov", "0.15.1" + spec.add_development_dependency "sqlite3", "~> 1.4.2" + spec.add_development_dependency "test-unit", "3.1.5" + spec.add_development_dependency "yard", "0.9.21" +end diff --git a/packages/lumberaxe/mkdocs.yml b/packages/lumberaxe/mkdocs.yml new file mode 100644 index 00000000..b0513be7 --- /dev/null +++ b/packages/lumberaxe/mkdocs.yml @@ -0,0 +1,5 @@ +site_name: Lumberaxe +nav: + - "Home": "README.md" +plugins: + - techdocs-core diff --git a/packages/lumberaxe/spec/internal/app/assets/config/manifest.js b/packages/lumberaxe/spec/internal/app/assets/config/manifest.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/lumberaxe/spec/internal/app/controllers/application_controller.rb b/packages/lumberaxe/spec/internal/app/controllers/application_controller.rb new file mode 100644 index 00000000..7944f9f9 --- /dev/null +++ b/packages/lumberaxe/spec/internal/app/controllers/application_controller.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationController < ActionController::Base +end diff --git a/packages/lumberaxe/spec/internal/app/controllers/campgrounds_controller.rb b/packages/lumberaxe/spec/internal/app/controllers/campgrounds_controller.rb new file mode 100644 index 00000000..5ed14564 --- /dev/null +++ b/packages/lumberaxe/spec/internal/app/controllers/campgrounds_controller.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CampgroundsController < ApplicationController + def create + campground_params = params.require(:campground).permit(:name) + campground = Campground.new(campground_params) + + Rails.logger.warn("Creating campground named #{campground.name}") + + if campground.save + render json: campground, status: :created + else + render json: campground.errors, status: :unprocessable_entity + end + end +end diff --git a/packages/lumberaxe/spec/internal/app/models/application_record.rb b/packages/lumberaxe/spec/internal/app/models/application_record.rb new file mode 100644 index 00000000..71fbba5b --- /dev/null +++ b/packages/lumberaxe/spec/internal/app/models/application_record.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/packages/lumberaxe/spec/internal/app/models/campground.rb b/packages/lumberaxe/spec/internal/app/models/campground.rb new file mode 100644 index 00000000..a620946b --- /dev/null +++ b/packages/lumberaxe/spec/internal/app/models/campground.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Campground < ApplicationRecord + self.table_name = "internal_campgrounds" + + validates :name, presence: true +end diff --git a/packages/lumberaxe/spec/internal/config/database.yml b/packages/lumberaxe/spec/internal/config/database.yml new file mode 100644 index 00000000..b9781191 --- /dev/null +++ b/packages/lumberaxe/spec/internal/config/database.yml @@ -0,0 +1,3 @@ +test: + adapter: sqlite3 + database: db/combustion_test.sqlite diff --git a/packages/lumberaxe/spec/internal/config/routes.rb b/packages/lumberaxe/spec/internal/config/routes.rb new file mode 100644 index 00000000..eaa5ece4 --- /dev/null +++ b/packages/lumberaxe/spec/internal/config/routes.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do + resources :campgrounds, only: :create +end diff --git a/packages/lumberaxe/spec/internal/config/storage.yml b/packages/lumberaxe/spec/internal/config/storage.yml new file mode 100644 index 00000000..b24060b3 --- /dev/null +++ b/packages/lumberaxe/spec/internal/config/storage.yml @@ -0,0 +1,3 @@ +test: + service: Disk + root: /Users/jill/power-tools/packages/lumberaxe/tmp/storage diff --git a/packages/lumberaxe/spec/internal/db/schema.rb b/packages/lumberaxe/spec/internal/db/schema.rb new file mode 100644 index 00000000..a59c0223 --- /dev/null +++ b/packages/lumberaxe/spec/internal/db/schema.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +ActiveRecord::Schema.define do + create_table(:internal_campgrounds, force: true) do |t| + t.string :name + t.timestamps + end +end diff --git a/packages/lumberaxe/spec/internal/log/.gitignore b/packages/lumberaxe/spec/internal/log/.gitignore new file mode 100644 index 00000000..bf0824e5 --- /dev/null +++ b/packages/lumberaxe/spec/internal/log/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/packages/lumberaxe/spec/internal/public/favicon.ico b/packages/lumberaxe/spec/internal/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/packages/lumberaxe/spec/lumberaxe/lumberaxe_spec.rb b/packages/lumberaxe/spec/lumberaxe/lumberaxe_spec.rb new file mode 100644 index 00000000..94dd6edd --- /dev/null +++ b/packages/lumberaxe/spec/lumberaxe/lumberaxe_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Lumberaxe, type: :request do + subject do + post "/campgrounds", params: { campground: { name: "Cloudland Canyon" }, format: :json } + end + + it "tags request_id" do + expect { subject }.to output(/"request_id":/).to_stdout_from_any_process + end + + it "tags IP" do + expect { subject }.to output(/"IP":/).to_stdout_from_any_process + end + + it "contains the defined progname" do + expect { subject }.to output(/"progname":"app"/).to_stdout_from_any_process + end + + it "contains message key/value pairs" do + expect { subject }.to output(/"message":/).to_stdout_from_any_process + end + + it "logs HTTP requests" do + expect { subject }.to output(%r{"method":"POST","path":"/campgrounds"}).to_stdout_from_any_process + end + + it "logs any params" do + expect { subject }.to output(/"params":{"campground":{"name":"Cloudland Canyon"}/).to_stdout_from_any_process + end + + it "logs anything passed to the rails logger" do + expect { subject }.to output(/Creating campground named Cloudland Canyon/).to_stdout_from_any_process + end + + it "logs DB requests" do + expect { subject }.to output(/INSERT INTO/).to_stdout_from_any_process + end + + it "logs error response" do + expect do + post "/campgrounds", params: { campground: { name: nil } } + end.to output(/"status":422/).to_stdout_from_any_process + end +end diff --git a/packages/lumberaxe/spec/rails_helper.rb b/packages/lumberaxe/spec/rails_helper.rb new file mode 100644 index 00000000..59247d9e --- /dev/null +++ b/packages/lumberaxe/spec/rails_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require_relative "spec_helper" +require "lumberaxe/railtie" + +Combustion.initialize! :active_record, :action_controller + +require "rspec/rails" diff --git a/packages/lumberaxe/spec/spec_helper.rb b/packages/lumberaxe/spec/spec_helper.rb new file mode 100644 index 00000000..7f21ad06 --- /dev/null +++ b/packages/lumberaxe/spec/spec_helper.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "bundler" + +Bundler.require :default, :development + +require "lumberaxe" +require "pry-byebug" + +RSpec.configure do |config| + if ENV["CI"] + config.before(:example, :focus) { raise "Should not commit focused specs" } + else + config.filter_run :focus + config.run_all_when_everything_filtered = true + end + config.warnings = false + + config.default_formatter = "doc" if config.files_to_run.one? + + # 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 + + # 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| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + expectations.syntax = :expect + 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| + # Enable only the newer, non-monkey-patching expect syntax. + # For more details, see: + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + mocks.syntax = :expect + + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended. + mocks.verify_partial_doubles = false + end +end diff --git a/packages/lumberaxe/test.sh b/packages/lumberaxe/test.sh new file mode 100755 index 00000000..68be8870 --- /dev/null +++ b/packages/lumberaxe/test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +bin/setup +bin/build +bin/doc +bin/test diff --git a/portal.yml b/portal.yml index 3b5f763d..c3dc4ea9 100644 --- a/portal.yml +++ b/portal.yml @@ -49,3 +49,19 @@ spec: lifecycle: production subcomponentOf: power-tools system: power-application-framework +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: lumberaxe + title: Lumberaxe + description: Handles logging output formatting + annotations: + backstage.io/techdocs-ref: dir:packages/lumberaxe +spec: + type: library + owner: heroes-for-hire + system: nitro + lifecycle: production + subcomponentOf: power-tools + system: power-application-framework