tools-cf-plugin 2.2.0 → 2.3.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.
@@ -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