12#ifdef PACS_WITH_LOGGER_SYSTEM
13#include <kcenon/common/interfaces/logger_interface.h>
14#include <kcenon/logger/core/logger.h>
15#include <kcenon/logger/interfaces/logger_types.h>
16#include <kcenon/logger/writers/console_writer.h>
17#include <kcenon/logger/writers/rotating_file_writer.h>
34#ifdef PACS_WITH_LOGGER_SYSTEM
36class logger_adapter::impl {
42 std::lock_guard lock(mutex_);
49 min_level_.store(config.min_level);
52 if (config.enable_file || config.enable_audit_log) {
53 std::filesystem::create_directories(config.log_directory);
57 logger_ = std::make_unique<kcenon::logger::logger>(
58 config.async_mode, config.buffer_size);
61 (void)logger_->set_level(convert_to_common_log_level(config.min_level));
64 if (config.enable_console) {
65 logger_->add_writer(std::make_unique<kcenon::logger::console_writer>());
69 if (config.enable_file) {
70 auto log_path = config.log_directory /
"pacs.log";
71 auto writer = std::make_unique<kcenon::logger::rotating_file_writer>(
73 config.max_file_size_mb * 1024 * 1024,
75 logger_->add_writer(std::move(writer));
82 if (config.enable_audit_log) {
83 audit_log_path_ = config.log_directory /
"audit.json";
90 std::lock_guard lock(mutex_);
102 initialized_ =
false;
106 return initialized_.load();
110 if (!initialized_ || !logger_) {
118 (void)logger_->log(convert_to_common_log_level(level), message);
123 return static_cast<int>(level) >=
static_cast<int>(min_level_.load());
133 min_level_.store(level);
135 (void)logger_->set_level(convert_to_common_log_level(level));
140 return min_level_.load();
143 [[nodiscard]]
auto get_config() const -> const logger_config& {
return config_; }
146 const std::string& outcome,
147 const std::map<std::string, std::string>& fields) {
148 if (!config_.enable_audit_log) {
152 std::lock_guard lock(audit_mutex_);
154 std::ofstream file(audit_log_path_, std::ios::app);
160 std::ostringstream json;
162 json <<
"\"timestamp\":\"" << format_iso8601() <<
"\",";
163 json <<
"\"event_type\":\"" << escape_json(event_type) <<
"\",";
164 json <<
"\"outcome\":\"" << escape_json(outcome) <<
"\"";
166 for (
const auto& [key, value] : fields) {
167 json <<
",\"" << escape_json(key) <<
"\":\"" << escape_json(value) <<
"\"";
177 [[nodiscard]]
static auto convert_to_common_log_level(
log_level level) -> kcenon::common::interfaces::log_level {
180 return kcenon::common::interfaces::log_level::trace;
182 return kcenon::common::interfaces::log_level::debug;
184 return kcenon::common::interfaces::log_level::info;
186 return kcenon::common::interfaces::log_level::warning;
188 return kcenon::common::interfaces::log_level::error;
190 return kcenon::common::interfaces::log_level::critical;
193 return kcenon::common::interfaces::log_level::off;
197 [[nodiscard]]
static auto format_iso8601() -> std::string {
198 auto now = std::chrono::system_clock::now();
199 auto time_t_val = std::chrono::system_clock::to_time_t(now);
200 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
201 now.time_since_epoch()) %
206 localtime_s(&tm_val, &time_t_val);
208 localtime_r(&time_t_val, &tm_val);
211 std::ostringstream oss;
212 oss << std::put_time(&tm_val,
"%Y-%m-%dT%H:%M:%S");
213 oss <<
'.' << std::setfill(
'0') << std::setw(3) << ms.count();
214 oss << std::put_time(&tm_val,
"%z");
218 [[nodiscard]]
static auto escape_json(
const std::string& str) -> std::string {
219 std::ostringstream oss;
244 if (
static_cast<unsigned char>(c) < 32) {
245 oss <<
"\\u" << std::hex << std::setw(4) << std::setfill(
'0')
246 <<
static_cast<int>(c);
256 mutable std::mutex mutex_;
257 mutable std::mutex audit_mutex_;
258 std::atomic<bool> initialized_{
false};
260 logger_config config_;
261 std::unique_ptr<kcenon::logger::logger> logger_;
262 std::filesystem::path audit_log_path_;
270 std::make_unique<logger_adapter::impl>();
277 pimpl_->initialize(config);
283 return pimpl_->is_initialized();
291 pimpl_->log(level, message);
295 return pimpl_->is_level_enabled(level);
305 pimpl_->set_min_level(level);
309 return pimpl_->get_min_level();
313 return pimpl_->get_config();
321 const std::string& event_type,
322 const std::string& outcome,
323 const std::map<std::string, std::string>& fields) {
324 pimpl_->write_audit_log(event_type, outcome, fields);
348 return default_config;
354 const std::map<std::string, std::string>&) {}
363 const std::string& called_ae,
364 const std::string& remote_ip) {
365 info(
"Association established: {} -> {} from {}",
366 calling_ae, called_ae, remote_ip);
369 {{
"calling_ae", calling_ae},
370 {
"called_ae", called_ae},
371 {
"remote_ip", remote_ip}});
375 const std::string& called_ae) {
376 debug(
"Association released: {} -> {}", calling_ae, called_ae);
379 {{
"calling_ae", calling_ae}, {
"called_ae", called_ae}});
383 const std::string& patient_id,
384 const std::string& study_uid,
385 const std::string& sop_instance_uid,
391 info(
"C-STORE received: patient={} study={} instance={} from {}",
392 patient_id, study_uid, sop_instance_uid, calling_ae);
394 warn(
"C-STORE failed: patient={} status={} from {}",
395 patient_id, status_str, calling_ae);
399 {{
"calling_ae", calling_ae},
400 {
"patient_id", patient_id},
401 {
"study_uid", study_uid},
402 {
"sop_instance_uid", sop_instance_uid},
403 {
"status", status_str}});
408 std::size_t matches_returned) {
411 debug(
"C-FIND executed: level={} matches={} from {}",
412 level_str, matches_returned, calling_ae);
415 {{
"calling_ae", calling_ae},
416 {
"query_level", level_str},
417 {
"matches_returned", std::to_string(matches_returned)}});
421 const std::string& destination_ae,
422 const std::string& study_uid,
423 std::size_t instances_moved,
432 info(
"C-MOVE completed: study={} instances={} to {} from {}",
433 study_uid, instances_moved, destination_ae, calling_ae);
435 warn(
"C-MOVE failed: study={} status={} to {} from {}",
436 study_uid, status_str, destination_ae, calling_ae);
440 {{
"calling_ae", calling_ae},
441 {
"destination_ae", destination_ae},
442 {
"study_uid", study_uid},
443 {
"instances_moved", std::to_string(instances_moved)},
444 {
"status", status_str}});
448 const std::string& description,
449 const std::string& user_id) {
455 info(
"Security event: {} - {}", type_str, description);
461 warn(
"Security event: {} - {}", type_str, description);
465 info(
"Security event: {} - {}", type_str, description);
469 std::map<std::string, std::string> fields = {
470 {
"security_event", type_str}, {
"description", description}};
472 if (!user_id.empty()) {
473 fields[
"user_id"] = user_id;
488 return "OutOfResources";
490 return "DataSetError";
492 return "CannotUnderstand";
494 return "ProcessingFailure";
496 return "DuplicateRejected";
498 return "DuplicateStored";
501 return "UnknownError";
510 return "PartialSuccess";
512 return "RefusedOutOfResources";
514 return "RefusedMoveDestinationUnknown";
516 return "IdentifierDoesNotMatch";
518 return "UnableToProcess";
523 return "UnknownError";
545 return "authentication_success";
547 return "authentication_failure";
549 return "access_denied";
551 return "configuration_change";
553 return "data_export";
555 return "association_rejected";
557 return "invalid_request";
static std::unique_ptr< impl > pimpl_
static void flush()
Flush all pending log messages.
static void log_c_find_executed(const std::string &calling_ae, query_level level, std::size_t matches_returned)
Log C-FIND operation.
static void log_security_event(security_event_type type, const std::string &description, const std::string &user_id="")
Log a security-related event.
static void write_audit_log(const std::string &event_type, const std::string &outcome, const std::map< std::string, std::string > &fields)
static void log_c_store_received(const std::string &calling_ae, const std::string &patient_id, const std::string &study_uid, const std::string &sop_instance_uid, storage_status status)
Log C-STORE operation.
static void log_association_released(const std::string &calling_ae, const std::string &called_ae)
Log DICOM association release.
static void log_c_move_executed(const std::string &calling_ae, const std::string &destination_ae, const std::string &study_uid, std::size_t instances_moved, move_status status)
Log C-MOVE operation.
static void shutdown()
Shutdown the logger.
static auto security_event_to_string(security_event_type type) -> std::string
static void log(log_level level, const std::string &message)
Log a message at the specified level.
static auto get_config() -> const logger_config &
Get the current configuration.
static auto log_level_to_string(log_level level) -> std::string
static auto storage_status_to_string(storage_status status) -> std::string
static auto is_level_enabled(log_level level) noexcept -> bool
Check if a log level is enabled.
static void set_min_level(log_level level)
Set the minimum log level.
static auto move_status_to_string(move_status status) -> std::string
static auto is_initialized() noexcept -> bool
Check if the logger is initialized.
static void initialize(const logger_config &config)
Initialize the logger with configuration.
static auto get_min_level() noexcept -> log_level
Get the current minimum log level.
static void log_association_established(const std::string &calling_ae, const std::string &called_ae, const std::string &remote_ip)
Log DICOM association establishment.
static auto query_level_to_string(query_level level) -> std::string
Adapter for DICOM audit logging using logger_system.
storage_status
Status of DICOM C-STORE operations.
security_event_type
Types of security events for audit logging.
log_level
Log severity levels.
query_level
DICOM query retrieve level.
move_status
Status of DICOM C-MOVE operations.
@ identifier_does_not_match
@ refused_move_destination_unknown
@ refused_out_of_resources
Configuration options for the logger adapter.