paul_bunyan 1.0.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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.travis.yml +9 -0
  4. data/Dockerfile +13 -0
  5. data/Gemfile +6 -0
  6. data/Guardfile +16 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +51 -0
  9. data/README.rdoc +3 -0
  10. data/Rakefile +19 -0
  11. data/bin/logging_demo +17 -0
  12. data/build.sh +7 -0
  13. data/docker-compose.yml +4 -0
  14. data/lib/paul_bunyan.rb +70 -0
  15. data/lib/paul_bunyan/json_formatter.rb +122 -0
  16. data/lib/paul_bunyan/level.rb +28 -0
  17. data/lib/paul_bunyan/log_relayer.rb +148 -0
  18. data/lib/paul_bunyan/rails_ext.rb +7 -0
  19. data/lib/paul_bunyan/rails_ext/instrumentation.rb +41 -0
  20. data/lib/paul_bunyan/rails_ext/rack_logger.rb +24 -0
  21. data/lib/paul_bunyan/railtie.rb +75 -0
  22. data/lib/paul_bunyan/railtie/log_subscriber.rb +182 -0
  23. data/lib/paul_bunyan/text_formatter.rb +11 -0
  24. data/lib/paul_bunyan/version.rb +3 -0
  25. data/lib/tasks/paul_bunyan_tasks.rake +4 -0
  26. data/paul_bunyan.gemspec +30 -0
  27. data/spec/dummy/Rakefile +6 -0
  28. data/spec/dummy/app/assets/images/.keep +0 -0
  29. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  32. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  33. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  34. data/spec/dummy/app/mailers/.keep +0 -0
  35. data/spec/dummy/app/models/.keep +0 -0
  36. data/spec/dummy/app/models/concerns/.keep +0 -0
  37. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/spec/dummy/bin/bundle +3 -0
  39. data/spec/dummy/bin/rails +4 -0
  40. data/spec/dummy/bin/rake +4 -0
  41. data/spec/dummy/bin/setup +29 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/config/application.rb +28 -0
  44. data/spec/dummy/config/boot.rb +5 -0
  45. data/spec/dummy/config/database.yml +25 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +41 -0
  48. data/spec/dummy/config/environments/production.rb +79 -0
  49. data/spec/dummy/config/environments/test.rb +42 -0
  50. data/spec/dummy/config/initializers/assets.rb +11 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  53. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/spec/dummy/config/initializers/inflections.rb +16 -0
  55. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  56. data/spec/dummy/config/initializers/secret_token.rb +1 -0
  57. data/spec/dummy/config/initializers/session_store.rb +3 -0
  58. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/dummy/config/locales/en.yml +23 -0
  60. data/spec/dummy/config/routes.rb +56 -0
  61. data/spec/dummy/config/secrets.yml +22 -0
  62. data/spec/dummy/lib/assets/.keep +0 -0
  63. data/spec/dummy/log/.keep +0 -0
  64. data/spec/dummy/public/404.html +67 -0
  65. data/spec/dummy/public/422.html +67 -0
  66. data/spec/dummy/public/500.html +66 -0
  67. data/spec/dummy/public/favicon.ico +0 -0
  68. data/spec/gemfiles/40.gemfile +5 -0
  69. data/spec/gemfiles/40.gemfile.lock +137 -0
  70. data/spec/gemfiles/41.gemfile +5 -0
  71. data/spec/gemfiles/41.gemfile.lock +142 -0
  72. data/spec/gemfiles/42.gemfile +5 -0
  73. data/spec/gemfiles/42.gemfile.lock +167 -0
  74. data/spec/lib/paul_bunyan/json_formatter_spec.rb +237 -0
  75. data/spec/lib/paul_bunyan/level_spec.rb +78 -0
  76. data/spec/lib/paul_bunyan/log_relayer_spec.rb +333 -0
  77. data/spec/lib/paul_bunyan/rails_ext/instrumentation_spec.rb +81 -0
  78. data/spec/lib/paul_bunyan/railtie/log_subscriber_spec.rb +304 -0
  79. data/spec/lib/paul_bunyan/railtie_spec.rb +37 -0
  80. data/spec/lib/paul_bunyan_spec.rb +137 -0
  81. data/spec/spec_helper.rb +24 -0
  82. data/spec/support/notification_helpers.rb +22 -0
  83. metadata +303 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0c8be4508c3bf748cc21adad0846a24d822dbea2
4
+ data.tar.gz: bee128e2be543d84c4ae31571949385c53c74a6f
5
+ SHA512:
6
+ metadata.gz: 49db1d3690ffdf9756b57200227e1ddb797dd867064fb3c4f63e6264e1babd8737473986abd01497a02a22b962ad17bcbfe4a4ad91ec7431c499307304c8363d
7
+ data.tar.gz: a41bb6c373d12cc1a4d68e0c38524aa5bea49a6e8adc175f439fdd17c7dcf9f53e46c39f03a8a7b279702b8c9c08efa7f72a103e79975cbcb7b9fb997c6936be
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log/*.log
19
+ pkg/
20
+ spec/dummy/db/*.sqlite3
21
+ spec/dummy/db/*.sqlite3-journal
22
+ spec/dummy/log/*.log
23
+ spec/dummy/tmp/
24
+ spec/dummy/.sass-cache
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ bundler_args: ''
2
+ language: ruby
3
+ gemfile:
4
+ - spec/gemfiles/40.gemfile
5
+ - spec/gemfiles/41.gemfile
6
+ - spec/gemfiles/42.gemfile
7
+ matrix:
8
+ exclude:
9
+ script: bundle exec rspec
data/Dockerfile ADDED
@@ -0,0 +1,13 @@
1
+ FROM docker.insops.net/instructure/instructure-ruby:2.1
2
+ MAINTAINER Tyler Pickett <tpickett@instructure.com>
3
+
4
+ WORKDIR /usr/src/app
5
+ COPY Gemfile* *.gemspec /usr/src/app/
6
+ COPY lib/paul_bunyan/version.rb /usr/src/app/lib/paul_bunyan/
7
+ RUN bundle install
8
+
9
+ COPY . /usr/src/app
10
+ USER root
11
+ RUN chown -R docker:docker /usr/src/app/*
12
+ USER docker
13
+ CMD bundle exec wwtd --parallel
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Declare your gem's dependencies in logging.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: 'bundle exec rspec' do
13
+ watch(%r{^spec/.+_spec\.rb$})
14
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Instructure Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Paul Bunyan (formerly: Logging)
2
+
3
+ PaulBunyan is a re-usable component with a globally accessible Logger with extra
4
+ support for handling logging in Rails.
5
+
6
+ ```
7
+ class Foo
8
+ include PaulBunyan
9
+
10
+ def bar
11
+ logger.warn "blah"
12
+ end
13
+ end
14
+ ```
15
+
16
+ Also included is a Railtie that overrides the default rails logger to always
17
+ print to STDOUT as well as format the messages to JSON for machine readable
18
+ goodness. This has been tested with Rails 4.2 but should compatible with
19
+ Rails 3 or newer since that's when Railties were introduced.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ gem 'paul_bunyan'
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install paul_bunyan
34
+
35
+ ## Usage
36
+
37
+ ### Non-Rails projects:
38
+
39
+ ```
40
+ require 'paul_bunyan'
41
+
42
+ include PaulBunyan::Logger
43
+
44
+ PaulBunyan.set_logger(STDOUT)
45
+
46
+ logger.warn "blah"
47
+ ```
48
+
49
+ ### Rails projects:
50
+
51
+ Nothing after it's added to your Gemfile, the Railtie takes care of the rest.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = PaulBunyan
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+ require "wwtd/tasks"
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'PaulBunyan'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ Bundler::GemHelper.install_tasks
18
+
19
+ task default: "wwtd"
data/bin/logging_demo ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require_relative '../lib/logging'
5
+
6
+ logger = PaulBunyan.set_logger(STDOUT)
7
+ puts "JSONFormatter"
8
+ logger.debug("PaulBunyanDemo") { "This is my debug message" }
9
+ logger.info("PaulBunyanDemo") { "This is my info message" }
10
+ logger.info("PaulBunyanDemo") { {foo: "bar", baz: "qux"} }
11
+ logger.warn("PaulBunyanDemo") { "This is my warn message" }
12
+ begin
13
+ raise "heck"
14
+ rescue => ex
15
+ logger.error("PaulBunyanDemo") { ex }
16
+ end
17
+ logger.fatal("PaulBunyanDemo") { "This is my fatal message" }
data/build.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ docker pull docker.insops.net/instructure/instructure-ruby:2.1
6
+ docker-compose build
7
+ docker-compose run test
@@ -0,0 +1,4 @@
1
+ test:
2
+ build: .
3
+ volumes:
4
+ - "coverage:/app/coverage"
@@ -0,0 +1,70 @@
1
+ require 'logger'
2
+
3
+ require_relative 'paul_bunyan/json_formatter'
4
+ require_relative 'paul_bunyan/level'
5
+ require_relative 'paul_bunyan/log_relayer'
6
+ require_relative 'paul_bunyan/text_formatter'
7
+ require_relative 'paul_bunyan/version'
8
+
9
+ require_relative 'paul_bunyan/railtie' if defined? ::Rails::Railtie
10
+
11
+ # Example Usage:
12
+ #
13
+ # class MyClass
14
+ # include PaulBunyan
15
+ #
16
+ # def initialize
17
+ # logger.info{ "something is working!" }
18
+ # end
19
+ # end
20
+ #
21
+ module PaulBunyan
22
+ class Error < StandardError; end
23
+ class UnknownFormatterError < Error; end
24
+ class UnknownLevelError < Error; end
25
+
26
+ # The ONE method we care about.
27
+ def logger
28
+ PaulBunyan.logger
29
+ end
30
+
31
+ class << self
32
+ attr_accessor :default_formatter_type
33
+ PaulBunyan.default_formatter_type = :json
34
+
35
+ def logger
36
+ create_logger(STDOUT) unless @logger
37
+ @logger
38
+ end
39
+
40
+ def create_logger(logdev, shift_age = 0, shift_size = 1048576, formatter_type: PaulBunyan.default_formatter_type)
41
+ logger = Logger.new(logdev, shift_age, shift_size)
42
+ logger.formatter = default_formatter(formatter_type) unless formatter_type.nil?
43
+ add_logger(logger)
44
+ end
45
+
46
+ def add_logger(logger)
47
+ @logger ||= LogRelayer.new
48
+ @logger.add_logger(logger)
49
+ logger
50
+ end
51
+
52
+ def remove_logger(logger)
53
+ @logger.remove_logger(logger)
54
+ end
55
+
56
+ private def default_formatter(formatter_type)
57
+ case formatter_type
58
+ when :json
59
+ JSONFormatter.new
60
+ when :text
61
+ TextFormatter.new
62
+ else
63
+ fail UnknownFormatterError, "Unknown formatter type #{formatter_type}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # For backwards compatibility with applications that used this prior to the rename
70
+ Logging = PaulBunyan
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+ require 'thread'
3
+
4
+ module PaulBunyan
5
+ class JSONFormatter
6
+ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%3N'
7
+
8
+ def call(severity, time, progname, msg)
9
+ metadata = {
10
+ "ts" => time.utc.strftime(DATETIME_FORMAT),
11
+ "unix_ts" => time.to_f,
12
+ "severity" => severity,
13
+ "pid" => $$,
14
+ }
15
+ metadata['program'] = progname if progname
16
+ metadata['tags'] = current_tags unless current_tags.empty?
17
+
18
+ message_data = format_message(msg)
19
+
20
+ JSON::generate(merge_metadata_and_message(metadata, message_data)) + "\n"
21
+ end
22
+
23
+ def clear_tags!
24
+ current_tags.clear
25
+ end
26
+
27
+ def current_tags
28
+ thread_key = @thread_key ||= "logging_tagged_logging_tags:#{ Thread.current.object_id }"
29
+ Thread.current[thread_key] ||= []
30
+ end
31
+
32
+ def datetime_format=(value)
33
+ # intentional nop because the whole point of this formatter is
34
+ # to have a consistent machine parsable format :-P
35
+ end
36
+
37
+ def datetime_format
38
+ DATETIME_FORMAT
39
+ end
40
+
41
+ def pop_tags(count = 1)
42
+ current_tags.pop(count)
43
+ end
44
+
45
+ def push_tags(*tags)
46
+ tags.flatten.reject{|t| t.nil? || t.to_s.strip == '' }.tap do |clean_tags|
47
+ current_tags.concat(clean_tags)
48
+ end
49
+ end
50
+
51
+ def tagged(*tags)
52
+ clean_tags = push_tags(tags)
53
+ yield
54
+ ensure
55
+ pop_tags(clean_tags.size)
56
+ end
57
+
58
+ private
59
+
60
+ # TODO: extract all of this formatting/merging out into another class if it grows
61
+ def format_message(message)
62
+ case message
63
+ when Exception
64
+ format_exception(message)
65
+ when String
66
+ format_string(message)
67
+ else
68
+ format_generic_object(message)
69
+ end
70
+ end
71
+
72
+ def format_exception(exception)
73
+ # TODO: capture the exception cause if it is present and handle the case where
74
+ # cause isn't actually an exception (such as Parslet::ParseFailed#cause)
75
+ {
76
+ "exception.class" => exception.class.to_s,
77
+ "exception.backtrace" => exception.backtrace,
78
+ "exception.message" => exception.message,
79
+ }
80
+ end
81
+
82
+ def format_string(message)
83
+ { "message" => strip_ansi(message.strip) }
84
+ end
85
+
86
+ def format_generic_object(object)
87
+ if object.respond_to?(:to_h)
88
+ object.to_h
89
+ elsif object.respond_to?(:to_hash)
90
+ object.to_hash
91
+ else
92
+ format_string(object.inspect)
93
+ end
94
+ end
95
+
96
+ def merge_metadata_and_message(metadata, message)
97
+ clean_message = sanitize_message_keys(message, metadata.keys)
98
+ metadata.merge(clean_message)
99
+ end
100
+
101
+ def sanitize_message_keys(message, metadata_keys)
102
+ message.inject({}) { |clean, (key, value)|
103
+ key = key.to_s
104
+ if metadata_keys.include?(key)
105
+ clean["user.#{ key }"] = value
106
+ else
107
+ clean[key] = value
108
+ end
109
+ clean
110
+ }
111
+ end
112
+
113
+ ANSI_REGEX = /(?:\e\[|\u009b)(?:\d{1,3}(?:;\d{0,3})*)?[0-9A-MRcf-npqrsuy]/
114
+ def strip_ansi(value)
115
+ if value.respond_to?(:to_str)
116
+ value.to_str.gsub(ANSI_REGEX, '')
117
+ elsif value
118
+ value.gsub(ANSI_REGEX, '')
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,28 @@
1
+ module PaulBunyan
2
+ module Level
3
+ LEVEL_MAP = {
4
+ DEBUG: Logger::DEBUG,
5
+ INFO: Logger::INFO,
6
+ WARN: Logger::WARN,
7
+ ERROR: Logger::ERROR,
8
+ FATAL: Logger::FATAL,
9
+ UNKNOWN: Logger::UNKNOWN
10
+ }.freeze
11
+ LOGGING_LEVEL_KEYS = LEVEL_MAP.keys.freeze
12
+ LOGGING_LEVELS = (Logger::DEBUG..Logger::UNKNOWN).freeze
13
+
14
+ def self.coerce_level(level)
15
+ coerced_level = level || Logger::DEBUG
16
+ if level =~ /\A\s*\d+\s*\z/
17
+ coerced_level = level.to_i
18
+ elsif level.is_a?(String) || level.is_a?(Symbol)
19
+ coerced_level = LEVEL_MAP[level.upcase.to_sym]
20
+ end
21
+
22
+ unless LOGGING_LEVELS.cover?(coerced_level)
23
+ fail UnknownLevelError, "Unknown logging level #{level}. Please try one of: #{LOGGING_LEVEL_KEYS.join(', ')}."
24
+ end
25
+ coerced_level
26
+ end
27
+ end
28
+ end