tp2 0.13.4 → 0.14

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: 8df38416f003b41f8a768a46f6d6177a479627e57b741a571f4c32333babb61a
4
- data.tar.gz: abfc514214ca7b053d023dc40d182cf4f608ed155a66e8812c53d113c4c6f3cd
3
+ metadata.gz: '08812ae0e3200972086172f46db095a85dd81ab9f92a19f3545cb7e08425e722'
4
+ data.tar.gz: 204dcb6bc431cccddcd2a1aee3416235bb495359f99cc0afaa96cd56353d51f9
5
5
  SHA512:
6
- metadata.gz: 0e37cab256b895605140bc727997a5f93fe821566525bfd4cbd9b2fc431be1470084ebed6ac235917105fffadd28a54c02638c304606464f83bcca16468c236d
7
- data.tar.gz: 5c55ab0d48a4a227f1ebc7aee1b14a54922f7d148b54f14e340f97d3d52b7811d530ef7c00ff6890d3879c383f2654ab0cde8257b3ba46d185ede6fd247516d9
6
+ metadata.gz: 49f58f9c2e4650816583633323a9f9fd38d5028ca27a3c024694baad38fbd656ec360b6cb6189876e7cb9d0574e8c340d2d71796086135225057e9367ebe7ef7
7
+ data.tar.gz: 1f9cc001a6613a89b094d07ebeadf884f377840d302080c269de4cb8f635220a18e358efe3a078936acc069479202eb17a7f47b5842fb5a150256891ce7819d0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # Version 0.14 2025-07-08
2
+
3
+ - Finish new logger
4
+
1
5
  # Version 0.13.4 2025-07-06
2
6
 
3
7
  - Improve logging for protocol errors, include more info
data/TODO.md CHANGED
@@ -14,6 +14,4 @@
14
14
  HEADER_RE = /^\s([^\s^\:]{1, #{MAX_HEADER_KEY_BYTES}}).../
15
15
  ```
16
16
 
17
- - Benchmark parsing with UM::Stream (current approach), against parsing with strscan
18
-
19
17
  - Add pluggable logging. Move default logging format outside and pass a callable.
data/examples/app.rb CHANGED
@@ -5,7 +5,7 @@ require 'tp2'
5
5
  require 'json'
6
6
 
7
7
  TP2.config do |req|
8
- raise 'foo'
8
+ # raise 'foo'
9
9
  body = req.headers.to_json
10
10
  req.respond(body, 'Content-Type' => 'application/json')
11
11
  end
@@ -5,7 +5,7 @@ require 'stringio'
5
5
 
6
6
  module TP2
7
7
  class HTTP1Connection
8
- attr_reader :fd
8
+ attr_reader :fd, :response_headers
9
9
 
10
10
  def initialize(machine, fd, opts, &app)
11
11
  @machine = machine
@@ -29,7 +29,10 @@ module TP2
29
29
  rescue UM::Terminate
30
30
  # server is terminated, do nothing
31
31
  rescue StandardError => e
32
- @logger&.call(e)
32
+ @logger&.error(
33
+ message: 'Uncaught error while running connection',
34
+ error: e
35
+ )
33
36
  ensure
34
37
  @machine.close_async(@fd)
35
38
  end
@@ -43,16 +46,23 @@ module TP2
43
46
  @app.call(request)
44
47
  persist_connection?(headers)
45
48
  rescue ProtocolError => e
46
- msg = "Protocol error: #{e.message}. Closing connection..."
47
- @logger&.call(msg)
49
+ @logger&.error(
50
+ message: 'Protocol error, closing connection',
51
+ error: e
52
+ )
48
53
  false
49
54
  rescue SystemCallError => e
50
- msg = "I/O error: #{e.class} #{e.message}. Closing connection..."
51
- @logger&.call(msg)
55
+ @logger&.error(
56
+ message: 'I/O error, closing connection',
57
+ error: e
58
+ )
52
59
  false
53
60
  rescue StandardError => e
54
- msg = "Internal error while serving request: #{e.class} #{e.message} (#{e.backtrace.inspect}). Abandoning connection..."
55
- @logger&.call(msg)
61
+ @logger&.error(
62
+ message: 'Internal error while serving request, abandoning connection',
63
+ error: e
64
+ )
65
+
56
66
  if request && !@done
57
67
  respond(request, 'Internal server error', ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
58
68
  end
@@ -116,7 +126,7 @@ module TP2
116
126
  else
117
127
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
118
128
  end
119
- @logger&.call(request, headers)
129
+ @logger&.info(request: request, response_headers: headers)
120
130
  @done = true
121
131
  @response_headers = headers
122
132
  end
@@ -155,7 +165,7 @@ module TP2
155
165
  @machine.send(@fd, data, data.bytesize, SEND_FLAGS)
156
166
  return if @done || !done
157
167
 
158
- @logger&.call(request, @response_headers)
168
+ @logger&.info(request: request, response_headers: @response_headers)
159
169
  @done = true
160
170
  end
161
171
 
@@ -167,7 +177,7 @@ module TP2
167
177
  @machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
168
178
  return if @done
169
179
 
170
- @logger&.call(request, @response_headers)
180
+ @logger&.info(request, request, response_headers: @response_headers)
171
181
  @done = true
172
182
  end
173
183
 
data/lib/tp2/logger.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module TP2
4
6
  class Logger
5
7
  def initialize(machine, fd = $stdout.fileno, **opts)
@@ -8,56 +10,83 @@ module TP2
8
10
  @opts = opts
9
11
  end
10
12
 
11
- # @param o <Qeweney::Request> request
12
- # @param h <Hash, nil> response headers
13
- def call(o, h = nil)
14
- log(format_log_line(o, h))
13
+ def info(o)
14
+ call(:INFO, o)
15
+ end
16
+
17
+ def warn(o)
18
+ call(:WARN, o)
19
+ end
20
+
21
+ def error(o)
22
+ call(:ERROR, o)
23
+ end
24
+
25
+ private
26
+
27
+ # @param level <Symbol> log level
28
+ # @param o <Hash> hash
29
+ def call(level, o)
30
+ emit(make_entry(level, o))
15
31
  rescue StandardError => e
32
+ puts 'Uncaught error while emitting log entry:'
16
33
  p e: e
17
34
  p e.backtrace
18
35
  exit
19
36
  end
20
37
 
21
- private
22
-
23
- def log(str)
24
- str = format(
25
- "%<stamp>s %<msg>s\n",
26
- stamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
27
- msg: str
28
- )
29
- @machine.write_async(@fd, str)
38
+ def emit(entry)
39
+ @machine.write_async(@fd, "#{entry.to_json}\n")
30
40
  end
31
41
 
32
- def format_log_line(o, h)
33
- case o
34
- when Exception
35
- format_error_log_line(o)
36
- when Qeweney::Request
37
- format_request_log_line(o, h)
38
- when String
39
- o
42
+ def make_entry(level, o)
43
+ if o[:request]
44
+ make_request_entry(level, o)
45
+ elsif o[:error]
46
+ make_error_entry(level, o)
40
47
  else
41
- o.to_s
48
+ make_hash_entry(level, o)
42
49
  end
43
50
  end
44
51
 
45
- def format_error_log_line(err)
46
- "Error: #{err.inspect}: #{err.backtrace.inspect}"
52
+ def make_error_entry(level, o)
53
+ err = o[:error]
54
+ {
55
+ level: level.to_s,
56
+ ts: (t = Time.now; t.to_i),
57
+ ts_s: t.iso8601
58
+ }
59
+ .merge(o)
60
+ .merge(
61
+ error: "#{err.class}: #{err.message}",
62
+ backtrace: err.backtrace
63
+ )
47
64
  end
48
65
 
49
- def format_request_log_line(request, response_headers)
66
+ def make_request_entry(level, o)
67
+ request = o[:request]
50
68
  request_headers = request.headers
51
- uri = full_uri(request_headers)
52
- status = response_headers[':status'] || '200'
53
- format(
54
- '%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d',
55
- client_ip: request.forwarded_for || '?',
56
- method: request_headers[':method'].upcase,
57
- uri: uri,
58
- status: status,
59
- tx: request_headers[':tx']
60
- )
69
+ response_headers = o[:response_headers]
70
+ {
71
+ level: level.to_s,
72
+ ts: (t = Time.now; t.to_i),
73
+ ts_s: t.iso8601,
74
+ message: o[:message] || 'HTTP request done',
75
+ client_ip: request.forwarded_for || '?',
76
+ http_method: request_headers[':method'].upcase,
77
+ user_agent: request_headers['user-agent'],
78
+ uri: full_uri(request_headers),
79
+ status: response_headers[':status'] || '200'
80
+ }
81
+ end
82
+
83
+ def make_hash_entry(level, hash)
84
+ {
85
+ level: level.to_s,
86
+ ts: (t = Time.now; t.to_i),
87
+ ts_s: t.iso8601
88
+ }
89
+ .merge(hash)
61
90
  end
62
91
 
63
92
  def full_uri(headers)
data/lib/tp2/server.rb CHANGED
@@ -17,7 +17,7 @@ module TP2
17
17
 
18
18
  def self.tp2_app(_machine, opts)
19
19
  if opts[:app_location]
20
- opts[:logger]&.call("Loading app at #{opts[:app_location]}")
20
+ opts[:logger]&.info(message: 'Loading web app', location: opts[:app_location])
21
21
  require opts[:app_location]
22
22
 
23
23
  opts.merge!(TP2.config)
@@ -65,7 +65,7 @@ module TP2
65
65
  @accept_fibers << @machine.spin { accept_incoming(fd) }
66
66
  end
67
67
  bind_string = bind_info.map { it.join(':') }.join(', ')
68
- @opts[:logger]&.call("Listening on #{bind_string}")
68
+ @opts[:logger]&.info(message: "Listening on #{bind_string}")
69
69
 
70
70
  # map fibers
71
71
  @connection_fiber_map = {}
@@ -121,7 +121,7 @@ module TP2
121
121
  end
122
122
 
123
123
  def graceful_shutdown
124
- @opts[:logger]&.call('Shutting down gracefully...')
124
+ @opts[:logger]&.info(message: 'Shutting down gracefully...')
125
125
 
126
126
  # stop listening
127
127
  close_all_server_fds
data/lib/tp2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.13.4'
2
+ VERSION = '0.14'
3
3
  end
data/lib/tp2.rb CHANGED
@@ -19,7 +19,7 @@ module TP2
19
19
  " o\n" +
20
20
  " \\|/ TP2 - a modern web server for Ruby apps\n" +
21
21
  " / \\ \n" +
22
- " / \\ https://github.com/noteflakes/tp2\n" +
22
+ " / x \\ https://github.com/noteflakes/tp2\n" +
23
23
  "⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
24
24
  )
25
25
 
@@ -37,7 +37,7 @@ module TP2
37
37
  machine = opts[:machine] || UM.new
38
38
  machine.puts(opts[:banner]) if opts[:banner]
39
39
 
40
- opts[:logger]&.call("Running TP2 #{TP2::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
40
+ opts[:logger]&.info(message: "Running TP2 #{TP2::VERSION}, UringMachine #{UM::VERSION}, Ruby #{RUBY_VERSION}")
41
41
 
42
42
  server = Server.new(machine, opts, &app)
43
43
 
data/test/test_server.rb CHANGED
@@ -247,17 +247,37 @@ class ServerTest < Minitest::Test
247
247
  assert_equal 'barbaz', body
248
248
  end
249
249
 
250
+ class TestLogger
251
+ attr_reader :entries
252
+
253
+ def initialize
254
+ @entries = []
255
+ end
256
+
257
+ def info(o)
258
+ @entries << o.merge(level: :INFO)
259
+ end
260
+
261
+ def error(o)
262
+ @entries << o.merge(level: :ERROR)
263
+ end
264
+ end
265
+
250
266
  def test_logging
251
267
  skip
252
268
  reqs = []
253
- @opts[:logger] = ->(req, _h) { reqs << req }
254
- @app = ->(req) { req.respond('Hello, world!', {}) }
269
+ @opts[:logger] = TestLogger.new
270
+ @app = ->(req) { reqs << req; req.respond('Hello, world!', {}) }
255
271
 
256
272
  write_http_request "GET / HTTP/1.0\r\n\r\n"
257
273
  response = read_client_side
258
274
  expected = "HTTP/1.1 200\r\nContent-Length: 13\r\n\r\nHello, world!"
259
275
  assert_equal(expected, response)
260
276
 
277
+ entries = @opts[:logger].entries
278
+ assert_equal 1, entries.size
261
279
  assert_equal 1, reqs.size
280
+
281
+ assert_equal reqs.first, entries.first[:request]
262
282
  end
263
283
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tp2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.4
4
+ version: '0.14'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner