Add plain text and coloured logging to the Client

Logs websocket events (connect, message, error and close events)
This commit is contained in:
Wilson Silva
2024-04-13 13:55:36 +01:00
parent a0cf41bfb4
commit 90ab1a6149
19 changed files with 742 additions and 10 deletions

View 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.

View 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
View 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.