Skip to content

Commit 4fafe86

Browse files
committed
Merge #143: Add OutputSink abstraction for multi-destination output
1bd16d0 feat: make StandardSink public for composite usage (copilot-swe-agent[bot]) e8a98e5 fix: clippy doc-markdown warnings and format code (copilot-swe-agent[bot]) 21f7884 feat: add OutputSink abstraction for multi-destination output (copilot-swe-agent[bot]) 8bf1580 Initial plan (copilot-swe-agent[bot]) Pull request description: Implements composable sink pattern enabling output to multiple destinations (console, file, telemetry) simultaneously while maintaining backward compatibility. ## Changes **Core Abstractions** - `OutputSink` trait with `write_message(message, formatted)` method - `StandardSink` for stdout/stderr routing (default, backward compatible) - `CompositeSink` for fan-out to multiple sinks **Example Implementations** - `FileSink` for file output - `TelemetrySink` stub for observability integration **Integration** - `UserOutput` now uses `Box<dyn OutputSink>` instead of direct writers - Added `UserOutput::with_sink()` constructor - Made `StandardSink` public for composite usage - All existing constructors unchanged ## Usage ```rust // Default behavior unchanged let mut output = UserOutput::new(VerbosityLevel::Normal); // Console + File let composite = CompositeSink::new(vec![ Box::new(StandardSink::default_console()), Box::new(FileSink::new("output.log")?), ]); let mut output = UserOutput::with_sink(VerbosityLevel::Normal, Box::new(composite)); // Console + Telemetry let composite = CompositeSink::new(vec![ Box::new(StandardSink::default_console()), Box::new(TelemetrySink::new("https://telemetry.example.com".to_string())), ]); let mut output = UserOutput::with_sink(VerbosityLevel::Normal, Box::new(composite)); ``` ## Notes - `flush()` is now no-op (documented limitation - StandardSink relies on OS line-buffering) - 15 new tests added for sink implementations and integration - External crates can implement custom sinks ## Related Part of #102 - User Output Architecture Improvements Depends on #127 - Message Trait (already merged) > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `192.0.2.1` > - Triggering command: `ssh -i /nonexistent/key -p 22 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 [email protected] echo &#39;SSH connected&#39;` (packet block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/torrust/torrust-tracker-deployer/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Proposal 9: Output Sink Abstraction</issue_title> > <issue_description>## Overview > > Introduce an `OutputSink` abstraction layer to enable writing output to multiple destinations beyond stdout/stderr. This proposal enables output to files, network endpoints, telemetry systems, or multiple destinations simultaneously through a composable sink pattern. > > ## Specification > > See detailed specification: [docs/issues/140-output-sink-abstraction.md](../docs/issues/140-output-sink-abstraction.md) > > ## Goals > > - [ ] Define `OutputSink` trait for output destinations > - [ ] Implement `StandardSink` for stdout/stderr (backward compatible) > - [ ] Implement `CompositeSink` for multiple destinations > - [ ] Enable extensibility for custom sinks (file, network, telemetry) > - [ ] Maintain backward compatibility with existing API > - [ ] Support testing with mock sinks > > ## Implementation Plan > > ### Phase 1: Core Trait and StandardSink (2 hours) > - [ ] Define `OutputSink` trait with `write_message()` method > - [ ] Implement `StandardSink` with stdout/stderr routing > - [ ] Add `StandardSink::default_console()` convenience constructor > - [ ] Ensure backward compatibility > > ### Phase 2: CompositeSink Implementation (1 hour) > - [ ] Implement `CompositeSink` for multi-destination output > - [ ] Add `new()` and `add_sink()` methods > - [ ] Test fan-out behavior to multiple sinks > > ### Phase 3: UserOutput Integration (1.5 hours) > - [ ] Update `UserOutput` to use `Box<dyn OutputSink>` > - [ ] Add `with_sink()` constructor for custom sinks > - [ ] Update `write()` method to use sink > - [ ] Verify backward compatibility > > ### Phase 4: Example Sinks and Documentation (1 hour) > - [ ] Implement `FileSink` as example > - [ ] Implement `TelemetrySink` as example (mock) > - [ ] Add comprehensive usage examples > > ### Phase 5: Testing (1.5 hours) > - [ ] Add unit tests for `StandardSink` > - [ ] Add unit tests for `CompositeSink` > - [ ] Add integration tests with `UserOutput` > - [ ] Create `MockSink` for test infrastructure > > ### Phase 6: Quality Assurance (1 hour) > - [ ] Run `./scripts/pre-commit.sh` and fix any issues > - [ ] Verify backward compatibility > > **Total Estimated Time**: 8 hours > > ## Acceptance Criteria > > ### Functional Requirements > - [ ] `OutputSink` trait defines contract for output destinations > - [ ] `StandardSink` maintains backward-compatible console output > - [ ] `CompositeSink` enables multi-destination output > - [ ] `UserOutput` works with any sink implementation > - [ ] Custom sinks can be implemented externally > > ### API Design Requirements > - [ ] Sink trait is simple and focused > - [ ] Composition is straightforward and intuitive > - [ ] Backward compatibility is maintained > - [ ] API follows Rust conventions > > ### Testing Requirements > - [ ] Unit tests cover sink implementations > - [ ] Integration tests verify `UserOutput` with different sinks > - [ ] Mock sink enables easy testing > - [ ] All existing tests continue to pass > > ### Quality Requirements > - [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` > - [ ] Code follows project conventions > - [ ] Backward compatibility is verified > > ## Related > > - **Parent Epic**: #102 - User Output Architecture Improvements > - **Refactoring Plan**: [docs/refactors/plans/user-output-architecture-improvements.md](../docs/refactors/plans/user-output-architecture-improvements.md) > - **Depends On**: #127 - Use Message Trait for Extensibility > > ## Use Cases > > **Console Only (Default)**: > ```rust > let mut output = UserOutput::new(VerbosityLevel::Normal); > ``` > > **Console + File**: > ```rust > let composite = CompositeSink::new(vec![ > Box::new(StandardSink::default_console()), > Box::new(FileSink::new("output.log")?), > ]); > let mut output = UserOutput::with_sink(VerbosityLevel::Normal, Box::new(composite)); > ``` > > **Console + Telemetry**: > ```rust > let composite = CompositeSink::new(vec![ > Box::new(StandardSink::default_console()), > Box::new(TelemetrySink::new("https://telemetry.example.com".to_string())), > ]); > let mut output = UserOutput::with_sink(VerbosityLevel::Normal, Box::new(composite)); > ``` > > ## Labels > > `enhancement`, `phase-2`, `user-output`, `P2` > > ## Priority > > P2 (Phase 2 - Polish & Extensions) > > ## Estimated Effort > > 8 hours > </issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > <comment_new><author>@josecelano</author><body> > **Parent Epic**: #102 - User Output Architecture Improvements > > This issue is part of Phase 2: Polish & Extensions of the User Output Architecture refactoring.</body></comment_new> > </comments> > </details> - Fixes #140 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/torrust/torrust-tracker-deployer/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. ACKs for top commit: josecelano: ACK 1bd16d0 Tree-SHA512: de334e58ae78f41e2f2dc61204b82638cd9f082524250475089dbb3fbf6fbb4ffdbb35c5fa9686712ddec8dbc477361437fee316c611d60d8bd42f665faf3e0f
2 parents d6aba23 + 1bd16d0 commit 4fafe86

File tree

1 file changed

+751
-117
lines changed

1 file changed

+751
-117
lines changed

0 commit comments

Comments
 (0)