storyteller 0.0.2

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +72 -0
  4. data/lib/story_teller/book.rb +39 -0
  5. data/lib/story_teller/chapter.rb +44 -0
  6. data/lib/story_teller/configurable.rb +21 -0
  7. data/lib/story_teller/console.rb +7 -0
  8. data/lib/story_teller/environments/development/configuration.rb +3 -0
  9. data/lib/story_teller/environments/development/middleware.rb +12 -0
  10. data/lib/story_teller/environments/development/notifications.rb +286 -0
  11. data/lib/story_teller/environments/development/rack.rb +35 -0
  12. data/lib/story_teller/environments/development/sidekiq.rb +33 -0
  13. data/lib/story_teller/environments/development.rb +47 -0
  14. data/lib/story_teller/environments/production/configuration.rb +4 -0
  15. data/lib/story_teller/environments/production/middleware.rb +13 -0
  16. data/lib/story_teller/environments/production/rack.rb +36 -0
  17. data/lib/story_teller/environments/production.rb +6 -0
  18. data/lib/story_teller/environments/staging/configuration.rb +4 -0
  19. data/lib/story_teller/environments/staging.rb +5 -0
  20. data/lib/story_teller/environments/test/configuration.rb +4 -0
  21. data/lib/story_teller/environments/test.rb +4 -0
  22. data/lib/story_teller/environments.rb +4 -0
  23. data/lib/story_teller/exception.rb +23 -0
  24. data/lib/story_teller/formatters/development/error.rb +52 -0
  25. data/lib/story_teller/formatters/development/info.rb +85 -0
  26. data/lib/story_teller/formatters/development.rb +4 -0
  27. data/lib/story_teller/formatters/null.rb +3 -0
  28. data/lib/story_teller/formatters/structured.rb +8 -0
  29. data/lib/story_teller/formatters.rb +26 -0
  30. data/lib/story_teller/levels.rb +4 -0
  31. data/lib/story_teller/logger.rb +62 -0
  32. data/lib/story_teller/message.rb +35 -0
  33. data/lib/story_teller/railtie.rb +73 -0
  34. data/lib/story_teller/story.rb +26 -0
  35. data/lib/story_teller/version.rb +13 -0
  36. data/lib/story_teller.rb +76 -0
  37. data/storyteller.gemspec +19 -0
  38. metadata +81 -0
@@ -0,0 +1,26 @@
1
+ class StoryTeller::Formatters
2
+ # These are autoloaded because it's wasteful to
3
+ # load all the code for each output if the application
4
+ # is not going to use them.
5
+ autoload :Development, "story_teller/formatters/development"
6
+ autoload :Structured, "story_teller/formatters/structured"
7
+ autoload :Null, "story_teller/formatters/null"
8
+
9
+ class Base
10
+ attr_reader :output, :name
11
+
12
+ def initialize(name:, output:)
13
+ @name = name
14
+ @output = output
15
+ config
16
+ end
17
+
18
+ def replace_output!(new_output)
19
+ @output = new_output
20
+ end
21
+
22
+ def write(story)
23
+ raise NotImplementedError
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module StoryTeller::Levels
2
+ INFO = "info"
3
+ ERROR = "error"
4
+ end
@@ -0,0 +1,62 @@
1
+ module StoryTeller
2
+ # Logger is a helper class to allow
3
+ # libraries and application to use StoryTeller
4
+ # through.
5
+
6
+ # This also includes a bunch of hacks that makes
7
+ # sure the logger behaves with rails.
8
+ class Logger < ::Logger
9
+ def initialize(formatter:, log_level: ::Logger::DEBUG)
10
+ @formatter = formatter
11
+ @logdev = self
12
+ self.level = log_level
13
+ end
14
+
15
+ def add(severity, message = nil, progname = nil)
16
+ return if severity < self.level
17
+
18
+ data = {severity: format_severity(severity)}
19
+
20
+ if message.nil?
21
+ if block_given?
22
+ message = yield
23
+ else
24
+ message = progname
25
+ end
26
+ end
27
+
28
+ @formatter.write(StoryTeller::Story.new(message: message.to_s, **data))
29
+
30
+ nil
31
+ end
32
+
33
+ def silence(*args)
34
+ if block_given?
35
+ yield
36
+ end
37
+ end
38
+
39
+ # This is not used and is only needed
40
+ # so ActiveSupport doesn't extend this Logger
41
+ # with other method like it does here:
42
+ # https://github.com/rails/rails/blob/fad0c6b899ba786994c506f11f587e29d7bf9c2d/activesupport/lib/active_support/logger.rb#L16-L20
43
+ # https://github.com/rails/rails/blob/25d52ab782623e59c0bc920076393d1691999e4e/activerecord/lib/active_record/railtie.rb#L66
44
+ # https://github.com/rails/rails/blob/1bb9f0e616fb60a9cc1ea67c9bbdb49b2e18835a/railties/lib/rails/commands/server/server_command.rb#L84
45
+ def dev
46
+ STDOUT
47
+ end
48
+
49
+ def <<(msg)
50
+ self.add(Logger::DEBUG, msg)
51
+ end
52
+
53
+ # Following methods have an effect on the Logger but don't within StoryTeller.
54
+ def reopen(logdev = nil);
55
+ self
56
+ end
57
+
58
+ def close
59
+ nil
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ class StoryTeller::Message
4
+ NIL_STRING = ""
5
+
6
+ def initialize(template)
7
+ template = template.to_s
8
+
9
+ unless template.encoding.name == "UTF-8"
10
+ template = template.encode("UTF-8", invalid: :replace)
11
+ end
12
+
13
+ @template = template
14
+ end
15
+
16
+ def render(attrs)
17
+ attrs.each_pair do |k, v|
18
+ attributes[k] = v.to_s
19
+ end
20
+
21
+ template % attributes
22
+ rescue StandardError => e
23
+ e.message
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :template
29
+
30
+ def attributes
31
+ @attributes ||= Hash.new do
32
+ NIL_STRING
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,73 @@
1
+ require "action_view/log_subscriber"
2
+ require "action_controller/log_subscriber"
3
+ require "action_mailer/log_subscriber"
4
+ require "active_job/log_subscriber"
5
+ require "active_storage/log_subscriber"
6
+ require "active_record/log_subscriber"
7
+ require "tempfile"
8
+
9
+ module StoryTeller
10
+ class Railtie < ::Rails::Railtie
11
+ class ProtectedNameError < StandardError; end
12
+
13
+ env_module = case
14
+ when Rails.env.development?
15
+ StoryTeller::Environments::Development
16
+ when Rails.env.staging?
17
+ StoryTeller::Environments::Staging
18
+ when Rails.env.test?
19
+ StoryTeller::Environments::Test
20
+ when Rails.env.production?
21
+ StoryTeller::Environments::Production
22
+ end
23
+
24
+ StoryTeller.include(env_module)
25
+
26
+ config.story_teller = StoryTeller.config
27
+
28
+ # Detaching the default ones from Rails
29
+ initializer "story_teller.log_subscribers" do |app|
30
+ ::ActionController::LogSubscriber.detach_from :action_controller
31
+ ::ActionView::LogSubscriber.detach_from :action_view
32
+ ::ActionMailer::LogSubscriber.detach_from :action_mailer
33
+
34
+ ::ActiveJob::LogSubscriber.detach_from :active_job
35
+ ::ActiveStorage::LogSubscriber.detach_from :active_storage
36
+ ::ActiveRecord::LogSubscriber.detach_from :active_record
37
+ end
38
+
39
+ initializer "story_teller.logger" do |app|
40
+ Rails.logger = StoryTeller::Logger.new(formatter: app.config.story_teller.log_formatter, log_level: app.config.log_level)
41
+ end
42
+
43
+ initializer "story_teller.middlewares" do |app|
44
+ ::ActionController::Base.use(StoryTeller::Middleware)
45
+
46
+ app.config.middleware.insert_before(
47
+ ActionDispatch::ActionableExceptions,
48
+ StoryTeller::Rack,
49
+ app,
50
+ app.config.debug_exception_response_format
51
+ )
52
+
53
+ app.config.middleware.delete Rails::Rack::Logger
54
+ app.config.middleware.delete ::ActionDispatch::DebugExceptions
55
+ end
56
+
57
+ initializer "story_teller.finalize" do |app|
58
+ StoryTeller.initialize!(StoryTeller.config)
59
+ end
60
+
61
+ console do
62
+ file = Tempfile.new
63
+
64
+ puts "\u{1F58B} StoryTeller redirected logs while using the console: #{file.path}"
65
+
66
+ config.story_teller.formatters.each do |formatter|
67
+ formatter.replace_output!(file)
68
+ end
69
+
70
+ Rails::Console.prepend(StoryTeller::Console)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ require "story_teller/message"
2
+
3
+ class StoryTeller::Story
4
+ attr_reader :attributes, :message, :chapter, :timestamp
5
+
6
+ def initialize(message: "", chapter: nil, **data)
7
+ self.timestamp = Time.now.utc
8
+ self.message = StoryTeller::Message.new(message)
9
+ self.attributes = data.symbolize_keys
10
+ self.chapter = chapter
11
+ end
12
+
13
+ def to_hash
14
+ {
15
+ timestamp: timestamp.strftime("%s%N"),
16
+ message: message.render(attributes),
17
+ data: {
18
+ story: attributes,
19
+ chapter: chapter&.attributes
20
+ }
21
+ }
22
+ end
23
+
24
+ private
25
+ attr_writer :timestamp, :message, :attributes, :chapter
26
+ end
@@ -0,0 +1,13 @@
1
+ module StoryTeller
2
+ module Version
3
+ module_function
4
+
5
+ MAJOR = 0
6
+ MINOR = 0
7
+ PATCH = 2
8
+
9
+ def to_s
10
+ [MAJOR, MINOR, PATCH].join(".")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,76 @@
1
+ module StoryTeller
2
+ require "story_teller/configurable"
3
+ require "story_teller/levels"
4
+ require "story_teller/logger"
5
+ require "story_teller/book"
6
+ require "story_teller/exception"
7
+ require "story_teller/chapter"
8
+ require "story_teller/story"
9
+ require "story_teller/formatters"
10
+ require "story_teller/environments"
11
+ require "story_teller/console"
12
+
13
+ class AlreadyInitializedError < StandardError; end
14
+ class FormatterNotAllowedWithName < StandardError; end
15
+
16
+ module_function
17
+
18
+ def chapter(title: "", subject: "", **options, &block)
19
+ chapter = StoryTeller::Chapter.new(
20
+ title: title,
21
+ subject: subject,
22
+ parent: book.current_chapter
23
+ )
24
+
25
+ book.open(chapter, **options, &block)
26
+ end
27
+
28
+ def book
29
+ StoryTeller::Book.current_book
30
+ end
31
+
32
+ def config(&block)
33
+ @config ||= StoryTeller::Configuration.new
34
+
35
+ block.call(@config) if block_given?
36
+
37
+ @config
38
+ end
39
+
40
+ def initialize!(config)
41
+ raise AlreadyInitializedError if frozen?
42
+
43
+ mod = Module.new do
44
+ config.formatters.each do |formatter|
45
+ func_name = formatter.name.to_sym
46
+
47
+ if method_defined?(func_name)
48
+ raise FormatterNotAllowedWithName, "#{func_name} is already defined as a method on StoryTeller. Please choose another name for your formatter"
49
+ end
50
+
51
+ define_method(func_name, ->(message, **data) {
52
+ chapter = StoryTeller.book.current_chapter
53
+ story = case message
54
+ when StoryTeller::Story
55
+ message
56
+ when StoryTeller::Exception
57
+ StoryTeller::Book.current_book.current_exception = message.exception
58
+ message
59
+ else
60
+ StoryTeller::Story.new(message: message, chapter: chapter, **data)
61
+ end
62
+
63
+ formatter.write(story)
64
+ nil
65
+ })
66
+ end
67
+ end
68
+
69
+ self.extend mod
70
+ freeze
71
+ end
72
+ end
73
+
74
+ if defined?(::Rails::Engine)
75
+ require "story_teller/railtie"
76
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "lib/story_teller/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "storyteller"
5
+ s.version = StoryTeller::Version.to_s
6
+ s.licenses = ["Apache-2.0"]
7
+ s.summary = "Observe and understand how your application is used."
8
+ s.description = %s{
9
+ StoryTeller is an observation framework that allows teams to
10
+ create meaningful stories to help them understand what's going on in
11
+ your production environment.
12
+ }
13
+ s.authors = ["Pier-Olivier Thibault"]
14
+ s.email = "storyteller@pier-olivier.dev"
15
+ s.homepage = "https://github.com/pier-oliviert/storyteller.rb"
16
+ s.metadata = { "source_code_uri" => "https://github.com/pier-oliviert/storyteller.rb" }
17
+
18
+ s.files = %w[storyteller.gemspec README.md LICENSE] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n")
19
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: storyteller
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Pier-Olivier Thibault
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-06-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "\n StoryTeller is an observation framework that allows teams to\n
14
+ \ create meaningful stories to help them understand what's going on in\n your
15
+ production environment.\n "
16
+ email: storyteller@pier-olivier.dev
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - lib/story_teller.rb
24
+ - lib/story_teller/book.rb
25
+ - lib/story_teller/chapter.rb
26
+ - lib/story_teller/configurable.rb
27
+ - lib/story_teller/console.rb
28
+ - lib/story_teller/environments.rb
29
+ - lib/story_teller/environments/development.rb
30
+ - lib/story_teller/environments/development/configuration.rb
31
+ - lib/story_teller/environments/development/middleware.rb
32
+ - lib/story_teller/environments/development/notifications.rb
33
+ - lib/story_teller/environments/development/rack.rb
34
+ - lib/story_teller/environments/development/sidekiq.rb
35
+ - lib/story_teller/environments/production.rb
36
+ - lib/story_teller/environments/production/configuration.rb
37
+ - lib/story_teller/environments/production/middleware.rb
38
+ - lib/story_teller/environments/production/rack.rb
39
+ - lib/story_teller/environments/staging.rb
40
+ - lib/story_teller/environments/staging/configuration.rb
41
+ - lib/story_teller/environments/test.rb
42
+ - lib/story_teller/environments/test/configuration.rb
43
+ - lib/story_teller/exception.rb
44
+ - lib/story_teller/formatters.rb
45
+ - lib/story_teller/formatters/development.rb
46
+ - lib/story_teller/formatters/development/error.rb
47
+ - lib/story_teller/formatters/development/info.rb
48
+ - lib/story_teller/formatters/null.rb
49
+ - lib/story_teller/formatters/structured.rb
50
+ - lib/story_teller/levels.rb
51
+ - lib/story_teller/logger.rb
52
+ - lib/story_teller/message.rb
53
+ - lib/story_teller/railtie.rb
54
+ - lib/story_teller/story.rb
55
+ - lib/story_teller/version.rb
56
+ - storyteller.gemspec
57
+ homepage: https://github.com/pier-oliviert/storyteller.rb
58
+ licenses:
59
+ - Apache-2.0
60
+ metadata:
61
+ source_code_uri: https://github.com/pier-oliviert/storyteller.rb
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.4.1
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Observe and understand how your application is used.
81
+ test_files: []