logfmt 0.0.10 → 0.1.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d357083dac3dea75a9cd937ac2ba24b8cfff5c11f0656749a084cc16fb6cd4a
4
- data.tar.gz: 97aad097b305ded6c753de8c233cd2317c12dcb9834fec52047ea799d842f8ff
3
+ metadata.gz: 6a4ffa4696a5c33a721cbe1c0e440af8d790cac06a9d2c5d1439798d5419186d
4
+ data.tar.gz: 2b7137ed007f395f5f74eb59c247f086e6f30b7e4b9deb5948a35bb1a0bfb3cd
5
5
  SHA512:
6
- metadata.gz: 8a140be6a60d67ca2f7eb8373689e7f246e7ffadae3fba22da5820992b38df1410a5faaabc0e37ab6e318434db0ba4973b4844e74e86f3e60ef9df19aff574c8
7
- data.tar.gz: 9df321f5d270af2e288a6de8e2391d9a05159e680ae3bfa669751a92bd0e155e30d6fc86c55f246896c20d5d274e45b93878a5f365ebb0c652c94b52d53379a2
6
+ metadata.gz: 27fe0c766d112a51224bb4850ed153a2afac3a881535f502678d4c855bce8cb36416b5711cf4eaa811004cbd195ecc7ea24eae12922a6225bb475c663f51e6c2
7
+ data.tar.gz: a604b0fdde0f5160ad76c17f589731f50be22703a7c631ac78d16a6edceca0dfab4f5656a176624d66c6e6408167cffa3f4217092c53e47c6df538f72506c380
data/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
5
5
 
6
6
  ## \[Unreleased\]
7
7
 
8
+ ## [0.1.0.beta.1] 2022-10-21
9
+ ### Added
10
+ - Add `Logfmt::Logger` and `Logfmt::TaggedLogger`.
11
+ The later is distributed as its own gem, `logfmt-tagged_logger`, but lives in this repo.
12
+
8
13
  ## [0.0.10] 2022-04-30
9
14
  ### Changed
10
15
  - Autoload the `Logfmt::Parser` when it's used, in preparation for the coming `Logfmt::Logger` and friends.
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec
3
+ gemspec name: "logfmt"
4
+ gemspec name: "logfmt-tagged_logger"
data/README.md CHANGED
@@ -1,6 +1,58 @@
1
1
  # Logfmt
2
2
 
3
- Parse log lines in the logfmt style:
3
+ Write and parse structured log lines in the [logfmt style][logfmt-blog].
4
+
5
+ ## Installation
6
+
7
+ Add this line to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "logfmt"
11
+ ```
12
+
13
+ And then install:
14
+
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself:
20
+
21
+ ```bash
22
+ $ gem install logfmt
23
+ ```
24
+
25
+ ### Versioning
26
+
27
+ This project adheres to [Semantic Versioning][semver].
28
+
29
+ ## Usage
30
+
31
+ `Logfmt` is composed to two parts: writing structured log lines in the `logfmt` style, and parsing `logfmt`-style log lines.
32
+
33
+ While writing and parsing `logfmt` are related, we've found that it's common to only need to do one or there other in a single application.
34
+ To support that usage, `Logfmt` leverages Ruby's `autoload` to lazily load the `Logfmt::Parser` or `Logfmt::Logger` (and associated code) into memory.
35
+ In the general case that looks something like:
36
+
37
+ ```ruby
38
+ require "logfmt"
39
+
40
+ Logfmt # This constant was already loaded, but neither Logfmt::Parser
41
+ # nor Logfmt::Logger constants are loaded. Yet.
42
+
43
+ Logfmt.parse("…")
44
+ # OR
45
+ Logfmt::Parser.parse("…")
46
+
47
+ # Either of the above will load the Logfmt::Parser constant.
48
+ # Similarly you can autoload the Logfmt::Logger via
49
+
50
+ Logfmt::Logger.new
51
+ ```
52
+
53
+ If you want to eagerly load the logger or parser, you can do that by requiring them directly
54
+
55
+ ### Parsing log lines
4
56
 
5
57
  ```ruby
6
58
  require "logfmt/parser"
@@ -8,3 +60,95 @@ require "logfmt/parser"
8
60
  Logfmt::Parser.parse('foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf')
9
61
  #=> {"foo"=>"bar", "a"=>14, "baz"=>"hello kitty", "cool%story"=>"bro", "f"=>true, "%^asdf"=>true}
10
62
  ```
63
+
64
+ ### Writing log lines
65
+
66
+ The `Logfmt::Logger` is built on the stdlib `::Logger` and adheres to its API.
67
+ The primary difference is that `Logfmt::Logger` defaults to a `logfmt`-style formatter.
68
+ Specifically, a `Logfmt::Logger::KeyValueFormatter`, which results in log lines something like this:
69
+
70
+ ```ruby
71
+ require "logfmt/logger"
72
+
73
+ logger = Logfmt::Logger.new($stdout)
74
+
75
+ logger.info(foo: "bar", a: 14, "baz" => "hello kitty", "cool%story" => "bro", f: true, "%^asdf" => true)
76
+ #=> time=2022-04-20T23:30:54.647403Z severity=INFO foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf
77
+
78
+ logger.debug("MADE IT HERE!")
79
+ #=> time=2022-04-20T23:33:44.912595Z severity=DEBUG msg="MADE IT HERE!"
80
+ ```
81
+
82
+ #### Tagged log lines
83
+
84
+ The `logfmt-tagged_logger` gem adds support for Rails-style [tagged logging][tagged-logger].
85
+ This gem adds a `Logfmt::TaggedLogger` which is built on `ActiveSupport::TaggedLogger`, but emits the tags in logfmt-style, as key/value pairs.
86
+ For example
87
+
88
+ ```ruby
89
+ logger = Logfmt::TaggedLogger.new($stdout)
90
+
91
+ logger.tagged(source: "api") do
92
+ logger.info(foo: "bar")
93
+ end
94
+
95
+ #=> time=2022-04-20T23:33:44.912595Z severity=info source=api foo=bar"
96
+ ```
97
+
98
+ You can also pass "bare" tags and they'll be collected and emitted under the `tags` key.
99
+
100
+ ```ruby
101
+ logger = Logfmt::TaggedLogger.new($stdout)
102
+
103
+ logger.tagged("API", "1.2.3.4") do
104
+ logger.info(foo: "bar")
105
+ end
106
+
107
+ #=> time=2022-04-20T23:33:44.912595Z severity=info tags="[API] [1.2.3.4]" foo=bar"
108
+ ```
109
+
110
+ It's likely more helpful and useful to use meaningful key/values for your tags, rather than bare tags.
111
+
112
+ #### Expected key/value transformations
113
+
114
+ When writing a log line with the `Logfmt::Logger::KeyValueFormatter` the keys and/or values will be transformed thusly:
115
+
116
+ * "Bare messages" (those with no key given when invoking the logger) will be wrapped in the `msg` key.
117
+
118
+ ```ruby
119
+ logger.info("here")
120
+ #=> time=2022-04-20T23:33:49.912997Z severity=INFO msg=here
121
+ ```
122
+
123
+ * Values, including bare messages, containing white space or control characters (spaces, tabs, newlines, emoji, etc…) will be wrapped in double quotes (`""`) and fully escaped.
124
+
125
+ ```ruby
126
+ logger.info("👻 Boo!")
127
+ #=> time=2022-04-20T23:33:35.912595Z severity=INFO msg="\u{1F47B} Boo!"
128
+
129
+ logger.info(number: 42, with_quotes: %{These "are" 'quotes', OK?})
130
+ #=> time=2022-04-20T23:33:36.412183Z severity=INFO number=42 with_quotes="These \"are\" 'quotes', OK?"
131
+ ```
132
+
133
+ * Floating point values are truncated to three digits.
134
+
135
+ * Time values are formatted as ISO8601 strings, with six digits sub-second precision.
136
+
137
+ * A value that is an Array is wrapped in square brackets, and then the above rules applied to each Array value.
138
+ This works well for arrays of simple values - like numbers, symbols, or simple strings.
139
+ But complex data structures will result in human mind-breaking escape sequences.
140
+ So don't do that.
141
+ Keep values simple.
142
+
143
+ ```ruby
144
+ logger.info(an_array: [1, "two", :three])
145
+ #=> time=2022-04-20T23:33:36.412183Z severity=INFO an_array="[1, two, three]"
146
+ ```
147
+
148
+ **NOTE**: it is **not** expected that log lines generated by `Logfmt` can be round-tripped by parsing the log line with `Logfmt`.
149
+ Specifically, this applies to Unicode and some control characters, as well as bare messages which will be wrapped in the `msg` key when writing.
150
+ Additionally, symbol keys will be parsed back into string keys.
151
+
152
+ [logfmt-blog]: https://brandur.org/logfmt "Structured log lines with key/value pairs"
153
+ [semver]: https://semver.org/spec/v2.0.0.html "Semantic Versioning 2.0.0"
154
+ [tagged-logger]: https://guides.rubyonrails.org/debugging_rails_applications.html#tagged-logging "Tagged Logging"
data/Rakefile CHANGED
@@ -1,8 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
3
+ require "bundler/gem_helper"
4
4
  require "rspec/core/rake_task"
5
5
 
6
+ desc "Run all specs"
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
9
+ namespace "logfmt" do
10
+ Bundler::GemHelper.install_tasks name: "logfmt"
11
+ end
12
+
13
+ # Inspired by how dotenv/dotenv-rails handles mulitple Gems in a single repo
14
+ class LogFmtTaggedLoggerGemHelper < Bundler::GemHelper
15
+ def guard_already_tagged
16
+ # noop
17
+ end
18
+
19
+ def tag_version
20
+ # noop
21
+ end
22
+ end
23
+
24
+ namespace "logfmt-tagged_logger" do
25
+ LogFmtTaggedLoggerGemHelper.install_tasks name: "logfmt-tagged_logger"
26
+ end
27
+
28
+ desc "Build logfmt and logfmt-tagged_logger into the pkg directory"
29
+ task build: ["logfmt:build", "logfmt-tagged_logger:build"]
30
+
31
+ desc "Build and install logfmt and logfmt-tagged_logger into system gems"
32
+ task install: ["logfmt:install", "logfmt-tagged_logger:install"]
33
+
34
+ desc "Create tag, build, and push logfmt and logfmt-tagged_logger to rubygems.org"
35
+ task release: ["logfmt:release", "logfmt-tagged_logger:release"]
36
+
8
37
  task default: :spec
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../logfmt"
4
+ require "logger"
5
+ require "time"
6
+
7
+ module Logfmt
8
+ class Logger < ::Logger
9
+ def initialize(*args, **kwargs)
10
+ super
11
+ @formatter ||= KeyValueFormatter.new
12
+ end
13
+
14
+ class KeyValueFormatter < ::Logger::Formatter
15
+ def call(severity, timestamp, progname, msg)
16
+ %(time=#{format_datetime(timestamp)} severity=#{severity.ljust(5)}#{format_progname(progname)} #{format_message(msg)}\n)
17
+ end
18
+
19
+ private
20
+
21
+ def format_datetime(time)
22
+ time.utc.iso8601(6)
23
+ end
24
+
25
+ def format_message(msg)
26
+ return unless msg
27
+
28
+ if msg.respond_to?(:to_hash)
29
+ pairs = msg.to_hash.map { |k, v| format_pair(k, v) }
30
+ pairs.compact.join(" ")
31
+ else
32
+ format_pair("msg", msg)
33
+ end
34
+ end
35
+
36
+ def format_pair(key, value)
37
+ return nil if value.nil?
38
+
39
+ # Return a bare key when the value is a `TrueClass`
40
+ return key if value == true
41
+
42
+ "#{key}=#{format_value(value)}"
43
+ end
44
+
45
+ def format_progname(progname)
46
+ return nil unless progname
47
+
48
+ # Format this pair like any other to ensure quoting, escaping, etc…,
49
+ # But we also need a leading space so we can interpolate the resulting
50
+ # key/value pair into our log line.
51
+ " #{format_pair(" progname", progname)}"
52
+ end
53
+
54
+ def format_value(value)
55
+ if value.is_a?(Float)
56
+ format("%.3f", value)
57
+ elsif value.is_a?(Time)
58
+ format_datetime(value)
59
+ elsif value.respond_to?(:to_ary)
60
+ format_value(
61
+ "[#{Array(value).map { |v| format_value(v) }.join(", ")}]"
62
+ )
63
+ else
64
+ # Interpolating due to a weird/subtle behaviour possible in #to_s.
65
+ # Namely, it's possible it doesn't actually return a String:
66
+ # https://github.com/ruby/spec/blob/3affe1e54fcd11918a242ad5d4a7ba895ee30c4c/language/string_spec.rb#L130-L141
67
+ value = "#{value}" # rubocop:disable Style/RedundantInterpolation
68
+ value = value.dump if value.match?(/[[:space:]]|[[:cntrl:]]/) # wrap in quotes and escape control characters
69
+ value
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logfmt
4
- VERSION = "0.0.10"
4
+ VERSION = "0.1.0.beta.2"
5
5
  end
data/lib/logfmt.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "logfmt/version"
4
4
 
5
5
  module Logfmt
6
+ autoload(:Logger, "logfmt/logger")
6
7
  autoload(:Parser, "logfmt/parser")
7
8
 
8
9
  def self.parse(line)
data/logfmt.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/logfmt/version"
4
+ require "English"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "logfmt"
8
+ spec.version = Logfmt::VERSION
9
+ spec.authors = ["Timothée Peignier"]
10
+ spec.email = ["timothee.peignier@tryphon.org"]
11
+
12
+ spec.summary = "Write and parse logfmt messages."
13
+ spec.description = "Write and parse log lines in the logfmt style."
14
+ spec.homepage = "https://github.com/cyberdelia/logfmt-ruby"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 2.5.0"
17
+
18
+ spec.metadata = {
19
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
20
+ "changelog_uri" => "#{spec.homepage}/blog/master/CHANGELOG.md",
21
+ "documentation_uri" => spec.homepage,
22
+ "source_code_uri" => spec.homepage
23
+ }
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
27
+ .reject { |f|
28
+ (f == __FILE__) ||
29
+ f.match?(%r{\A(?:(?:bin|spec|features)/|\.(?:git|github))}) ||
30
+ f.match?(/tagged_logger/)
31
+ }
32
+ end
33
+ spec.bindir = "bin"
34
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_development_dependency "pry-byebug", "~> 3.9"
38
+ spec.add_development_dependency "rake", "~> 13.0"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logfmt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.1.0.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timothée Peignier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-30 00:00:00.000000000 Z
11
+ date: 2022-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
- description: Parse log lines in the logfmt style.
55
+ description: Write and parse log lines in the logfmt style.
56
56
  email:
57
57
  - timothee.peignier@tryphon.org
58
58
  executables: []
@@ -65,13 +65,11 @@ files:
65
65
  - README.md
66
66
  - Rakefile
67
67
  - bench.rb
68
- - bin/bundle
69
- - bin/console
70
- - bin/rake
71
- - bin/rspec
72
68
  - lib/logfmt.rb
69
+ - lib/logfmt/logger.rb
73
70
  - lib/logfmt/parser.rb
74
71
  - lib/logfmt/version.rb
72
+ - logfmt.gemspec
75
73
  homepage: https://github.com/cyberdelia/logfmt-ruby
76
74
  licenses:
77
75
  - MIT
@@ -88,15 +86,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
86
  requirements:
89
87
  - - ">="
90
88
  - !ruby/object:Gem::Version
91
- version: 2.4.0
89
+ version: 2.5.0
92
90
  required_rubygems_version: !ruby/object:Gem::Requirement
93
91
  requirements:
94
- - - ">="
92
+ - - ">"
95
93
  - !ruby/object:Gem::Version
96
- version: '0'
94
+ version: 1.3.1
97
95
  requirements: []
98
96
  rubygems_version: 3.3.7
99
97
  signing_key:
100
98
  specification_version: 4
101
- summary: Parse logfmt messages.
99
+ summary: Write and parse logfmt messages.
102
100
  test_files: []
data/bin/bundle DELETED
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'bundle' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "rubygems"
12
-
13
- m = Module.new do
14
- module_function
15
-
16
- def invoked_as_script?
17
- File.expand_path($0) == File.expand_path(__FILE__)
18
- end
19
-
20
- def env_var_version
21
- ENV["BUNDLER_VERSION"]
22
- end
23
-
24
- def cli_arg_version
25
- return unless invoked_as_script? # don't want to hijack other binstubs
26
- return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
- bundler_version = nil
28
- update_index = nil
29
- ARGV.each_with_index do |a, i|
30
- if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
- bundler_version = a
32
- end
33
- next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
- bundler_version = $1
35
- update_index = i
36
- end
37
- bundler_version
38
- end
39
-
40
- def gemfile
41
- gemfile = ENV["BUNDLE_GEMFILE"]
42
- return gemfile if gemfile && !gemfile.empty?
43
-
44
- File.expand_path("../../Gemfile", __FILE__)
45
- end
46
-
47
- def lockfile
48
- lockfile =
49
- case File.basename(gemfile)
50
- when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
- else "#{gemfile}.lock"
52
- end
53
- File.expand_path(lockfile)
54
- end
55
-
56
- def lockfile_version
57
- return unless File.file?(lockfile)
58
- lockfile_contents = File.read(lockfile)
59
- return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
- Regexp.last_match(1)
61
- end
62
-
63
- def bundler_requirement
64
- @bundler_requirement ||=
65
- env_var_version || cli_arg_version ||
66
- bundler_requirement_for(lockfile_version)
67
- end
68
-
69
- def bundler_requirement_for(version)
70
- return "#{Gem::Requirement.default}.a" unless version
71
-
72
- bundler_gem_version = Gem::Version.new(version)
73
-
74
- requirement = bundler_gem_version.approximate_recommendation
75
-
76
- return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
77
-
78
- requirement += ".a" if bundler_gem_version.prerelease?
79
-
80
- requirement
81
- end
82
-
83
- def load_bundler!
84
- ENV["BUNDLE_GEMFILE"] ||= gemfile
85
-
86
- activate_bundler
87
- end
88
-
89
- def activate_bundler
90
- gem_error = activation_error_handling do
91
- gem "bundler", bundler_requirement
92
- end
93
- return if gem_error.nil?
94
- require_error = activation_error_handling do
95
- require "bundler/version"
96
- end
97
- return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98
- warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99
- exit 42
100
- end
101
-
102
- def activation_error_handling
103
- yield
104
- nil
105
- rescue StandardError, LoadError => e
106
- e
107
- end
108
- end
109
-
110
- m.load_bundler!
111
-
112
- if m.invoked_as_script?
113
- load Gem.bin_path("bundler", "bundle")
114
- end
data/bin/console DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "logfmt"
6
-
7
- require "pry"
8
- Pry.start
data/bin/rake DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rake' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path("../bundle", __FILE__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("rake", "rake")
data/bin/rspec DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rspec' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path("../bundle", __FILE__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("rspec-core", "rspec")