berkeley_library-logging 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|