berkeley_library-logging 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +388 -0
- data/.idea/inspectionProfiles/Project_Default.xml +17 -0
- data/.idea/logging.iml +139 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +271 -0
- data/.ruby-version +1 -0
- data/.simplecov +4 -0
- data/.travis.yml +9 -0
- data/CHANGES.md +12 -0
- data/Dockerfile +57 -0
- data/Gemfile +3 -0
- data/Jenkinsfile +21 -0
- data/LICENSE.md +21 -0
- data/README.md +47 -0
- data/Rakefile +31 -0
- data/berkeley_library-logging.gemspec +47 -0
- data/docker-compose.yml +15 -0
- data/lib/berkeley_library/logging.rb +38 -0
- data/lib/berkeley_library/logging/configurator.rb +41 -0
- data/lib/berkeley_library/logging/env.rb +38 -0
- data/lib/berkeley_library/logging/events.rb +39 -0
- data/lib/berkeley_library/logging/formatters.rb +54 -0
- data/lib/berkeley_library/logging/logger.rb +12 -0
- data/lib/berkeley_library/logging/loggers.rb +79 -0
- data/lib/berkeley_library/logging/module_info.rb +16 -0
- data/lib/berkeley_library/logging/railtie.rb +15 -0
- data/lib/berkeley_library/logging/tagged_logging_extensions.rb +21 -0
- data/rakelib/.rubocop.yml +4 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +36 -0
- data/rakelib/gem.rake +56 -0
- data/rakelib/rubocop.rake +14 -0
- data/rakelib/spec.rake +31 -0
- data/spec/.rubocop.yml +27 -0
- data/spec/rails/ucblit/logging/configurator_spec.rb +101 -0
- data/spec/rails/ucblit/logging/env_spec.rb +25 -0
- data/spec/rails/ucblit/logging/formatters_spec.rb +44 -0
- data/spec/rails/ucblit/logging/loggers_spec.rb +117 -0
- data/spec/rails/ucblit/logging/railtie_spec.rb +46 -0
- data/spec/rails/ucblit/logging_spec.rb +132 -0
- data/spec/rails_helper.rb +15 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/standalone/ucblit/logging/configurator_spec.rb +103 -0
- data/spec/standalone/ucblit/logging/formatters_spec.rb +44 -0
- data/spec/standalone/ucblit/logging/loggers_spec.rb +278 -0
- data/spec/standalone/ucblit/logging_spec.rb +133 -0
- data/spec/standalone_helper.rb +25 -0
- metadata +363 -0
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', __dir__)
|
2
|
+
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
3
|
+
|
4
|
+
# ------------------------------------------------------------
|
5
|
+
# Application code
|
6
|
+
|
7
|
+
File.expand_path('lib', __dir__).tap do |lib|
|
8
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
9
|
+
end
|
10
|
+
|
11
|
+
# ------------------------------------------------------------
|
12
|
+
# CI
|
13
|
+
|
14
|
+
if ENV['CI']
|
15
|
+
ENV['RAILS_ENV'] = 'test'
|
16
|
+
ENV['GENERATE_REPORTS'] ||= 'true'
|
17
|
+
end
|
18
|
+
|
19
|
+
# ------------------------------------------------------------
|
20
|
+
# Custom tasks
|
21
|
+
|
22
|
+
desc 'Remove artifacts directory'
|
23
|
+
task :clean do
|
24
|
+
FileUtils.rm_rf('artifacts')
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Check test coverage, check code style, check gems for vulnerabilities'
|
28
|
+
task check: %w[coverage rubocop bundle:audit]
|
29
|
+
|
30
|
+
desc 'Clean, check, build gem'
|
31
|
+
task default: %i[clean check gem]
|
@@ -0,0 +1,47 @@
|
|
1
|
+
File.expand_path('lib', __dir__).tap do |lib|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
end
|
4
|
+
|
5
|
+
ruby_version = begin
|
6
|
+
ruby_version_file = File.expand_path('.ruby-version', __dir__)
|
7
|
+
File.read(ruby_version_file).strip
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'berkeley_library/logging/module_info'
|
11
|
+
|
12
|
+
Gem::Specification.new do |spec|
|
13
|
+
spec.name = BerkeleyLibrary::Logging::ModuleInfo::NAME
|
14
|
+
spec.author = BerkeleyLibrary::Logging::ModuleInfo::AUTHOR
|
15
|
+
spec.email = BerkeleyLibrary::Logging::ModuleInfo::AUTHOR_EMAIL
|
16
|
+
spec.summary = BerkeleyLibrary::Logging::ModuleInfo::SUMMARY
|
17
|
+
spec.description = BerkeleyLibrary::Logging::ModuleInfo::DESCRIPTION
|
18
|
+
spec.license = BerkeleyLibrary::Logging::ModuleInfo::LICENSE
|
19
|
+
spec.version = BerkeleyLibrary::Logging::ModuleInfo::VERSION
|
20
|
+
spec.homepage = BerkeleyLibrary::Logging::ModuleInfo::HOMEPAGE
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0")
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.required_ruby_version = ">= #{ruby_version}"
|
27
|
+
|
28
|
+
spec.add_dependency 'activesupport', '~> 6.0'
|
29
|
+
spec.add_dependency 'amazing_print', '~> 1.1'
|
30
|
+
spec.add_dependency 'lograge', '~> 0.11'
|
31
|
+
spec.add_dependency 'ougai', '~> 1.8'
|
32
|
+
|
33
|
+
spec.add_development_dependency 'brakeman', '~> 4.9'
|
34
|
+
spec.add_development_dependency 'bundle-audit', '~> 0.1'
|
35
|
+
spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
|
36
|
+
spec.add_development_dependency 'colorize', '~> 0.8'
|
37
|
+
spec.add_development_dependency 'dotenv', '~> 2.7'
|
38
|
+
spec.add_development_dependency 'irb', '~> 1.2' # workaroundfor https://github.com/bundler/bundler/issues/6929
|
39
|
+
spec.add_development_dependency 'listen', '>= 3.0.5', '< 3.2'
|
40
|
+
spec.add_development_dependency 'rails', '~> 6.0'
|
41
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
42
|
+
spec.add_development_dependency 'rspec-support', '~> 3.9'
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 0.91.0'
|
44
|
+
spec.add_development_dependency 'simplecov', '~> 0.21.1'
|
45
|
+
spec.add_development_dependency 'simplecov-console', '~> 0.9.1'
|
46
|
+
spec.add_development_dependency 'simplecov-rcov', '~> 0.2'
|
47
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
services:
|
2
|
+
gem:
|
3
|
+
build:
|
4
|
+
context: .
|
5
|
+
target: development
|
6
|
+
ports:
|
7
|
+
- target: 3000
|
8
|
+
published: 3000
|
9
|
+
restart: always
|
10
|
+
volumes:
|
11
|
+
# Note that this mounts the *entire* repo directory (including
|
12
|
+
# files ignored in .dockerignore when building the image)
|
13
|
+
- ./:/opt/app
|
14
|
+
|
15
|
+
version: "3.7"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
if defined?(Rails)
|
2
|
+
require 'berkeley_library/logging/railtie'
|
3
|
+
else
|
4
|
+
require 'berkeley_library/logging/configurator'
|
5
|
+
end
|
6
|
+
|
7
|
+
module BerkeleyLibrary
|
8
|
+
# Include this module to get access to a shared global logger.
|
9
|
+
module Logging
|
10
|
+
def logger
|
11
|
+
Logging.logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger=(v)
|
15
|
+
Logging.logger = v
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def logger
|
20
|
+
@logger ||= BerkeleyLibrary::Logging::Loggers.default_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger=(v)
|
24
|
+
@logger = (ensure_logger(v) unless v.nil?)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
LOG_METHODS = %i[debug info warn error].freeze
|
30
|
+
|
31
|
+
def ensure_logger(v)
|
32
|
+
return v if (missing = LOG_METHODS.reject { |m| v.respond_to?(m) }).empty?
|
33
|
+
|
34
|
+
raise ArgumentError, "Not a logger: #{v.inspect} does not respond to #{missing.join(', ')}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'berkeley_library/logging/env'
|
2
|
+
require 'berkeley_library/logging/events'
|
3
|
+
require 'berkeley_library/logging/formatters'
|
4
|
+
require 'berkeley_library/logging/loggers'
|
5
|
+
|
6
|
+
module BerkeleyLibrary
|
7
|
+
module Logging
|
8
|
+
class Configurator
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def configure(config)
|
12
|
+
configure_lograge(config)
|
13
|
+
|
14
|
+
logger = Loggers.new_default_logger(config)
|
15
|
+
logger.info("Custom logger initialized for environment #{Logging.env.inspect}")
|
16
|
+
configure_webpacker(logger)
|
17
|
+
config.logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def configure_lograge(config)
|
23
|
+
return unless config.respond_to?(:lograge)
|
24
|
+
|
25
|
+
config.lograge.tap do |lograge|
|
26
|
+
lograge.enabled = true
|
27
|
+
lograge.custom_options = Events.extract_data_for_lograge
|
28
|
+
lograge.formatter = Formatters.lograge_formatter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure_webpacker(logger)
|
33
|
+
return unless defined?(::Webpacker)
|
34
|
+
|
35
|
+
logger.info('Using custom logger for Webpacker')
|
36
|
+
::Webpacker::Instance.logger = logger
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/string_inquirer'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Logging
|
5
|
+
class << self
|
6
|
+
FALLBACK_ENV = 'development'.freeze
|
7
|
+
ENV_PREDICATES = %i[production? test? development?].freeze
|
8
|
+
private_constant :ENV_PREDICATES
|
9
|
+
|
10
|
+
def env
|
11
|
+
return Rails.env if defined?(Rails)
|
12
|
+
|
13
|
+
@env ||= begin
|
14
|
+
# Note: can't just self.env= b/c it returns the wrong value -- see
|
15
|
+
# https://stackoverflow.com/q/65226532/27358
|
16
|
+
env = (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || FALLBACK_ENV)
|
17
|
+
ensure_rails_env_like(env)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def env=(v)
|
22
|
+
if defined?(Rails)
|
23
|
+
Rails.env = v
|
24
|
+
else
|
25
|
+
@env = ensure_rails_env_like(v)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def ensure_rails_env_like(v)
|
32
|
+
return v if ENV_PREDICATES.all? { |p| v.respond_to?(p) }
|
33
|
+
|
34
|
+
ActiveSupport::StringInquirer.new(v)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module BerkeleyLibrary
|
2
|
+
module Logging
|
3
|
+
module Events
|
4
|
+
class << self
|
5
|
+
def extract_data_for_lograge
|
6
|
+
->(event) { extract_event_data(event) }
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def extract_event_data(event)
|
12
|
+
event_data = { time: Time.now }
|
13
|
+
extracted_headers = extract_headers(event)
|
14
|
+
event_data.merge(extracted_headers)
|
15
|
+
end
|
16
|
+
|
17
|
+
def extract_headers(event)
|
18
|
+
return {} unless (headers = event.payload[:headers])
|
19
|
+
|
20
|
+
extracted_headers = {
|
21
|
+
# yes, RFC 2616 uses a variant spelling for 'referrer', it's a known issue
|
22
|
+
# https://tools.ietf.org/html/rfc2616#section-14.36
|
23
|
+
referer: headers['HTTP_REFERER'],
|
24
|
+
request_id: headers['action_dispatch.request_id'],
|
25
|
+
remote_ip: headers['action_dispatch.remote_ip'],
|
26
|
+
remote_addr: headers['REMOTE_ADDR'],
|
27
|
+
x_forwarded_for: headers['HTTP_X_FORWARDED_FOR'],
|
28
|
+
forwarded: headers['HTTP_FORWARDED'] # RFC 7239
|
29
|
+
}
|
30
|
+
|
31
|
+
# Some of these 'headers' include recursive structures
|
32
|
+
# that cause SystemStackErrors in JSON serialization,
|
33
|
+
# so we convert them all to strings
|
34
|
+
extracted_headers.transform_values(&:to_s)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'ougai'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Logging
|
5
|
+
module Formatters
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def new_json_formatter
|
9
|
+
Bunyan.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_readable_formatter
|
13
|
+
Ougai::Formatters::Readable.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def lograge_formatter
|
17
|
+
->(data) { { msg: 'Request', request: Formatters.ensure_hash(data) } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def ensure_hash(message)
|
21
|
+
return {} unless message
|
22
|
+
return message if message.is_a?(Hash)
|
23
|
+
|
24
|
+
{ msg: message }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# ------------------------------------------------------------
|
29
|
+
# Private helper classes
|
30
|
+
|
31
|
+
class Bunyan < Ougai::Formatters::Bunyan
|
32
|
+
include Ougai::Logging::Severity
|
33
|
+
|
34
|
+
def _call(severity, time, progname, data)
|
35
|
+
original_data = Formatters.ensure_hash(data)
|
36
|
+
|
37
|
+
# Ougai::Formatters::Bunyan replaces the human-readable severity string
|
38
|
+
# with a numeric level, so we add it here as a separate attribute
|
39
|
+
severity = ensure_human_readable(severity)
|
40
|
+
merged_data = { severity: severity }.merge(original_data)
|
41
|
+
super(severity, time, progname, merged_data)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ensure_human_readable(severity)
|
45
|
+
return to_label(severity) if severity.is_a?(Integer)
|
46
|
+
|
47
|
+
severity.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private_constant :Bunyan
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'active_support/logger'
|
2
|
+
require 'ougai'
|
3
|
+
require 'berkeley_library/logging/tagged_logging_extensions'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module Logging
|
7
|
+
class Logger < Ougai::Logger
|
8
|
+
include ActiveSupport::LoggerThreadSafeLevel
|
9
|
+
include ActiveSupport::LoggerSilence
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'berkeley_library/logging/env'
|
2
|
+
require 'berkeley_library/logging/formatters'
|
3
|
+
require 'berkeley_library/logging/logger'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module Logging
|
7
|
+
module Loggers
|
8
|
+
class << self
|
9
|
+
FALLBACK_LOG_DIR = 'log'.freeze
|
10
|
+
|
11
|
+
def default_logger
|
12
|
+
if defined?(Rails)
|
13
|
+
return Rails.logger if Rails.logger
|
14
|
+
|
15
|
+
warn('Rails is defined, but Rails logger is nil')
|
16
|
+
end
|
17
|
+
|
18
|
+
new_default_logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO: support passing a hash / passing default_log_file
|
22
|
+
def new_default_logger(config = nil)
|
23
|
+
return new_readable_logger($stdout) unless config
|
24
|
+
return new_json_logger($stdout) if Logging.env.production?
|
25
|
+
return rails_file_logger(config) if Logging.env.test?
|
26
|
+
return new_broadcast_logger(config) if Logging.env.development?
|
27
|
+
|
28
|
+
raise ArgumentError, "Can't create logger for environment: #{Logging.env.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_json_logger(logdev)
|
32
|
+
new_logger_with(logdev: logdev, formatter: Formatters.new_json_formatter)
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_readable_logger(logdev)
|
36
|
+
new_logger_with(logdev: logdev, formatter: Formatters.new_readable_formatter)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def new_broadcast_logger(config)
|
42
|
+
new_json_logger($stdout).tap do |json_logger|
|
43
|
+
file_logger = rails_file_logger(config)
|
44
|
+
json_logger.extend Ougai::Logger.broadcast(file_logger)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def rails_file_logger(config)
|
49
|
+
log_file = default_log_file_for(config)
|
50
|
+
new_readable_logger(log_file)
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_logger_with(logdev:, formatter:)
|
54
|
+
Logger.new(logdev).tap { |l| l.formatter = formatter }
|
55
|
+
end
|
56
|
+
|
57
|
+
def default_log_file_for(config)
|
58
|
+
return config.default_log_file if config.respond_to?(:default_log_file)
|
59
|
+
|
60
|
+
File.join(ensure_log_directory, "#{Logging.env}.log")
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_log_directory
|
64
|
+
File.join(workdir, FALLBACK_LOG_DIR).tap do |log_dir|
|
65
|
+
FileUtils.mkdir(log_dir) unless File.exist?(log_dir)
|
66
|
+
raise ArgumentError, "Not a directory: #{log_dir}" unless File.directory?(log_dir)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def workdir
|
71
|
+
return Rails.application.root if defined?(Rails)
|
72
|
+
|
73
|
+
Pathname.getwd
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BerkeleyLibrary
|
2
|
+
module Logging
|
3
|
+
class ModuleInfo
|
4
|
+
NAME = 'berkeley_library-logging'.freeze
|
5
|
+
AUTHOR = 'David Moles'.freeze
|
6
|
+
AUTHOR_EMAIL = 'dmoles@berkeley.edu'.freeze
|
7
|
+
SUMMARY = 'Opinionated Ruby/Rails logging for UC Berkeley Library'.freeze
|
8
|
+
DESCRIPTION = 'A gem providing shared logging code for UC Berkeley Library gems and Rails applications'.freeze
|
9
|
+
LICENSE = 'MIT'.freeze
|
10
|
+
VERSION = '0.2.0'.freeze
|
11
|
+
HOMEPAGE = 'https://github.com/BerkeleyLibrary/logging'.freeze
|
12
|
+
|
13
|
+
private_class_method :new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'lograge'
|
2
|
+
require 'lograge/railtie' # NOTE: registers Lograge::Railtie by side effect
|
3
|
+
require 'berkeley_library/logging/configurator'
|
4
|
+
|
5
|
+
module BerkeleyLibrary
|
6
|
+
module Logging
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
# Don't use the Railtie's own `config` because configure() needs
|
9
|
+
# Rails::Application::Configuration#default_log_file
|
10
|
+
initializer('logging.configure_berkeley_library_logging', before: :initialize_logger) do |app|
|
11
|
+
Configurator.configure(app.config)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_support/tagged_logging'
|
2
|
+
require 'berkeley_library/logging/formatters'
|
3
|
+
|
4
|
+
# Monkey-patch ActiveSupport::TaggedLogging::Formatter
|
5
|
+
# not to produce garbage by prepending tags to hashes.
|
6
|
+
#
|
7
|
+
# TODO: Can we intercept Formatter.extend() instead? See
|
8
|
+
# https://github.com/rails/rails/blob/v6.0.3.4/activesupport/lib/active_support/tagged_logging.rb#L73
|
9
|
+
module ActiveSupport
|
10
|
+
module TaggedLogging
|
11
|
+
module Formatter
|
12
|
+
def call(severity, time, progname, data)
|
13
|
+
return super unless current_tags.present?
|
14
|
+
|
15
|
+
original_data = BerkeleyLibrary::Logging::Formatters.ensure_hash(data)
|
16
|
+
merged_data = { tags: current_tags }.merge(original_data)
|
17
|
+
super(severity, time, progname, merged_data)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|