diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 9ac31de..0a07e18 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -21,4 +21,8 @@ jobs: bash -c " rubocop --require code_scanning --format CodeScanning::SarifFormatter -o rubocop.sarif [[ $? -ne 2 ]] - " \ No newline at end of file + " + - name: Upload Sarif output + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: rubocop.sarif \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1340a..e7ab9b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Telemetry::Logger +## v0.2.0 +* Adding new module called `Telemetry::Logger::ExceptionHandler` +* Adding methods for `exception` and magic for detecting `ElasticAPM` and `OpenTelemetry` +* Adding `Upload Sarif output` step to `rubocop` GitHub action +* Adding `SECURITY.md` file + ## v0.1.1 * Fixing issue with color option using the wrong method name * Removing format logger from the setup command diff --git a/README.md b/README.md index eadee99..84d61a2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,49 @@ # Telemetry::Logger A generic gem to handle logging for all other telemetry gems -Example +#### Setting up the logger +```ruby +Telemetry::Logger.setup(level: 'warn', color: false, log_file: './telemetry.log') + +opts = { + include_pid: false, + level: 'info', + log_file: nil, + color: true, + application: 'telemetry', + app_version: Telemetry::Logger::VERSION +} +``` + +#### Example Logging ```ruby Telemetry::Logger.setup(level: 'info') Telemetry::Logger.info 'test info' + Telemetry::Logger.debug 'test debug' Telemetry::Logger.warn 'test warn' Telemetry::Logger.error 'test error' Telemetry::Logger.fatal 'test fatal' Telemetry::Logger.unknown 'test unknown' ``` + +#### Example Exception Tracking +Instead of repeating the same error method all over the place for exceptions, you can use the `exception` method to +save on complexity and automatically report exceptions to supported APMs + +```ruby +Telemetry::Logger.setup(level: 'info') +Telemetry::Logger.exception(StandardError.new('test error'), level: 'warn') +Telemetry::Logger.exception(StandardError.new('test error'), level: 'fatal') +Telemetry::Logger.exception(StandardError.new('test error'), handled: true) +Telemetry::Logger.exception(StandardError.new('test error'), backtrace: true) + +# options for exception method +opts = { + level: 'error', # sets the log level + handled: true, # tells the apms if we handled this exception + backtrace: true, # should we log the backtrace? + backtrace_limit: 20, # how many lines should we limit the backtrace to? + raise: false, # should we reraise this exception instead of swallowing it? +} +``` \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4136a70 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 0.x.x | :white_check_mark: | + + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/lib/telemetry/logger.rb b/lib/telemetry/logger.rb index 0f4002d..ac1bb2a 100644 --- a/lib/telemetry/logger.rb +++ b/lib/telemetry/logger.rb @@ -2,6 +2,7 @@ require 'telemetry/logger/defaults' require 'telemetry/logger/builder' require 'telemetry/logger/methods' +require 'telemetry/logger/exception_handler' module Telemetry module Logger @@ -9,8 +10,10 @@ class << self include Telemetry::Logger::Defaults include Telemetry::Logger::Methods include Telemetry::Logger::Builder + include Telemetry::Logger::ExceptionHandler def setup(level: 'info', **opts) + @opts = opts output(**opts) self.log_level = level self diff --git a/lib/telemetry/logger/exception_handler.rb b/lib/telemetry/logger/exception_handler.rb new file mode 100644 index 0000000..3956cd4 --- /dev/null +++ b/lib/telemetry/logger/exception_handler.rb @@ -0,0 +1,33 @@ +module Telemetry + module Logger + module ExceptionHandler + def exception(exc, level: 'error', backtrace: true, backtrace_limit: 20, **opts) + level = 'unknown' unless %w[debug info warn error fatal].include? level.to_s + Telemetry::Logger.send(level, "#{exc.class}: #{exc.message}") + Telemetry::Logger.send(level, exc.backtrace[0..backtrace_limit]) if backtrace && !exc.backtrace.nil? + + send_to_apm(exc, level: level) + + raise(exc) if opts[:raise] + end + + def send_to_apm(exc, **opts) + handled = opts[:handled] || %w[info debug].include?(opts[:level]) + ::ElasticAPM.report(exc, handled: handled) if elastic_apm? + ::OpenTelemetry.handle_error(exception: exc, message: exc.message) if open_telemetry? + end + + def elastic_apm? + @elastic_apm unless @elastic_apm.nil? + + @elastic_apm = Kernel.const_defined? 'ElasticAPM' + end + + def open_telemetry? + @open_telemetry unless @open_telemetry.nil? + + @open_telemetry = Kernel.const_defined? 'OpenTelemetry' + end + end + end +end diff --git a/lib/telemetry/logger/version.rb b/lib/telemetry/logger/version.rb index 6d25e22..53324fd 100644 --- a/lib/telemetry/logger/version.rb +++ b/lib/telemetry/logger/version.rb @@ -1,5 +1,5 @@ module Telemetry module Logger - VERSION = '0.1.1'.freeze + VERSION = '0.2.0'.freeze end end diff --git a/spec/exception_handler_spec.rb b/spec/exception_handler_spec.rb new file mode 100644 index 0000000..023674c --- /dev/null +++ b/spec/exception_handler_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' +require 'telemetry/logger/exception_handler' + +RSpec.describe Telemetry::Logger::ExceptionHandler do + it { should be_a Module } + before :all do + @class = Class.new do + # include Telemetry::Logger::ExceptionHandler + def initialize + extend Telemetry::Logger::ExceptionHandler + end + end + + Telemetry::Logger.log_level = 'debug' + + @exception = StandardError.new('this is my test exception') + end + + it 'should be able to log exceptions' do + expect(@class.new.exception(@exception)) + expect { @class.new.exception(@exception, level: 'debug') }.to output(/DEBUG/).to_stdout_from_any_process + expect { @class.new.exception(@exception, level: 'info') }.to output(/INFO/).to_stdout_from_any_process + expect { @class.new.exception(@exception, level: 'warn') }.to output(/WARN/).to_stdout_from_any_process + expect { @class.new.exception(@exception, level: 'error') }.to output(/ERROR/).to_stdout_from_any_process + expect { @class.new.exception(@exception, level: 'fatal') }.to output(/FATAL/).to_stdout_from_any_process + end + + it 'should be able to re raise an exception' do + expect { @class.new.exception(StandardError.new('test'), raise: true) }.to raise_exception StandardError + end +end