Add plain text and coloured logging to the Client
Logs websocket events (connect, message, error and close events)
This commit is contained in:
122
adr/0003-logging-methods-vs-logger-class.md
Normal file
122
adr/0003-logging-methods-vs-logger-class.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# 3. Logging methods vs logger class
|
||||
|
||||
Date: 2024-03-19
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
I'm deciding between integrating logging directly into the main class or creating a dedicated logger class.
|
||||
|
||||
### Option 1: Logging methods
|
||||
|
||||
The first approach weaves logging actions into the operational code, resulting in a tight coupling of functionality and
|
||||
logging. Classes should be open for extension but closed for modification, and this strategy violates that principle.
|
||||
|
||||
```ruby
|
||||
class Client
|
||||
def connect(relay)
|
||||
execute_within_an_em_thread do
|
||||
client = build_websocket_client(relay.url)
|
||||
parent_to_child_channel.subscribe do |msg|
|
||||
client.send(msg)
|
||||
emit(:send, msg)
|
||||
log_send(msg) # <------ new code
|
||||
end
|
||||
|
||||
client.on :open do
|
||||
child_to_parent_channel.push(type: :open, relay:)
|
||||
log_connection_opened(relay) # <------ new code
|
||||
end
|
||||
|
||||
client.on :message do |event|
|
||||
child_to_parent_channel.push(type: :message, data: event.data)
|
||||
log_message_received(event.data) # <------ new code
|
||||
end
|
||||
|
||||
client.on :error do |event|
|
||||
child_to_parent_channel.push(type: :error, message: event.message)
|
||||
log_error(event.message) # <------ new code
|
||||
end
|
||||
|
||||
client.on :close do |event|
|
||||
child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason)
|
||||
log_connection_closed(event.code, event.reason) # <------ new code
|
||||
end
|
||||
end
|
||||
|
||||
# ------ new code below ------
|
||||
|
||||
def log_send(msg)
|
||||
logger.info("Message sent: #{msg}")
|
||||
end
|
||||
|
||||
def log_connection_opened(relay)
|
||||
logger.info("Connection opened to #{relay.url}")
|
||||
end
|
||||
|
||||
def log_message_received(data)
|
||||
logger.info("Message received: #{data}")
|
||||
end
|
||||
|
||||
def log_error(message)
|
||||
logger.error("Error: #{message}")
|
||||
end
|
||||
|
||||
def log_connection_closed(code, reason)
|
||||
logger.info("Connection closed with code: #{code}, reason: #{reason}")
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Option 2: Logger class
|
||||
|
||||
The second strategy separates logging into its own class, promoting cleaner code and adherence to the Single
|
||||
Responsibility Principle. Client already exposes events that can be tapped into, so the logger class can listen to these
|
||||
events and log accordingly.
|
||||
|
||||
```ruby
|
||||
class ClientLogger
|
||||
def attach_to(client)
|
||||
logger_instance = self
|
||||
|
||||
client.on(:connect) { |relay| logger_instance.on_connect(relay) }
|
||||
client.on(:message) { |message| logger_instance.on_message(message) }
|
||||
client.on(:send) { |message| logger_instance.on_send(message) }
|
||||
client.on(:error) { |message| logger_instance.on_error(message) }
|
||||
client.on(:close) { |code, reason| logger_instance.on_close(code, reason) }
|
||||
end
|
||||
|
||||
def on_connect(relay); end
|
||||
def on_message(message); end
|
||||
def on_send(message); end
|
||||
def on_error(message); end
|
||||
def on_close(code, reason); end
|
||||
end
|
||||
|
||||
client = Nostr::Client.new
|
||||
logger = Nostr::ClientLogger.new
|
||||
logger.attach_to(client)
|
||||
```
|
||||
|
||||
This approach decouples logging from the main class, making it easier to maintain and extend the logging system without
|
||||
affecting the core logic.
|
||||
|
||||
## Decision
|
||||
|
||||
I've chosen the dedicated logger class route. This choice is driven by a desire for extensibility in the logging system.
|
||||
With a separate logger, I can easily modify logging behavior—like changing formats, adjusting verbosity levels,
|
||||
switching colors, or altering output destinations (files, networks, etc.) — without needing to rewrite any of the main
|
||||
operational code.
|
||||
|
||||
## Consequences
|
||||
|
||||
Adopting a dedicated logger class offers greater flexibility and simplifies maintenance, making it straightforward to
|
||||
adjust how and what I log independently of the core logic. This separation of concerns means that any future changes
|
||||
to logging preferences or requirements can be implemented quickly and without risk to the main class's functionality.
|
||||
However, it's important to manage the integration carefully to avoid introducing complexity, such as handling
|
||||
dependencies and ensuring seamless communication between the main operations and the logging system.
|
||||
|
||||
66
adr/0004-default-logging-activation.md
Normal file
66
adr/0004-default-logging-activation.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 3. Default Logging Activation
|
||||
|
||||
Date: 2024-03-19
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
Logging provides visibility into the gem's behavior and helps to diagnose issues. The decision centered on whether
|
||||
to enable logging by default or require manual activation.
|
||||
|
||||
### Option 1: On-demand Logging
|
||||
|
||||
```ruby
|
||||
client = Nostr::Client.new
|
||||
logger = Nostr::ClientLogger.new
|
||||
logger.attach_to(client)
|
||||
```
|
||||
|
||||
#### Advantages:
|
||||
|
||||
- Offers users flexibility and control over logging.
|
||||
- Conserves resources by logging only when needed.
|
||||
|
||||
#### Disadvantages:
|
||||
|
||||
- Potential to miss critical logs if not enabled.
|
||||
- Requires users to read and understand documentation to enable logging.
|
||||
- Requires users to manually activate and configure logging.
|
||||
|
||||
### Option 2: Automatic Logging
|
||||
|
||||
```ruby
|
||||
class Client
|
||||
def initialize(logger: ClientLogger.new)
|
||||
@logger = logger
|
||||
logger&.attach_to(self)
|
||||
end
|
||||
end
|
||||
|
||||
client = Nostr::Client.new
|
||||
```
|
||||
|
||||
#### Advantages:
|
||||
|
||||
- Ensures comprehensive logging without user intervention.
|
||||
- Simplifies debugging and monitoring.
|
||||
- Balances logging detail with performance impact.
|
||||
|
||||
#### Disadvantages:
|
||||
|
||||
- Needs additional steps to disable logging.
|
||||
|
||||
## Decision
|
||||
|
||||
Logging will be enabled by default. Users can disable logging by passing a `nil` logger to the client:
|
||||
|
||||
```ruby
|
||||
client = Nostr::Client.new(logger: nil)
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
Enabling logging by default favors ease of use and simplifies the developer's experience.
|
||||
32
adr/0005-logger-types.md
Normal file
32
adr/0005-logger-types.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 3. Logger Types
|
||||
|
||||
Date: 2024-04-01
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
In developing the `Nostr::Client` logging mechanism, I identified a need to cater to multiple development environments
|
||||
and developer preferences. The consideration was whether to implement a singular logger type or to introduce
|
||||
multiple, specialized loggers. The alternatives were:
|
||||
|
||||
- A single `ClientLogger` providing a one-size-fits-all solution.
|
||||
- Multiple logger types:
|
||||
- `Nostr::Client::Logger`: The base class for logging, providing core functionalities.
|
||||
- `Nostr::Client::ColorLogger`: An extension of the base logger, introducing color-coded outputs for enhanced readability in environments that support ANSI colors.
|
||||
- `Nostr::Client::PlainLogger`: A variation that produces logs without color coding, suitable for environments lacking color support or for users preferring plain text.
|
||||
|
||||
## Decision
|
||||
|
||||
I decided to implement the latter option: three specific kinds of loggers (`Nostr::Client::Logger`,
|
||||
`Nostr::Client::ColorLogger`, and `Nostr::Client::PlainLogger`). This approach is intended to offer flexibility and
|
||||
cater to the varied preferences and requirements of our users, recognizing the diverse environments in which our
|
||||
library might be used.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `Developer Choice`: Developers gain the ability to select the one that best matches their needs and environmental constraints, thereby enhancing the library's usability.
|
||||
- `Code Complexity`: While introducing multiple logger types increases the library's code complexity, this is offset by the significant gain in flexibility and user satisfaction.
|
||||
- `Broad Compatibility`: This decision ensures that the logging mechanism is adaptable to a wide range of operational environments, enhancing the library's overall robustness and accessibility.
|
||||
Reference in New Issue
Block a user