tools-cf-plugin 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,7 +23,11 @@ module CFTools
23
23
  user = input[:user]
24
24
  pass = input[:password]
25
25
 
26
- NATS.start(:uri => "nats://#{user}:#{pass}@#{host}:#{port}") do
26
+ render_apps("nats://#{user}:#{pass}@#{host}:#{port}")
27
+ end
28
+
29
+ def render_apps(uri)
30
+ NATS.start(:uri => uri) do
27
31
  NATS.subscribe("dea.advertise") do |msg|
28
32
  payload = JSON.parse(msg)
29
33
  dea_id = payload["id"]
@@ -34,6 +38,11 @@ module CFTools
34
38
  render_table
35
39
  end
36
40
  end
41
+ rescue NATS::ServerError => e
42
+ if e.to_s =~ /slow consumer/i
43
+ line c("dropped by server; reconnecting...", :error)
44
+ retry
45
+ end
37
46
  end
38
47
 
39
48
  private
@@ -4,3 +4,4 @@ require "tools-cf-plugin/dea-ads"
4
4
  require "tools-cf-plugin/dea-apps"
5
5
  require "tools-cf-plugin/tunnel/tunnel-nats"
6
6
  require "tools-cf-plugin/tunnel/watch-logs"
7
+ require "tools-cf-plugin/spacetime"
@@ -0,0 +1,72 @@
1
+ require "cf/cli"
2
+ require "time"
3
+
4
+ module CFTools
5
+ class SpaceTime < CF::CLI
6
+ def precondition; end
7
+
8
+ def self.io_from_input
9
+ proc do |name|
10
+ if name == "-"
11
+ $stdin
12
+ else
13
+ File.open(File.expand_path(name))
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.pattern_from_input
19
+ proc do |pat|
20
+ Regexp.new(pat)
21
+ end
22
+ end
23
+
24
+ desc "Space out line entries based on their timestamps."
25
+ input :input, :argument => :required, :from_given => io_from_input,
26
+ :desc => "Input source; '-' for stdin."
27
+ input :pattern, :argument => :required, :from_given => pattern_from_input,
28
+ :desc => "Regexp matching the timestamp in each log."
29
+ input :scale, :type => :float, :default => 1.0,
30
+ :desc => "Space scaling factor"
31
+ def spacetime
32
+ io = input[:input]
33
+ pattern = input[:pattern]
34
+ scale = input[:scale]
35
+
36
+ prev_time = nil
37
+ io.each do |entry|
38
+ prev_time = space_line(
39
+ entry, pattern, scale, prev_time)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def space_line(entry, pattern, scale, prev_time)
46
+ matches = entry.match(pattern)
47
+ return unless matches
48
+
49
+ current_time =
50
+ begin
51
+ Time.parse(matches[1])
52
+ rescue
53
+ line c("failed to parse: #{matches[1]}", :error)
54
+ return
55
+ end
56
+
57
+ print_spacing(prev_time, current_time, scale)
58
+
59
+ current_time
60
+ ensure
61
+ line entry.chomp
62
+ end
63
+
64
+ def print_spacing(prev_time, current_time, scale)
65
+ return unless prev_time
66
+
67
+ ((current_time - prev_time) * scale).round.times do
68
+ line
69
+ end
70
+ end
71
+ end
72
+ end
@@ -14,6 +14,11 @@ module CFTools::Tunnel
14
14
  "router" => ["gorouter/gorouter.log"]
15
15
  }
16
16
 
17
+ # colorize if told to; don't check if output is tty
18
+ def color?
19
+ input[:color]
20
+ end
21
+
17
22
  desc "Stream logs from the jobs of a deployment"
18
23
  input :director, :argument => :required, :desc => "BOSH director address"
19
24
  input :components, :argument => :splat, :desc => "Which components to log"
@@ -1,3 +1,3 @@
1
1
  module CFTools
2
- VERSION = "2.2.0".freeze
2
+ VERSION = "2.3.0".freeze
3
3
  end
@@ -1,3 +1,5 @@
1
+ require "time"
2
+
1
3
  require "cf/cli"
2
4
  require "nats/client"
3
5
 
@@ -5,6 +7,11 @@ module CFTools
5
7
  class Watch < CF::App::Base
6
8
  def precondition; end
7
9
 
10
+ # colorize if told to; don't check if output is tty
11
+ def color?
12
+ input[:color]
13
+ end
14
+
8
15
  REPLY_PREFIX = "`- reply to "
9
16
  COLUMN_WIDTH = 30
10
17
 
@@ -23,6 +30,7 @@ module CFTools
23
30
  :desc => "NATS server user"
24
31
  input :password, :alias => "-p", :default => "nats",
25
32
  :desc => "NATS server password"
33
+ input :logs, :alias => "-l", :desc => "NATS server logs to replay"
26
34
  def watch
27
35
  app = get_app(input[:app])
28
36
  host = input[:host]
@@ -37,12 +45,17 @@ module CFTools
37
45
 
38
46
  $stdout.sync = true
39
47
 
40
- watching_nats("nats://#{user}:#{pass}@#{host}:#{port}", subjects) do |msg, reply, sub|
48
+ watching_nats(
49
+ :uri => "nats://#{user}:#{pass}@#{host}:#{port}",
50
+ :logs => input[:logs],
51
+ :subjects => subjects) do |msg, reply, sub, time|
41
52
  begin
53
+ time ||= Time.now.strftime("%r")
54
+
42
55
  if @requests.include?(sub)
43
- process_response(sub, reply, msg, app)
56
+ process_response(sub, reply, msg, app, time)
44
57
  elsif !app || msg.include?(app.guid)
45
- process_message(sub, reply, msg, app)
58
+ process_message(sub, reply, msg, app, time)
46
59
  end
47
60
  rescue => e
48
61
  line c("couldn't deal w/ #{sub} '#{msg}': #{e.class}: #{e}", :error)
@@ -52,10 +65,6 @@ module CFTools
52
65
 
53
66
  private
54
67
 
55
- def timestamp
56
- Time.now.strftime("%r")
57
- end
58
-
59
68
  def list(vals)
60
69
  if vals.empty?
61
70
  d("none")
@@ -64,7 +73,7 @@ module CFTools
64
73
  end
65
74
  end
66
75
 
67
- def process_message(sub, reply, msg, app)
76
+ def process_message(sub, reply, msg, app, time)
68
77
  register_request(sub, reply) if reply
69
78
 
70
79
  payload = JSON.parse(msg) rescue msg
@@ -120,10 +129,10 @@ module CFTools
120
129
  end
121
130
 
122
131
  sub = sub.ljust(COLUMN_WIDTH)
123
- line "#{timestamp}\t#{sub}\t#{payload}"
132
+ line "#{time}\t#{sub}\t#{payload}"
124
133
  end
125
134
 
126
- def process_response(sub, _, msg, _)
135
+ def process_response(sub, _, msg, _, time)
127
136
  payload = JSON.parse(msg) # rescue msg
128
137
 
129
138
  sub, id = @requests[sub]
@@ -139,7 +148,7 @@ module CFTools
139
148
  sub, payload = pretty_component_discover_response(sub, payload)
140
149
  end
141
150
 
142
- line "#{timestamp}\t#{REPLY_PREFIX}#{sub} (#{c(id, :error)})\t#{payload}"
151
+ line "#{time}\t#{REPLY_PREFIX}#{sub} (#{c(id, :error)})\t#{payload}"
143
152
  end
144
153
 
145
154
  def pretty_dea_advertise(sub, payload)
@@ -455,14 +464,62 @@ module CFTools
455
464
  guid
456
465
  end
457
466
 
458
- def watching_nats(uri, subjects, &blk)
459
- NATS.start(:uri => uri) do
460
- subjects.each do |subject|
461
- NATS.subscribe(subject, &blk)
467
+ def watching_nats(options, &blk)
468
+ if options[:logs]
469
+ logs = Dir[File.expand_path(options[:logs])]
470
+
471
+ logs.each do |log|
472
+ File.open(log) do |io|
473
+ io.each do |line|
474
+ dispatch_line(line, options[:subjects], &blk)
475
+ end
476
+ end
477
+ end
478
+ else
479
+ begin
480
+ NATS.start(:uri => options[:uri]) do
481
+ options[:subjects].each do |subject|
482
+ NATS.subscribe(subject, &blk)
483
+ end
484
+ end
485
+ rescue NATS::ServerError => e
486
+ if e.to_s =~ /slow consumer/i
487
+ line c("dropped by server; reconnecting...", :error)
488
+ retry
489
+ end
462
490
  end
463
491
  end
464
492
  end
465
493
 
494
+ def dispatch_line(line, subjects)
495
+ matches = line.match(/^([^ ]+) \[#\d+\] Received on \[([^\]]+)\] : '(.*)'$/)
496
+ timestamp = matches[1]
497
+ subject = matches[2]
498
+ message = matches[3]
499
+
500
+ return unless subjects.any? { |p| subject_matches?(subject, p) }
501
+
502
+ gmt = Time.parse("#{timestamp} GMT")
503
+ yield message, nil, subject, gmt.strftime("%Y-%m-%d %r")
504
+ end
505
+
506
+ def subject_matches?(subject, pattern)
507
+ ssegs = subject.split(".")
508
+ psegs = pattern.split(".")
509
+
510
+ # match sizes because zip is silly
511
+ ssegs[psegs.size - 1] ||= nil
512
+
513
+ ssegs.zip(psegs) do |a, b|
514
+ break if b == ">"
515
+ return false if a == nil
516
+ next if b == "*"
517
+ return false if a != b
518
+ end
519
+
520
+ true
521
+ end
522
+
466
523
  def register_request(sub, reply)
467
524
  @requests[reply] = [sub, @request_ticker += 1]
468
525
  end
@@ -8,11 +8,11 @@ describe CFTools::DEAApps do
8
8
 
9
9
  before { stub_client }
10
10
 
11
- before {
11
+ before do
12
12
  app1.stub(:exists? => true)
13
13
  app2.stub(:exists? => true)
14
14
  app3.stub(:exists? => true)
15
- }
15
+ end
16
16
 
17
17
  before do
18
18
  NATS.stub(:start).and_yield
@@ -20,8 +20,7 @@ describe CFTools::DEAApps do
20
20
  EM.stub(:add_periodic_timer).and_yield
21
21
  end
22
22
 
23
- context "When a NATS message is recieved" do
24
- let(:advertise) { <<PAYLOAD }
23
+ let(:advertise) { <<PAYLOAD }
25
24
  {
26
25
  "app_id_to_count": {
27
26
  "#{app1.guid}": #{app1.total_instances},
@@ -38,17 +37,35 @@ describe CFTools::DEAApps do
38
37
  }
39
38
  PAYLOAD
40
39
 
41
- before do
42
- NATS.stub(:subscribe).and_yield(advertise)
40
+ before do
41
+ NATS.stub(:subscribe).and_yield(advertise)
42
+ end
43
+
44
+ it "outputs the list of apps, memory and math" do
45
+ cf %W[dea-apps]
46
+ expect(output).to say(%r{app_name\s+app_guid\s+org/space\s+reserved\s+math})
47
+ expect(output).to say(%r{myapp3\s+myappguid-3\s+organization-\w+ / space-\w+\s+4G\s+\(1G\s+x\s+4\)})
48
+ expect(output).to say(%r{myapp2\s+myappguid-2\s+organization-\w+ / space-\w+\s+1G\s+\(256M\s+x\s+4\)})
49
+ expect(output).to say(%r{myapp1\s+myappguid-1\s+organization-\w+ / space-\w+\s+512M\s+\(128M\s+x\s+4\)})
50
+ end
51
+
52
+ context "when the server drops us for being a slow consumer" do
53
+ it "reconnects" do
54
+ expect(NATS).to receive(:subscribe).and_raise(
55
+ NATS::ServerError, "Slow consumer detected, connection dropped")
56
+
57
+ expect(NATS).to receive(:start).twice
58
+
59
+ cf %W[dea-apps]
43
60
  end
44
61
 
45
- it "outputs the list of apps, memory and math" do
62
+ it "says it's reconnecting" do
63
+ expect(NATS).to receive(:subscribe).and_raise(
64
+ NATS::ServerError, "Slow consumer detected, connection dropped")
65
+
46
66
  cf %W[dea-apps]
47
- expect(output).to say(%r{app_name\s+app_guid\s+org/space\s+reserved\s+math})
48
- expect(output).to say(%r{myapp3\s+myappguid-3\s+organization-\w+ / space-\w+\s+4G\s+\(1G\s+x\s+4\)})
49
- expect(output).to say(%r{myapp2\s+myappguid-2\s+organization-\w+ / space-\w+\s+1G\s+\(256M\s+x\s+4\)})
50
- expect(output).to say(%r{myapp1\s+myappguid-1\s+organization-\w+ / space-\w+\s+512M\s+\(128M\s+x\s+4\)})
67
+
68
+ expect(output).to say("reconnecting")
51
69
  end
52
70
  end
53
-
54
71
  end
@@ -0,0 +1,4 @@
1
+ 2013-07-04_18:00:36.99086 [#12294219] Received on [health.stop] : '{"droplet":"some-app-guid","last_updated":1372959488,"instances":{"bd56b936c9ba8e69b4c3f0b8a4dab1cb":"1837d4f7-2e02-4f4e-98a4-e1bd4b016194"},"running":{"c1820a4c-846a-4581-9be3-a7488829c138":16,"1837d4f7-2e02-4f4e-98a4-e1bd4b016194":0,"7a99dd4d-c525-4fdc-a18d-1f9919a765d0":0},"version":"c1820a4c-846a-4581-9be3-a7488829c138"}'
2
+ 2013-07-04_18:00:36.99088 [#12294220] Received on [dea.stop] : '{"droplet":"some-app-guid","instances":["bd56b936c9ba8e69b4c3f0b8a4dab1cb"]}'
3
+ 2013-07-04_19:00:36.99088 [#12294220] Received on [foo.bar] : 'sup'
4
+ 2013-07-04_19:00:39.99088 [#12294220] Received on [foo] : 'sup'
@@ -0,0 +1,5 @@
1
+ [2013-07-04 16:15:12 -0700] line one
2
+ [2013-07-04 16:15:12 -0700] line two
3
+ [2013-07-04 16:15:13 -0700] line three
4
+ [2013-07-04 16:15:13 -0700] line four
5
+ [2013-07-04 16:15:23 -0700] line five
data/spec/spacetime.rb ADDED
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+
3
+ describe CFTools::SpaceTime do
4
+ it "spaces the lines out based on the time between them" do
5
+ cf %W[spacetime #{fixture("spacetime/1")} ^\\[([^\\\]]+)\\]]
6
+ expect(output).to say <<EOF
7
+ [2013-07-04 16:15:12 -0700] line one
8
+ [2013-07-04 16:15:12 -0700] line two
9
+
10
+ [2013-07-04 16:15:13 -0700] line three
11
+ [2013-07-04 16:15:13 -0700] line four
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ [2013-07-04 16:15:23 -0700] line five
23
+ EOF
24
+ end
25
+
26
+ context "when the regex fails" do
27
+ it "prints the lines anyway" do
28
+ cf %W[spacetime #{fixture("spacetime/1")} LOL --scale 0.5]
29
+ expect(output).to say <<EOF
30
+ [2013-07-04 16:15:12 -0700] line one
31
+ [2013-07-04 16:15:12 -0700] line two
32
+ [2013-07-04 16:15:13 -0700] line three
33
+ [2013-07-04 16:15:13 -0700] line four
34
+ [2013-07-04 16:15:23 -0700] line five
35
+ EOF
36
+ end
37
+ end
38
+
39
+ context "when the regex matches something that ain't no time" do
40
+ it "prints the lines anyway" do
41
+ cf %W[spacetime #{fixture("spacetime/1")} line --scale 0.5]
42
+ expect(output).to say <<EOF
43
+ [2013-07-04 16:15:12 -0700] line one
44
+ [2013-07-04 16:15:12 -0700] line two
45
+ [2013-07-04 16:15:13 -0700] line three
46
+ [2013-07-04 16:15:13 -0700] line four
47
+ [2013-07-04 16:15:23 -0700] line five
48
+ EOF
49
+ end
50
+ end
51
+
52
+ context "when a scaling factor is given" do
53
+ it "divides the spacing by the scaling factor" do
54
+ cf %W[spacetime #{fixture("spacetime/1")} ^\\[([^\\\]]+)\\] --scale 0.5]
55
+ expect(output).to say <<EOF
56
+ [2013-07-04 16:15:12 -0700] line one
57
+ [2013-07-04 16:15:12 -0700] line two
58
+
59
+ [2013-07-04 16:15:13 -0700] line three
60
+ [2013-07-04 16:15:13 -0700] line four
61
+
62
+
63
+
64
+
65
+
66
+ [2013-07-04 16:15:23 -0700] line five
67
+ EOF
68
+ end
69
+ end
70
+ end
data/spec/spec_helper.rb CHANGED
@@ -12,6 +12,10 @@ require "nats/client"
12
12
 
13
13
  require "#{SPEC_ROOT}/../lib/tools-cf-plugin/plugin"
14
14
 
15
+ def fixture(path)
16
+ "#{SPEC_ROOT}/fixtures/#{path}"
17
+ end
18
+
15
19
  RSpec.configure do |c|
16
20
  c.include Fake::FakeMethods
17
21
 
data/spec/watch_spec.rb CHANGED
@@ -78,6 +78,78 @@ describe CFTools::Watch do
78
78
  end
79
79
  end
80
80
 
81
+
82
+ context "when the server drops us for being a slow consumer" do
83
+ it "reconnects" do
84
+ expect(NATS).to receive(:subscribe).and_raise(
85
+ NATS::ServerError, "Slow consumer detected, connection dropped")
86
+
87
+ expect(NATS).to receive(:start).twice
88
+
89
+ cf %W[watch]
90
+ end
91
+
92
+ it "says it's reconnecting" do
93
+ expect(NATS).to receive(:subscribe).and_raise(
94
+ NATS::ServerError, "Slow consumer detected, connection dropped")
95
+
96
+ cf %W[watch]
97
+
98
+ expect(output).to say("reconnecting")
99
+ end
100
+ end
101
+
102
+ context "when NATS message logs are given" do
103
+ let(:app) { fake :app, :guid => "some-app-guid" }
104
+ let(:client) { fake_client :apps => [app] }
105
+
106
+ it "does not connect to NATS" do
107
+ expect(NATS).to_not receive(:start)
108
+ cf %W[watch -l #{fixture("nats_logs")}/1]
109
+ end
110
+
111
+ it "prints the date/time from the source file, in local time" do
112
+ cf %W[watch -l #{fixture("nats_logs")}/1]
113
+ gmt = Time.parse("2013-07-04_18:00:36.99086 GMT")
114
+ local = gmt.strftime("%Y-%m-%d %r")
115
+ expect(output).to say(/^#{local}/)
116
+ end
117
+
118
+ context "and a subject is given" do
119
+ it "only prints matching entries" do
120
+ cf %W[watch -s dea.stop -l #{fixture("nats_logs")}/1]
121
+ expect(output).to_not say("health.stop")
122
+ expect(output).to say("dea.stop")
123
+ end
124
+
125
+ context "and it uses *" do
126
+ it "only prints matching entries" do
127
+ cf %W[watch -s *.stop -l #{fixture("nats_logs")}/1]
128
+ expect(output).to say("health.stop")
129
+ expect(output).to say("dea.stop")
130
+ expect(output).to_not say("foo.bar")
131
+ end
132
+
133
+ it "matches based on segment length" do
134
+ cf %W[watch -s * -l #{fixture("nats_logs")}/1]
135
+ expect(output).to_not say("health.stop")
136
+ expect(output).to_not say("dea.stop")
137
+ expect(output).to_not say("foo.bar")
138
+ expect(output).to say("foo")
139
+ end
140
+ end
141
+
142
+ context "and it uses >" do
143
+ it "only prints matching entries" do
144
+ cf %W[watch -s foo.> -l #{fixture("nats_logs")}/1]
145
+ expect(output).to_not say("health.stop")
146
+ expect(output).to_not say("dea.stop")
147
+ expect(output).to say("foo.bar")
148
+ end
149
+ end
150
+ end
151
+ end
152
+
81
153
  context "when a malformed message comes in" do
82
154
  let(:messages) { [["foo", nil, "some.subject"]] }
83
155
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tools-cf-plugin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-26 00:00:00.000000000 Z
12
+ date: 2013-07-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cfoundry
@@ -271,29 +271,33 @@ extensions: []
271
271
  extra_rdoc_files: []
272
272
  files:
273
273
  - Rakefile
274
+ - lib/tools-cf-plugin/dea-ads.rb
275
+ - lib/tools-cf-plugin/dea-apps.rb
274
276
  - lib/tools-cf-plugin/plugin.rb
275
- - lib/tools-cf-plugin/version.rb
276
- - lib/tools-cf-plugin/tunnel/watch-logs.rb
277
- - lib/tools-cf-plugin/tunnel/log_entry.rb
278
- - lib/tools-cf-plugin/tunnel/tunnel-nats.rb
279
- - lib/tools-cf-plugin/tunnel/stream_location.rb
277
+ - lib/tools-cf-plugin/shell.rb
278
+ - lib/tools-cf-plugin/spacetime.rb
280
279
  - lib/tools-cf-plugin/tunnel/base.rb
280
+ - lib/tools-cf-plugin/tunnel/log_entry.rb
281
281
  - lib/tools-cf-plugin/tunnel/multi_line_stream.rb
282
+ - lib/tools-cf-plugin/tunnel/stream_location.rb
283
+ - lib/tools-cf-plugin/tunnel/tunnel-nats.rb
284
+ - lib/tools-cf-plugin/tunnel/watch-logs.rb
285
+ - lib/tools-cf-plugin/version.rb
282
286
  - lib/tools-cf-plugin/watch.rb
283
- - lib/tools-cf-plugin/dea-apps.rb
284
- - lib/tools-cf-plugin/shell.rb
285
- - lib/tools-cf-plugin/dea-ads.rb
287
+ - spec/dea-ads_spec.rb
288
+ - spec/dea-apps_spec.rb
289
+ - spec/fixtures/nats_logs/1
290
+ - spec/fixtures/spacetime/1
286
291
  - spec/shell_spec.rb
287
- - spec/watch_spec.rb
292
+ - spec/spacetime.rb
288
293
  - spec/spec_helper.rb
289
- - spec/dea-apps_spec.rb
290
- - spec/dea-ads_spec.rb
291
- - spec/tunnel/log_entry_spec.rb
292
- - spec/tunnel/watch-logs_spec.rb
293
294
  - spec/tunnel/base_spec.rb
294
- - spec/tunnel/stream_location_spec.rb
295
+ - spec/tunnel/log_entry_spec.rb
295
296
  - spec/tunnel/multi_line_stream_spec.rb
297
+ - spec/tunnel/stream_location_spec.rb
296
298
  - spec/tunnel/tunnel-nats_spec.rb
299
+ - spec/tunnel/watch-logs_spec.rb
300
+ - spec/watch_spec.rb
297
301
  homepage: http://github.com/cloudfoundry/tools-cf-plugin
298
302
  licenses: []
299
303
  post_install_message:
@@ -319,14 +323,17 @@ signing_key:
319
323
  specification_version: 3
320
324
  summary: Cloud Foundry tooling commands.
321
325
  test_files:
326
+ - spec/dea-ads_spec.rb
327
+ - spec/dea-apps_spec.rb
328
+ - spec/fixtures/nats_logs/1
329
+ - spec/fixtures/spacetime/1
322
330
  - spec/shell_spec.rb
323
- - spec/watch_spec.rb
331
+ - spec/spacetime.rb
324
332
  - spec/spec_helper.rb
325
- - spec/dea-apps_spec.rb
326
- - spec/dea-ads_spec.rb
327
- - spec/tunnel/log_entry_spec.rb
328
- - spec/tunnel/watch-logs_spec.rb
329
333
  - spec/tunnel/base_spec.rb
330
- - spec/tunnel/stream_location_spec.rb
334
+ - spec/tunnel/log_entry_spec.rb
331
335
  - spec/tunnel/multi_line_stream_spec.rb
336
+ - spec/tunnel/stream_location_spec.rb
332
337
  - spec/tunnel/tunnel-nats_spec.rb
338
+ - spec/tunnel/watch-logs_spec.rb
339
+ - spec/watch_spec.rb