process_bot 0.1.18 → 0.1.20

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00371cf3c9ed3fa904d5d3a8655be99178ce77a729ab2aad459572fc63bb4779
4
- data.tar.gz: db9145067629c7872c810c03e1e97caa6c626837ec26d07fa6ceeebf1a623bdd
3
+ metadata.gz: 667ddcc3026131cd08e43c08b0d21fa797b563dcc9ba577bb6c02feb77ed4d78
4
+ data.tar.gz: b37c21f69c8bf609f7889f994156bb18889f88e9ea53e00d84ad31d3bc791710
5
5
  SHA512:
6
- metadata.gz: 89a0dde6cbadf510efb51b07f690ec90ca03af46ba06c9a1eca1f75a86e718bf23d263207aedd5a2763463e9470d9655d81487d5073f24a077edd1798acb2691
7
- data.tar.gz: 27a95d5f8911f704c15faf58339c0173ca6cc01a3d7e8caa70743d6f3eadbbb9a44122b2ece7e74feaf4b7b6e7a03b1d7809e22cfa26efb4eb9967972f6846ff
6
+ metadata.gz: 4958033faa3e693c056fbf3e38f608bcbf98a6be923bd98cb895956c02d56ca423428835a64f5be9ec6c77d6a0f5a81d2498db2ee20ba2a52c8612a50a211c5f
7
+ data.tar.gz: f4428d73807c72cc8037a3d1e0631077f02684cb0bc0080b71b6499cd8dc7daeea514beb97a153b8618bd92ec475d208ee1f01d663b09bd783e29db316fecdca
data/AGENTS.md CHANGED
@@ -13,3 +13,4 @@
13
13
  - Added `graceful_no_wait` command and Capistrano task for non-blocking graceful shutdowns.
14
14
  - Always add or update tests for new/changed functionality, and run them.
15
15
  - Added coverage for graceful_no_wait and Capistrano wait defaults.
16
+ - Bumped version to 0.1.20 for log streaming updates.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## [Unreleased]
2
2
  - Stop accepting new control commands during shutdown so in-flight responses complete reliably.
3
+ - Stream ProcessBot logs to connected control clients for Capistrano output.
4
+ - Sanitize broadcast log output to keep JSON encoding safe.
5
+ - Bump version to 0.1.20.
3
6
 
4
7
  ## [0.1.0] - 2022-04-03
5
8
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- process_bot (0.1.18)
4
+ process_bot (0.1.20)
5
5
  knjrbfw (>= 0.0.116)
6
6
  pry
7
7
  rake
@@ -1,3 +1,4 @@
1
+ require "json"
1
2
  require "socket"
2
3
 
3
4
  class ProcessBot::ClientSocket
@@ -26,23 +27,39 @@ class ProcessBot::ClientSocket
26
27
  logger.logs "Sending: #{data}"
27
28
  begin
28
29
  client.puts(JSON.generate(data))
29
- response_raw = client.gets
30
- rescue Errno::ECONNRESET, Errno::EPIPE
31
- return :nil
32
- end
30
+ loop do
31
+ response_raw = client.gets
32
+ return :nil if response_raw.nil?
33
33
 
34
- # Happens if process is interrupted
35
- return :nil if response_raw.nil?
34
+ response = JSON.parse(response_raw)
36
35
 
37
- response = JSON.parse(response_raw)
36
+ case response.fetch("type")
37
+ when "log"
38
+ write_log_output(response)
39
+ when "success"
40
+ return :success
41
+ when "error"
42
+ error = RuntimeError.new("Command raised an error: #{response.fetch("message")}")
43
+ error.set_backtrace(response.fetch("backtrace") + Thread.current.backtrace)
38
44
 
39
- return :success if response.fetch("type") == "success"
45
+ raise error
46
+ else
47
+ raise "Unknown response type: #{response.fetch("type")}"
48
+ end
49
+ end
50
+ rescue Errno::ECONNRESET, Errno::EPIPE
51
+ :nil
52
+ end
53
+ end
40
54
 
41
- if response.fetch("type") == "error"
42
- error = RuntimeError.new("Command raised an error: #{response.fetch("message")}")
43
- error.set_backtrace(response.fetch("backtrace") + Thread.current.backtrace)
55
+ def write_log_output(response)
56
+ output = response["output"].to_s
57
+ stream = response.fetch("stream", "stdout")
44
58
 
45
- raise error
59
+ if stream == "stderr"
60
+ $stderr.print output
61
+ else
62
+ $stdout.print output
46
63
  end
47
64
  end
48
65
  end
@@ -3,12 +3,14 @@ require "json"
3
3
  require "knjrbfw"
4
4
 
5
5
  class ProcessBot::ControlSocket
6
- attr_reader :options, :port, :process, :server
6
+ attr_reader :clients, :clients_mutex, :options, :port, :process, :server
7
7
 
8
8
  def initialize(options:, process:)
9
9
  @options = options
10
10
  @process = process
11
11
  @port = options.fetch(:port).to_i
12
+ @clients = []
13
+ @clients_mutex = Mutex.new
12
14
  end
13
15
 
14
16
  def logger
@@ -20,6 +22,9 @@ class ProcessBot::ControlSocket
20
22
  run_client_loop
21
23
  logger.logs "TCPServer started"
22
24
  options.events.call(:on_socket_opened, port: @port)
25
+ options.events.connect(:on_log) do |event_name, output:, type:|
26
+ broadcast_log(event_name, output: output, type: type)
27
+ end
23
28
  end
24
29
 
25
30
  def start_tcp_server
@@ -70,6 +75,8 @@ class ProcessBot::ControlSocket
70
75
  end
71
76
 
72
77
  def handle_client(client) # rubocop:disable Metrics/AbcSize
78
+ add_client(client)
79
+
73
80
  loop do
74
81
  data = client.gets
75
82
  break if data.nil? # Client disconnected
@@ -105,6 +112,42 @@ class ProcessBot::ControlSocket
105
112
  client.puts(JSON.generate(type: "error", message: "Unknown command: #{command_type}", backtrace: Thread.current.backtrace))
106
113
  end
107
114
  end
115
+ ensure
116
+ remove_client(client)
117
+ client.close unless client.closed?
118
+ end
119
+
120
+ def broadcast_log(_event_name, output:, type:)
121
+ safe_output = normalize_output(output)
122
+ payload = JSON.generate(type: "log", stream: type.to_s, output: safe_output)
123
+
124
+ clients_snapshot.each do |client|
125
+ client.puts(payload)
126
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET
127
+ remove_client(client)
128
+ end
129
+ end
130
+
131
+ def add_client(client)
132
+ clients_mutex.synchronize do
133
+ clients << client
134
+ end
135
+ end
136
+
137
+ def remove_client(client)
138
+ clients_mutex.synchronize do
139
+ clients.delete(client)
140
+ end
141
+ end
142
+
143
+ def clients_snapshot
144
+ clients_mutex.synchronize do
145
+ clients.dup
146
+ end
147
+ end
148
+
149
+ def normalize_output(output)
150
+ output.to_s.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
108
151
  end
109
152
 
110
153
  def symbolize_keys(hash)
@@ -11,6 +11,7 @@ class ProcessBot::Logger
11
11
 
12
12
  def log(output, type: :stdout)
13
13
  write_output(output, type)
14
+ broadcast_log(output, type)
14
15
 
15
16
  return unless log_to_file?
16
17
  return if type == :debug && !debug_enabled?
@@ -54,6 +55,25 @@ class ProcessBot::Logger
54
55
  fp_log.flush
55
56
  end
56
57
 
58
+ def broadcast_log(output, type)
59
+ return unless should_broadcast?(type)
60
+
61
+ options.events.call(:on_log, output: output, type: type)
62
+ end
63
+
64
+ def should_broadcast?(type)
65
+ case type
66
+ when :stdout, :stderr
67
+ true
68
+ when :info
69
+ logging_enabled?
70
+ when :debug
71
+ debug_enabled?
72
+ else
73
+ false
74
+ end
75
+ end
76
+
57
77
  def debug_enabled?
58
78
  truthy_option?(:debug)
59
79
  end
@@ -24,6 +24,7 @@ class ProcessBot::Options
24
24
  require "knjrbfw"
25
25
 
26
26
  event_handler = ::Knj::Event_handler.new
27
+ event_handler.add_event(name: :on_log)
27
28
  event_handler.add_event(name: :on_process_started)
28
29
  event_handler.add_event(name: :on_socket_opened)
29
30
  event_handler
@@ -1,3 +1,3 @@
1
1
  module ProcessBot
2
- VERSION = "0.1.18".freeze
2
+ VERSION = "0.1.20".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.18
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-08 00:00:00.000000000 Z
11
+ date: 2026-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: knjrbfw