Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@

import Foundation

/// Specifies the format to be used in the log message
/// A protocol that defines the interface for transforming and styling log messages.
///
/// Types conforming to `ILogFormatter` act as individual steps in a formatting pipeline.
/// They are responsible for taking a message string and augmenting it with additional
/// information—such as timestamps, tags, or emojis—based on the message's severity.
public protocol ILogFormatter {
/// Concatenates the specified attributes and generates the final log message
/// Processes the input message and returns a decorated version of it.
///
/// This method is called by a `Logger` or a `Printer` strategy to prepare the
/// message for final output. Since formatters are often used in a chain,
/// the `message` parameter might already contain modifications from previous formatters.
///
/// - Parameters:
/// - message: A `String` value that contains the message.
/// - logLevel: A `LogLevel` value that contains the logging level.
/// - message: The current string content of the log message.
/// - logLevel: The `LogLevel` associated with the message, used to determine
/// appropriate styling or metadata.
/// - Returns: A transformed string containing the formatted log message.
func format(message: String, with logLevel: LogLevel) -> String
}
39 changes: 28 additions & 11 deletions Sources/Log/Classes/Core/Formatters/PrefixLogFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,49 @@

import Foundation

/// A log formatter that adds a custom prefix to log messages.
/// A log formatter that prepends a custom identifier and visual indicators to log messages.
///
/// `PrefixLogFormatter` is used to categorize logs by adding a string tag (e.g., a module name).
/// It also automatically adds high-visibility emojis for critical levels like `.fault` and `.error`
/// to help them stand out in dense log outputs.
public struct PrefixLogFormatter: ILogFormatter {
// MARK: Properties
// MARK: - Properties

/// The custom prefix to be added to log messages.
/// The string identifier (e.g., "Network", "Auth") prepended to every log message.
private let name: String

// MARK: Initialization
// MARK: - Initialization

/// Creates a new `PrefixLogFormatter` instance with the specified prefix.
/// Initializes a new formatter with a specific name.
///
/// - Parameter name: The custom prefix to be added to log messages.
/// - Parameter name: The custom prefix string used to identify the source of the log.
public init(name: String) {
self.name = name
}

// MARK: ILogFormatter
// MARK: - ILogFormatter

/// Prepends the prefix and professional severity-based symbols to the log message.
///
/// The style is optimized for professional environments:
/// - `.fault`: Uses the stop sign (⛔️) to indicate a critical, unrecoverable failure.
/// - `.error`: Uses the warning sign (⚠️) to indicate a significant but recoverable issue.
/// - `.info`: Uses the information circle (ℹ️) for high-level progress tracking.
/// - `.debug`: Uses a simple diamond (🔹) for development-level details.
public func format(message: String, with logLevel: LogLevel) -> String {
switch logLevel {
let symbol = switch logLevel {
case .fault:
"🚨🚨🚨 [\(name)] => \(message)"
"⛔️ [\(name)]"
case .error:
"💣💥💣💥 [\(name)] => \(message)"
"⚠️ [\(name)]"
case .info:
"ℹ️ [\(name)]"
case .debug:
"🔹 [\(name)]"
default:
"[\(name)] => \(message)"
"[\(name)]"
}

return "\(symbol) => \(message)"
}
}
29 changes: 21 additions & 8 deletions Sources/Log/Classes/Core/Formatters/TimestampLogFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,44 @@

import Foundation

/// A log formatter that adds a timestamp to log messages.
/// A log formatter that prepends a chronological timestamp to log messages.
///
/// `TimestampLogFormatter` is essential for tracing the sequence of events during
/// application execution. It allows you to define a custom date format to match
/// your specific debugging or analytics requirements.
open class TimestampLogFormatter: ILogFormatter {
// MARK: Properties
// MARK: - Properties

/// The date formatter.
/// The internal date formatter used to convert the current system time into a string.
///
/// This property is lazily initialized to optimize performance and ensure the
/// formatter is only created when the first log message is processed.
private lazy var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return dateFormatter
}()

/// The date format string.
/// The string pattern defining the appearance of the timestamp (e.g., "yyyy-MM-dd HH:mm:ss").
private let dateFormat: String

// MARK: Initialization
// MARK: - Initialization

/// Creates a new `PrefixFormatter` instance with the specified data formmater.
/// Initializes a new `TimestampLogFormatter` with the specified date format.
///
/// - Parameter dateFormat: The date format string.
/// - Parameter dateFormat: A string representing the desired date and time pattern.
public init(dateFormat: String) {
self.dateFormat = dateFormat
}

// MARK: ILogFormatter
// MARK: - ILogFormatter

/// Prepends a timestamp based on the current system time to the log message.
///
/// - Parameters:
/// - message: The raw or previously formatted log message.
/// - logLevel: The severity level (ignored by this specific formatter).
/// - Returns: A string combining the timestamp and the message, separated by a space.
public func format(message: String, with _: LogLevel) -> String {
let timestamp = dateFormatter.string(from: Date())
return [timestamp, message].joined(separator: " ")
Expand Down
30 changes: 21 additions & 9 deletions Sources/Log/Classes/Core/Logger/ILogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@

import Foundation

/// A type that provides logging functionality.
/// A protocol defining the public interface for a logging coordinator.
///
/// `ILogger` provides a set of methods for dispatching messages at various severity levels.
/// Implementations should handle filtering based on the current configuration and ensure
/// that log production has minimal impact on application performance.
public protocol ILogger {
/// Dispatches the given message using the logger if the debug log level is set.
/// Dispatches a message for fine-grained informational events useful for debugging.
///
/// - Parameter message: An autoclosure returning the message to log.
/// - Parameter message: An autoclosure returning the string to log. The closure is
/// evaluated only if the `.debug` log level is active, preventing unnecessary
/// string construction.
func debug(message: @autoclosure () -> String)

/// Dispatches the given message using the logger if the info log level is set.
/// Dispatches an informational message that highlights the progress of the application.
///
/// - Parameter message: An autoclosure returning the message to log.
/// - Parameter message: An autoclosure returning the string to log. The closure is
/// evaluated only if the `.info` log level is active.
func info(message: @autoclosure () -> String)

/// Dispatches the given message using the logger if the fault log level is set.
/// Dispatches a message about a severe error or system-level fault.
///
/// - Parameter message: An autoclosure returning the message to log.
/// Use this for critical issues that may require system-level attention or indicate
/// a corruption of state.
///
/// - Parameter message: An autoclosure returning the string to log. The closure is
/// evaluated only if the `.fault` is active.
func fault(message: @autoclosure () -> String)

/// Dispatches the given message using the logger if the error log level is set.
/// Dispatches a message about an error event that might still allow the application to continue running.
///
/// - Parameter message: An autoclosure returning the message to log.
/// - Parameter message: An autoclosure returning the string to log. The closure is
/// evaluated only if the `.error` log level is active.
func error(message: @autoclosure () -> String)
}
67 changes: 49 additions & 18 deletions Sources/Log/Classes/Core/Logger/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,75 @@ import Foundation

// MARK: - Logger

/// A class responsible for logging functionality.
/// A high-level logging coordinator responsible for filtering and dispatching log messages.
///
/// `Logger` acts as the central entry point for the logging system. It evaluates whether a message
/// should be logged based on the current `logLevel` and then broadcasts allowed messages
/// to a collection of `IPrinterStrategy` implementations.
open class Logger {
// MARK: Properties

/// The current log level for this logger.
private var _logLevel: Atomic<LogLevel>
/// A recursive lock used to synchronize access to the logger's mutable state.
private let lock = NSRecursiveLock()

/// The current log level for this logger.
/// Internal storage for the log level, protected by a lock.
private var _logLevel: LogLevel

/// The active log level configuration for this logger.
///
/// This property uses a lock to ensure thread-safe read and write access.
public var logLevel: LogLevel {
get { _logLevel.value }
set { _logLevel.value = newValue }
lock.lock()
defer { lock.unlock() }
return _logLevel
}

/// An array of printer strategies to handle the log output.
/// The collection of output strategies (printers) that handle the actual delivery of log messages.
let printers: [IPrinterStrategy]

// MARK: Initialization
// MARK: - Initialization

/// Initializes a new Logger instance.
/// Initializes a new `Logger` instance.
///
/// - Parameters:
/// - printers: An array of printer strategies.
/// - logLevel: The initial log level.
/// - printers: An array of strategies defining where logs should be sent (e.g., Console, OSLog, File).
/// - logLevel: The initial set of allowed log levels. Defaults to specific levels if not provided.
public init(
printers: [IPrinterStrategy],
logLevel: LogLevel
) {
self.printers = printers
_logLevel = Atomic(value: logLevel)
_logLevel = logLevel
}

/// Atomically updates the current log level using a transformation closure.
///
/// This method prevents race conditions that can occur when multiple threads try to
/// modify the `logLevel` simultaneously (e.g., two threads trying to add different flags at once).
///
/// - Parameter transform: A closure that receives the current `LogLevel` and returns the new desired value.
public func updateLogLevel(_ transform: (LogLevel) -> LogLevel) {
lock.lock()
defer { lock.unlock() }
_logLevel = transform(_logLevel)
}

// MARK: Private
// MARK: - Private Methods

/// Passes the message to each receiver's printer.
/// Dispatches a message to all registered printers if the log level is enabled.
///
/// - Parameters:
/// - message: The message to dispatch.
/// - logLevel: The message's level.
/// - message: The string content to log.
/// - logLevel: The severity level associated with this message.
private func log(_ message: String, logLevel: LogLevel) {
guard isLoggerEnabled(for: logLevel) else { return }
printers.forEach { $0.log(message, logLevel: logLevel) }
}

/// Checks if the given `LogLevel` is allowed by the receiver.
/// Evaluates whether the current logger configuration allows a specific log level.
///
/// - Parameter logLevel: The log level to check.
/// - Parameter logLevel: The level to validate against the current settings.
/// - Returns: `true` if the level is included in the active `logLevel` set.
private func isLoggerEnabled(for logLevel: LogLevel) -> Bool {
self.logLevel.contains(logLevel)
}
Expand All @@ -61,18 +84,26 @@ open class Logger {
// MARK: ILogger

extension Logger: ILogger {
/// Logs a message for debugging purposes.
/// - Parameter message: A closure returning the string to log, evaluated only if `.debug` is enabled.
public func debug(message: @autoclosure () -> String) {
log(message(), logLevel: .debug)
}

/// Logs an informational message highlighting application progress.
/// - Parameter message: A closure returning the string to log, evaluated only if `.info` is enabled.
public func info(message: @autoclosure () -> String) {
log(message(), logLevel: .info)
}

/// Logs a critical fault that may require system-level attention.
/// - Parameter message: A closure returning the string to log, evaluated only if `.fault` is enabled.
public func fault(message: @autoclosure () -> String) {
log(message(), logLevel: .fault)
}

/// Logs an error that occurred during execution.
/// - Parameter message: A closure returning the string to log, evaluated only if `.error` is enabled.
public func error(message: @autoclosure () -> String) {
log(message(), logLevel: .error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@

import Foundation

/// A protocol that defines the behavior of a logging printer strategy
/// A protocol that defines a strategy for processing and delivering log messages.
///
/// `IPrinterStrategy` combines the responsibilities of message formatting and
/// final output delivery. Types conforming to this protocol use an array of
/// `ILogFormatter` objects to transform raw messages before writing them
/// to their respective destinations (e.g., Console, System Logs, or Files).
public protocol IPrinterStrategy {
/// An array of log formatters to customize log message output.
/// A collection of formatters used to process and style the log message content.
///
/// The formatters are typically applied in sequence to add metadata such as
/// timestamps, emojis, or thread information.
var formatters: [ILogFormatter] { get }

/// Logs a message with a specified log level.
/// Processes and dispatches a log message.
///
/// Implementation should first pass the raw message through the `formatters`
/// chain and then transmit the resulting string to the specific output target.
///
/// - Parameters:
/// - message: A `String` value that contains the message to dispatch.
/// - logLevel: A `LogLevel` value that contains the logging level.
/// - message: The raw string content provided by the logger.
/// - logLevel: The severity level used for both formatting decisions and
/// target-specific severity mapping.
func log(_ message: String, logLevel: LogLevel)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,31 @@ import Foundation

// MARK: - IStyleLogStrategy

/// Specifies the format to be used in the log message
/// A specialized printer strategy that focuses on styling log messages before output.
///
/// `IStyleLogStrategy` provides a default implementation for taking a raw log string
/// and passing it through a chain of formatters. This ensures consistency in how
/// logs look across different output targets (e.g., Console vs. OSLog).
protocol IStyleLogStrategy: IPrinterStrategy {
/// An array of log formatters to customize log message output.
/// A collection of formatters used to customize the visual representation of the log.
var formatters: [ILogFormatter] { get }
}

extension IStyleLogStrategy {
/// Format the message using formatting rules.
/// Transforms a raw message into a formatted string by applying all registered formatters.
///
/// This method iterates through the `formatters` array in the order they were provided,
/// allowing each formatter to append, prefix, or modify the string from the previous step.
///
/// - Parameters:
/// - message: A `String` value that contains the message.
/// - logLevel: A `LogLevel` value that contains the logging level.
/// - message: The original raw string content to be logged.
/// - logLevel: The severity level, which formatters use to determine styling
/// (e.g., adding a 🔴 emoji for errors).
///
/// - Returns: Formatted message.
/// - Returns: A final, fully styled string ready for output.
func formatMessage(_ message: String, logLevel: LogLevel) -> String {
var message = message
// Sequentially transform the message string using the pipeline of formatters.
formatters.forEach { message = $0.format(message: message, with: logLevel) }
return message
}
Expand Down
Loading