๐Ÿ‘ a fluffy Gleam web server
23

Configure Feed

Select the types of activity you want to include in your feed.

add logging

vshakitskiy (Sep 25, 2025, 1:54 PM +0300) d349b7d9 456102de

+105 -22
+5
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + # Unreleased 4 + 5 + - Improve internal comments 6 + - Add logging 7 + 3 8 # v1.0.0-rc2 4 9 5 10 - Rename internal imports
+4
README.md
··· 17 17 18 18 ```gleam 19 19 import gleam/erlang/process 20 + import logging 20 21 import gleam/http/response 21 22 22 23 import ewe.{type Request, type Response} ··· 28 29 } 29 30 30 31 pub fn main() { 32 + logging.configure() 33 + logging.set_level(logging.Info) 34 + 31 35 let assert Ok(_) = 32 36 ewe.new(handler) 33 37 |> ewe.bind_all()
+2 -1
src/ewe.gleam
··· 104 104 import gleam/result 105 105 import gleam/string_tree.{type StringTree} 106 106 import gleam/yielder 107 + import logging 107 108 108 109 import glisten 109 110 import glisten/internal/listener ··· 407 408 <> ":" 408 409 <> int.to_string(server.port) 409 410 410 - io.println("Listening on " <> url) 411 + logging.log(logging.Info, "Listening on " <> url) 411 412 }, 412 413 on_crash: response.new(500) |> response.set_body(Empty), 413 414 listener_name: process.new_name("glisten_listener"),
+8 -1
src/ewe/internal/clock.gleam
··· 5 5 import gleam/result 6 6 import gleam/string 7 7 import gleam/string_tree 8 + import logging 8 9 9 10 type Message { 10 11 Tick ··· 41 42 pub fn get_http_date() -> String { 42 43 case lookup_http_date() { 43 44 Ok(date) -> date 44 - Error(Nil) -> calculate_http_date() 45 + Error(Nil) -> { 46 + logging.log( 47 + logging.Warning, 48 + "Failed to lookup HTTP date, calculating new one", 49 + ) 50 + calculate_http_date() 51 + } 45 52 } 46 53 } 47 54
+25 -9
src/ewe/internal/handler.gleam
··· 10 10 import gleam/int 11 11 import gleam/option.{type Option, None, Some} 12 12 import gleam/result 13 + import gleam/string 13 14 import gleam/yielder.{type Yielder} 15 + import logging 14 16 15 17 import glisten 16 18 import glisten/socket ··· 25 27 } as http_ 26 28 27 29 // ----------------------------------------------------------------------------- 28 - // TYPES 30 + // PUBLIC TYPES 29 31 // ----------------------------------------------------------------------------- 30 32 31 - // Control flow for the handler loop 32 - type Next { 33 - Continue(new_state: GlistenState) 34 - Stop 35 - } 36 - 33 + // Custom message that can be sent to or received from the Glisten actor 37 34 pub type GlistenMessage { 38 35 IdleTimeout 39 36 } 40 37 38 + // State of the Glisten actor 41 39 pub type GlistenState { 42 40 GlistenState( 43 41 timer: Option(process.Timer), ··· 46 44 } 47 45 48 46 // ----------------------------------------------------------------------------- 47 + // INTERNAL TYPES 48 + // ----------------------------------------------------------------------------- 49 + 50 + // Control flow for the handler loop 51 + type Next { 52 + Continue(new_state: GlistenState) 53 + Stop 54 + } 55 + 56 + // ----------------------------------------------------------------------------- 49 57 // PUBLIC API 50 58 // ----------------------------------------------------------------------------- 51 59 60 + /// Initializes the Glisten actor's state and selector for custom messages 52 61 pub fn init(_) -> #(GlistenState, Option(process.Selector(GlistenMessage))) { 53 62 let subject = process.new_subject() 54 63 let selector = ··· 96 105 _ -> 400 97 106 } 98 107 108 + logging.log( 109 + logging.Error, 110 + "Received invalid request: " <> string.inspect(reason), 111 + ) 112 + 99 113 let _ = 100 114 response.new(status) 101 115 |> response.set_body(bytes_tree.new()) ··· 125 139 ) -> Next { 126 140 let resp = 127 141 exception.rescue(fn() { handler(req) }) 128 - |> result.map_error(fn(_e) { 129 - on_crash |> response.set_header("connection", "close") 142 + |> result.map_error(fn(e) { 143 + logging.log(logging.Error, string.inspect(e)) 144 + 145 + response.set_header(on_crash, "connection", "close") 130 146 }) 131 147 |> result.unwrap_both() 132 148
+13 -2
src/ewe/internal/http.gleam
··· 36 36 import ewe/internal/file 37 37 38 38 // ----------------------------------------------------------------------------- 39 - // TYPES 39 + // PUBLIC TYPES 40 40 // ----------------------------------------------------------------------------- 41 41 42 42 // HTTP response body types ··· 105 105 MissingWebsocketKey 106 106 } 107 107 108 + // Possible results of consuming some amount of data from the request body 108 109 pub type Stream { 109 110 Consumed(data: BitArray, next: fn(Int) -> Result(Stream, ParseError)) 110 111 Done 111 112 } 112 113 114 + // ----------------------------------------------------------------------------- 115 + // INTERNAL TYPES 116 + // ----------------------------------------------------------------------------- 117 + 118 + // Function that reads `N` amount of bytes from the request body 113 119 type Consumer = 114 120 fn(Int) -> Result(Stream, ParseError) 115 121 ··· 120 126 FinalChunk(rest: Buffer) 121 127 } 122 128 129 + // State of the chunked body parsing 123 130 type ChunkedStreamState { 124 131 ChunkedStreamState(data: Buffer, chunk: Buffer, done: Bool) 125 132 } ··· 586 593 // BODY 587 594 // ----------------------------------------------------------------------------- 588 595 596 + /// Creates a consumer function that reads `N` amount of bytes from the request 597 + /// body until it is fully consumed 589 598 fn do_stream_body(req: Request(Connection), buffer: Buffer) -> Consumer { 590 599 fn(size: Int) { 591 600 let buffer_size = bit_array.byte_size(buffer.data) ··· 701 710 } 702 711 } 703 712 713 + /// Creates a consumer function that reads `N` amount of bytes from the chunked 714 + /// request body until it is fully consumed 704 715 fn do_stream_body_chunked( 705 716 req: Request(Connection), 706 717 chunked_stream_state: ChunkedStreamState, ··· 724 735 } 725 736 } 726 737 738 + /// Reads data from socket until `N` amount of bytes are read 727 739 fn read_from_socket_until( 728 740 transport transport: Transport, 729 741 socket socket: Socket, ··· 854 866 // EXTERNAL FFI 855 867 // ----------------------------------------------------------------------------- 856 868 857 - /// Splits binary data using external Erlang binary:split 858 869 @external(erlang, "binary", "split") 859 870 fn split( 860 871 subject: BitArray,
+21 -6
src/ewe/internal/websocket.gleam
··· 11 11 import gleam/option.{type Option, None, Some} 12 12 import gleam/otp/actor 13 13 import gleam/result 14 + import gleam/string 15 + import logging 14 16 15 17 import glisten/socket.{type Socket, type SocketReason} 16 18 import glisten/socket/options.{ActiveMode, Once} ··· 153 155 |> process.select_record(atom.create("ssl_closed"), 1, fn(_) { Close }) 154 156 } 155 157 158 + /// Maps user selector to internal message 156 159 fn user_selector( 157 160 selector: Option(Selector(user_message)), 158 161 ) -> Option(Selector(InternalMessage(user_message))) { ··· 239 242 deflate: Option(compression.Context), 240 243 data: data, 241 244 ) -> Result(Nil, SocketReason) { 242 - // use <- time_function("send_frame_total") 243 - 244 245 let frame = 245 246 exception.rescue(fn() { 246 247 encoder(data, deflate, option.None) ··· 249 250 250 251 case frame { 251 252 Ok(frame) -> frame 252 - Error(_) -> panic as non_owning_process 253 + Error(reason) -> { 254 + logging.log( 255 + logging.Error, 256 + "Frame should be sent from the WebSocket connection, but was sent from different process: " 257 + <> string.inspect(reason), 258 + ) 259 + panic as non_owning_process 260 + } 253 261 } 254 262 } 255 263 ··· 295 303 } 296 304 } 297 305 306 + /// Handles frames processing 298 307 fn handle_frames_processing( 299 308 state: WebsocketState(user_state), 300 309 conn: WebsocketConnection, ··· 303 312 handler: Handler(user_state, user_message), 304 313 on_close: OnClose(user_state), 305 314 ) { 306 - // use <- time_function("handle_frames_processing") 307 - 308 315 let frames = list.append(state.awaiting_frames, frames) 309 316 310 317 let #(data_frames, control_frames) = separate_frames(frames, [], []) ··· 379 386 } 380 387 } 381 388 389 + /// Separates frames into data and control frames 382 390 fn separate_frames( 383 391 frames: List(websocket.ParsedFrame), 384 392 data_frames: List(websocket.ParsedFrame), ··· 449 457 [websocket.Continuation(_, _), ..], Continue(_, _) -> { 450 458 AbnormalStop("Unexpected continuation frame") 451 459 } 460 + 452 461 // Data frames 453 462 [frame, ..rest], Continue(user_state, selector) -> { 454 463 let call = ··· 531 540 on_close(conn, state.user_state) 532 541 533 542 case abnormal_reason { 534 - Some(reason) -> actor.stop_abnormal(reason) 543 + Some(reason) -> { 544 + logging.log( 545 + logging.Error, 546 + "WebSocket connection closed abnormally: " <> reason, 547 + ) 548 + actor.stop_abnormal(reason) 549 + } 535 550 None -> actor.stop() 536 551 } 537 552 }
+3 -3
src/ewe_ffi.erl
··· 1 1 -module(ewe_ffi). 2 2 3 - -export([decode_packet/3, rescue/1, validate_field_value/1, init_clock_storage/0, 4 - set_http_date/1, lookup_http_date/0, now/0, now_microseconds/0, 5 - open_file/1, close_file/1]). 3 + -export([close_file/1, decode_packet/3, init_clock_storage/0, lookup_http_date/0, 4 + now/0, now_microseconds/0, open_file/1, rescue/1, set_http_date/1, 5 + validate_field_value/1]). 6 6 7 7 % ----------------------------------------------------------------------------- 8 8 % HTTP
+4
test/autobahn.gleam
··· 1 1 import ewe 2 2 import gleam/erlang/process 3 3 import gleam/otp/static_supervisor as supervisor 4 + import logging 4 5 5 6 pub fn main() -> Nil { 7 + logging.configure() 8 + logging.set_level(logging.Info) 9 + 6 10 // Before running Autobahn tests, make sure to add this line to glisten/internal/acceptor.gleam: 7 11 // Replace line 140: 8 12 // |> supervisor.start
+4
test/ewe_test.gleam
··· 1 1 import gleeunit 2 + import logging 2 3 3 4 pub fn main() -> Nil { 5 + logging.configure() 6 + logging.set_level(logging.Info) 7 + 4 8 gleeunit.main() 5 9 }
+4
test/preview/file_serving.gleam
··· 1 1 import gleam/erlang/process 2 + import logging 2 3 3 4 import gleam/http/response 4 5 import gleam/option.{None} ··· 21 22 } 22 23 23 24 pub fn main() { 25 + logging.configure() 26 + logging.set_level(logging.Info) 27 + 24 28 let assert Ok(_) = 25 29 ewe.new(fn(req) { serve_file(req.path) }) 26 30 |> ewe.bind_all()
+4
test/preview/getting_request_body.gleam
··· 1 1 import gleam/erlang/process 2 + import logging 2 3 3 4 import gleam/http/request 4 5 import gleam/http/response ··· 28 29 } 29 30 30 31 pub fn main() { 32 + logging.configure() 33 + logging.set_level(logging.Info) 34 + 31 35 let assert Ok(_) = 32 36 ewe.new(handle_echo) 33 37 |> ewe.bind_all()
+4
test/preview/quick_start.gleam
··· 1 1 import gleam/erlang/process 2 2 import gleam/http/response 3 + import logging 3 4 4 5 import ewe.{type Request, type Response} 5 6 ··· 10 11 } 11 12 12 13 pub fn main() { 14 + logging.configure() 15 + logging.set_level(logging.Info) 16 + 13 17 let assert Ok(_) = 14 18 ewe.new(handler) 15 19 |> ewe.bind_all()
+4
test/preview/sending_response.gleam
··· 1 1 import gleam/erlang/process 2 + import logging 2 3 3 4 import gleam/crypto 4 5 import gleam/http/request.{type Request} ··· 32 33 } 33 34 34 35 pub fn main() { 36 + logging.configure() 37 + logging.set_level(logging.Info) 38 + 35 39 let assert Ok(_) = 36 40 ewe.new(handler) 37 41 |> ewe.bind_all()