autotoc_md1848
doc_id: "LOG-MIGR-002" doc_title: "Decorator Pattern Writer Migration Guide" doc_version: "1.0.0" doc_date: "2026-04-04" doc_status: "Released" project: "logger_system"
category: "MIGR"
Decorator Pattern Writer Migration Guide
SSOT: This document is the single source of truth for Decorator Pattern Writer Migration Guide.
Version: 0.4.0.0 Last Updated: 2026-01-22
Table of Contents
- Overview
- New Architecture
- Built-in Decorators
- Migrating Custom Writers
- Composing Decorators
- Deprecation Timeline and Legacy Patterns
- Best Practices
- Troubleshooting
Overview
Version 4.0.0 introduces a decorator pattern-based writer architecture that separates cross-cutting concerns (filtering, buffering, formatting) from destination-specific logic (console, file, network output).
Why Decorator Pattern?
| Problem (v3.x) | Solution (v4.0) |
| ~70% code duplication across writers | Each concern implemented once in decorators |
| Writers had multiple responsibilities | Single Responsibility Principle enforced |
| Hard to test individual features | Each decorator independently testable |
| Tight coupling between concerns | Loose coupling via composition |
Key Components
┌─────────────────────────────────────────────────────────────┐
│ Decorator Writers │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ formatted │→ │ buffered │→ │ filtered │→ [Leaf] │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Leaf Writers │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ console │ │ file │ │ network │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
New Architecture
Base Classes
log_writer_interface
The core interface that all writers must implement:
class log_writer_interface {
public:
virtual std::string get_name() const = 0;
virtual bool is_healthy() const = 0;
virtual ~log_writer_interface() = default;
};
decorator_writer_base
Abstract base class for decorators that eliminates boilerplate:
class decorator_writer_base : public log_writer_interface, public decorator_writer_tag {
public:
explicit decorator_writer_base(std::unique_ptr<log_writer_interface> wrapped,
std::string_view decorator_name);
std::string get_name() const override;
bool is_healthy() const override;
protected:
log_writer_interface& wrapped() noexcept;
const log_writer_interface& wrapped() const noexcept;
};
Built-in Decorators
filtered_writer
Filters log entries before passing to the wrapped writer:
auto writer = std::make_unique<filtered_writer>(
std::make_unique<file_writer>("errors.log"),
std::make_unique<filters::level_filter>(log_level::error)
);
auto composite = std::make_unique<filters::composite_filter>(
filters::composite_filter::logic_type::AND
);
composite->add_filter(std::make_unique<filters::level_filter>(log_level::info));
composite->add_filter(std::make_unique<filters::regex_filter>("security", true));
auto security_writer = std::make_unique<filtered_writer>(
std::make_unique<file_writer>("security.log"),
std::move(composite)
);
Decorator that applies filtering to wrapped log writers.
Log filtering functionality.
buffered_writer
Buffers log entries before writing to improve performance:
buffered_writer::config config;
config.max_buffer_size = 100;
config.flush_interval = std::chrono::seconds(5);
auto writer = std::make_unique<buffered_writer>(
std::make_unique<file_writer>("app.log"),
config
);
Decorator that provides buffering for wrapped log writers.
formatted_writer
Applies formatting before writing:
auto writer = std::make_unique<formatted_writer>(
std::make_unique<file_writer>("app.json"),
std::make_unique<json_formatter>()
);
Migrating Custom Writers
Before (v3.x): Monolithic Writer
class my_custom_writer : public base_writer {
public:
if (!should_log(entry)) return {};
auto formatted = format_entry(entry);
buffer_.push_back(formatted);
if (buffer_.size() >= buffer_size_) {
flush_buffer();
}
send_to_my_destination(formatted);
return {};
}
private:
std::vector<std::string> buffer_;
size_t buffer_size_;
};
After (v4.0): Focused Leaf Writer
class my_custom_writer : public thread_safe_writer, public sync_writer_tag {
public:
explicit my_custom_writer(const std::string& destination)
: destination_(destination) {}
std::string get_name() const override { return "my_custom"; }
protected:
return send_to_my_destination(entry);
}
return flush_my_destination();
}
private:
std::string destination_;
};
auto writer = std::make_unique<formatted_writer>(
std::make_unique<buffered_writer>(
std::make_unique<filtered_writer>(
std::make_unique<my_custom_writer>("my-endpoint"),
std::make_unique<level_filter>(log_level::info)
),
buffered_writer::config{.max_buffer_size = 50}
),
std::make_unique<json_formatter>()
);
Creating a Custom Decorator
If you need custom cross-cutting behavior, extend decorator_writer_base:
class rate_limiting_writer : public decorator_writer_base {
public:
rate_limiting_writer(std::unique_ptr<log_writer_interface> wrapped,
size_t max_per_second)
: decorator_writer_base(std::move(wrapped), "rate_limited"),
max_per_second_(max_per_second) {}
auto now = std::chrono::steady_clock::now();
if (now - window_start_ >= std::chrono::seconds(1)) {
window_start_ = now;
count_ = 0;
}
if (count_ >= max_per_second_) {
return {};
}
++count_;
return wrapped().write(entry);
}
private:
size_t max_per_second_;
size_t count_ = 0;
std::chrono::steady_clock::time_point window_start_;
};
Composing Decorators
Order Matters
Decorators are applied in reverse order of construction (innermost first):
auto writer = std::make_unique<formatted_writer>(
std::make_unique<buffered_writer>(
std::make_unique<filtered_writer>(
std::make_unique<file_writer>("app.log"),
std::make_unique<level_filter>(log_level::info)
),
buffered_writer::config{.max_buffer_size = 100}
),
std::make_unique<json_formatter>()
);
Recommended Order
- Filtering (earliest) - Drop unwanted entries before any processing
- Buffering - Batch entries for efficiency
- Formatting - Transform entries just before output
- Output (leaf writer) - Write to destination
Using writer_builder (Recommended)
Since v4.1.0, the writer_builder provides a fluent API for composing writers with decorators:
#include <kcenon/logger/builders/writer_builder.h>
auto writer = writer_builder()
.file("app.log")
.build();
auto production_writer = writer_builder()
.file("app.log")
.buffered(500)
.async(20000)
.build();
auto console_writer = writer_builder()
.console()
.filtered(std::make_unique<level_filter>(log_level::warning))
.build();
#ifdef LOGGER_WITH_ENCRYPTION
auto secure_writer = writer_builder()
.file("secure.log.enc")
.encrypted(key)
.buffered(100)
.async()
.build();
#endif
Benefits of writer_builder:
- Readable: Fluent API is self-documenting
- Type-safe: Compile-time validation
- Consistent order: Decorators applied in optimal order
- Error-proof: No manual nesting required
See writer_builder_example.cpp for complete examples.
Using logger_builder
The logger builder accepts writers created by writer_builder:
auto logger = logger_builder()
.add_writer("file", writer_builder().file("app.log").build())
.add_level_filter(log_level::info)
.with_batch_writing(true)
.with_formatter(std::make_unique<json_formatter>())
.build();
Deprecation Timeline and Legacy Patterns
Deprecated Writer Patterns
The following legacy writer patterns are deprecated as of v4.1.0:
| Legacy Pattern | Status | Replacement |
rotating_file_writer | Deprecated (v4.1) | writer_builder().file().buffered() |
async_file_writer | Deprecated (v4.1) | writer_builder().file().async() |
| Combined writer classes | Deprecated (v4.1) | writer_builder() with decorator chain |
Timeline
| Version | Status | Action Required |
| 4.0.0 | Decorator pattern introduced | Optional: Begin planning migration |
| 4.1.0 | writer_builder available | Recommended: Migrate to builder API |
| 4.1.0 | Deprecation warnings added | Legacy classes emit compile warnings |
| 5.0.0 | Legacy classes removed | Required: Must use decorator pattern |
Migration Paths
Note: All scenarios below show complete working examples. If you encounter issues during migration, see Troubleshooting.
Scenario 1: Simple File Writer
Before (Deprecated):
auto writer = std::make_unique<file_writer>("app.log");
After (Recommended):
auto writer = writer_builder().file("app.log").build();
Migration Steps:
- Replace
std::make_unique<file_writer>() with writer_builder().file()
- Add
.build() at the end
- Test that logs are written correctly
- Common Issue: If entries aren't being written, ensure writer is added to logger with
add_writer()
Scenario 2: Rotating File Writer
Before (Deprecated):
auto writer = std::make_unique<rotating_file_writer>(
"app.log",
10 * 1024 * 1024,
5
);
After (Recommended):
auto writer = std::make_unique<rotating_file_writer>("app.log", 10 * 1024 * 1024, 5);
auto writer = writer_builder().file("app.log").build();
Migration Steps:
- If file rotation is critical, continue using
rotating_file_writer (not deprecated yet)
- Consider external rotation tools (logrotate on Linux, log management services)
- Plan for future migration when decorator-based rotation becomes available
- Common Issue: See Flush not working if rotation timing is incorrect
Scenario 3: Async + Encrypted Writer
Before (Manual Nesting):
auto writer = std::make_unique<async_writer>(
std::make_unique<encrypted_writer>(
std::make_unique<file_writer>("secure.log.enc"),
key
),
10000
);
After (Builder API):
auto writer = writer_builder()
.file("secure.log.enc")
.encrypted(key)
.async(10000)
.build();
Migration Steps:
- Replace innermost nesting (file_writer) with
.file()
- Add
.encrypted() for encryption decorator
- Add
.async() for async decorator (outermost)
- Call
.build() to construct the writer chain
- Important: Must call
start() on async writer before use: if (auto* async_w = dynamic_cast<async_writer*>(writer.get())) {
async_w->start();
}
- Common Issue: If logs don't appear, check async writer is started. See Troubleshooting
Scenario 4: Buffered + Filtered Writer
Before (Manual Nesting):
auto writer = std::make_unique<buffered_writer>(
std::make_unique<filtered_writer>(
std::make_unique<console_writer>(),
std::make_unique<level_filter>(log_level::warning)
),
buffered_writer::config{.max_buffer_size = 100}
);
After (Builder API):
auto writer = writer_builder()
.console()
.filtered(std::make_unique<level_filter>(log_level::warning))
.buffered(100)
.build();
Migration Steps:
- Start with core writer:
.console()
- Add filter decorator:
.filtered(filter) (applied first)
- Add buffer decorator:
.buffered(size) (applied after filter)
- Build the chain:
.build()
- Order Matters: Filter → Buffer → Output (see Composing Decorators)
- Common Issue: Wrong decorator order. See Writer not receiving entries
Scenario 5: Multiple Decorators (Production Setup)
Before (Complex Nesting):
auto writer = std::make_unique<formatted_writer>(
std::make_unique<async_writer>(
std::make_unique<buffered_writer>(
std::make_unique<filtered_writer>(
std::make_unique<file_writer>("production.log"),
std::make_unique<level_filter>(log_level::info)
),
buffered_writer::config{.max_buffer_size = 500}
),
20000
),
std::make_unique<json_formatter>()
);
After (Fluent Builder):
auto writer = writer_builder()
.file("production.log")
.filtered(std::make_unique<level_filter>(log_level::info))
.buffered(500)
.formatted(std::make_unique<json_formatter>())
.async(20000)
.build();
Migration Steps:
- Identify all nested decorators (4 layers in this example)
- Reverse the nesting order when using builder (innermost → outermost)
- Core writer first:
.file()
- Add decorators in order: filter → buffer → format → async
- Start async writer after building (if using async)
- Test thoroughly with actual log messages
- Performance Tip: This pattern provides ~4M msg/s throughput. See Performance Guide
- Common Issue: If formatting fails, ensure formatter is compatible. See Entries not being formatted
Scenario 6: Custom Writer Integration
Before (Inheritance):
class my_combined_writer : public base_writer {
};
After (Composition):
class my_destination_writer : public thread_safe_writer {
public:
std::string get_name() const override { return "my_destination"; }
protected:
return send_to_my_destination(entry);
}
return flush_my_destination();
}
};
auto writer = writer_builder()
.custom(std::make_unique<my_destination_writer>())
.filtered(filter)
.buffered(100)
.async()
.build();
Migration Steps:
- Separate concerns: Extract destination-specific I/O from cross-cutting logic
- Create leaf writer: Inherit from
thread_safe_writer or sync_writer_tag
- Implement only I/O methods:
write_entry_impl(), flush_impl(), get_name()
- Remove duplicated logic: Delete filtering, buffering, formatting from custom class
- Use decorators: Compose with
.filtered(), .buffered(), .async() as needed
- Benefits: 70% less code, independently testable, reusable
- Common Issue: If custom writer doesn't compile, ensure it implements
log_writer_interface. See Best Practices
Support Policy
- v4.x series: Full backward compatibility, deprecation warnings only
- v5.0.0: Legacy classes removed, builder API required
- Migration support: See examples/writer_builder_example.cpp
Best Practices
1. Keep Leaf Writers Simple
Leaf writers should only handle destination-specific I/O:
class kafka_writer : public thread_safe_writer {
protected:
return kafka_client_.send(topic_, entry.formatted_message());
}
};
class kafka_writer : public thread_safe_writer {
protected:
if (entry.level() < min_level_) return {};
auto json = to_json(entry);
buffer_.push(json);
}
};
@ json
JSON structured format.
2. Use Category Tags
Mark your writers with appropriate category tags:
class my_sync_writer : public thread_safe_writer, public sync_writer_tag { ... };
class my_async_writer : public queued_writer_base, public async_writer_tag { ... };
class my_decorator : public decorator_writer_base, public decorator_writer_tag { ... };
3. Handle Errors Gracefully
Decorators should propagate errors from wrapped writers:
auto result = wrapped().write(entry);
if (result.is_err()) {
return result;
}
return {};
}
4. Ensure Thread Safety
If your decorator maintains state, protect it:
class stateful_decorator : public decorator_writer_base {
std::lock_guard<std::mutex> lock(mutex_);
return wrapped().write(entry);
}
private:
std::mutex mutex_;
};
Troubleshooting
Common Issues
"Writer not receiving entries"
Symptom: Log entries not appearing in output file or console.
Possible Causes:
- Decorator order is wrong - Filters should be innermost:
auto wrong = std::make_unique<filtered_writer>(
std::make_unique<buffered_writer>(...),
filter
);
auto right = std::make_unique<buffered_writer>(
std::make_unique<filtered_writer>(..., filter),
config
);
auto correct = writer_builder()
.file("app.log")
.filtered(filter)
.buffered(100)
.build();
- Writer not added to logger:
auto writer = writer_builder().file("app.log").build();
logger.add_writer(
"main", std::move(writer));
- Async writer not started:
auto writer = writer_builder().file("app.log").async().build();
if (auto* async_w = dynamic_cast<async_writer*>(writer.get())) {
async_w->start();
}
logger.add_writer(
"main", std::move(writer));
- Filter is too restrictive:
auto filter = std::make_unique<level_filter>(log_level::error);
auto writer = writer_builder()
.console()
.filtered(std::move(filter))
.build();
logger.log(log_level::info,
"This won't show");
logger.log(log_level::error,
"This will show");
"Entries not being formatted"
Symptom: Log entries appear in plain text instead of JSON/custom format.
Possible Causes:
- Formatter applied in wrong order:
auto wrong = std::make_unique<buffered_writer>(
std::make_unique<formatted_writer>(...),
config
);
auto right = std::make_unique<formatted_writer>(
std::make_unique<buffered_writer>(..., config),
formatter
);
auto correct = writer_builder()
.file("app.json")
.buffered(100)
.formatted(std::make_unique<json_formatter>())
.build();
- Formatter not provided:
auto writer = writer_builder().file("app.json").build();
auto writer = writer_builder()
.file("app.json")
.formatted(std::make_unique<json_formatter>())
.build();
- Incompatible formatter for writer type:
"Flush not working"
Symptom: Logs not appearing immediately, delayed until program exit.
Possible Causes:
- Flushing wrong writer:
auto inner = std::make_unique<file_writer>("app.log");
auto buffered = std::make_unique<buffered_writer>(std::move(inner), config);
auto outermost = std::make_unique<formatted_writer>(std::move(buffered), formatter);
outermost->flush();
- Not calling flush() at all:
auto writer = writer_builder().file("app.log").buffered(100).build();
logger.add_writer(
"main", std::move(writer));
logger.log(log_level::info,
"Message");
- Async writer not stopped properly:
auto writer = writer_builder().file("app.log").async().build();
if (auto* async_w = dynamic_cast<async_writer*>(writer.get())) {
async_w->start();
}
logger.add_writer(
"main", std::move(writer));
auto& writer_ref =
logger.get_writer(
"main");
if (auto* async_w = dynamic_cast<async_writer*>(&writer_ref)) {
async_w->stop();
}
- Buffer size too large:
auto writer = writer_builder()
.file("app.log")
.buffered(10000)
.build();
auto writer = writer_builder()
.file("app.log")
.buffered(100)
.build();
"Compilation errors with custom writers"
Symptom: Custom writer doesn't compile after migration to decorator pattern.
Possible Causes:
- Missing interface methods:
class my_writer : public thread_safe_writer {
};
class my_writer : public thread_safe_writer {
public:
std::string get_name() const override { return "my_writer"; }
protected:
return {};
}
return {};
}
};
- Wrong base class:
class my_writer : public base_writer { ... };
class my_writer : public thread_safe_writer { ... };
- Incorrect method signatures:
std::string get_name() { return "my_writer"; }
std::string get_name() const override { return "my_writer"; }
"Performance regression after migration"
Symptom: Logging is slower after switching to decorator pattern.
Possible Causes:
- Too many decorator layers:
auto writer = writer_builder()
.file("app.log")
.filtered(filter1)
.filtered(filter2)
.filtered(filter3)
.buffered(100)
.buffered(50)
.async()
.build();
auto composite_filter = std::make_unique<composite_filter>(logic_type::AND);
composite_filter->add_filter(std::move(filter1));
composite_filter->add_filter(std::move(filter2));
composite_filter->add_filter(std::move(filter3));
auto writer = writer_builder()
.file("app.log")
.filtered(std::move(composite_filter))
.buffered(100)
.async()
.build();
- Wrong decorator order:
auto slow = writer_builder()
.file("app.log")
.buffered(100)
.filtered(filter)
.build();
auto fast = writer_builder()
.file("app.log")
.filtered(filter)
.buffered(100)
.build();
- Async not used for high-throughput scenarios:
auto slow = writer_builder()
.file("app.log")
.buffered(100)
.build();
auto fast = writer_builder()
.file("app.log")
.buffered(100)
.async(20000)
.build();
Migration Checklist
Before starting migration:
- Read this entire migration guide
- Identify all existing writer usage in codebase
During migration:
- Identify cross-cutting concerns in existing writers
- Separate destination logic from filtering/buffering/formatting
- Create simple leaf writers inheriting from
thread_safe_writer
- Replace manual nesting with
writer_builder()
- Use built-in decorators for common functionality
- Create custom decorators only for unique cross-cutting needs
- Start async writers with
.start() where applicable
After migration:
- Update tests to verify decorator composition
- Test with actual log messages (info, warning, error levels)
- Verify flush() works correctly before program exit
- Benchmark to ensure no performance regression
- Update documentation for your project
- Remove deprecated code once validated
Getting Help
If you encounter issues not covered in this guide:
- Check examples/decorator_usage.cpp for comprehensive examples
- Review writer_builder_example.cpp for builder API patterns
- See Best Practices for general guidance
- Check FAQ for common questions
- File an issue on GitHub with:
- Current code (manual nesting)
- Expected behavior
- Error messages or compilation errors
- Platform and compiler version
Related Documentation
Part of Logger System v4.1+ documentation Last updated: 2026-02-01 for v4.1.0 deprecation timeline