diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..8dc7e75
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,41 @@
+---
+name: "🐛 Bug Report"
+about: Report a reproducible bug or regression.
+title: 'Bug: '
+labels: 'bug'
+
+---
+
+
+
+Application version:
+
+## Steps To Reproduce
+
+1.
+2.
+
+
+
+Link to code example:
+
+
+
+## The current behavior
+
+
+## The expected behavior
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..6b2eb8c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,11 @@
+---
+name: 🛠 Feature request
+about: If you have a feature request for the log, file it here.
+labels: 'type: enhancement'
+---
+
+**Feature description**
+Clearly and concisely describe the feature.
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_template.yml b/.github/PULL_REQUEST_TEMPLATE/bug_template.yml
new file mode 100644
index 0000000..7d6a149
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/bug_template.yml
@@ -0,0 +1,9 @@
+## Bug description
+Clearly and concisely describe the problem.
+
+## Solution description
+Describe your code changes in detail for reviewers. Explain the technical solution you have provided and how it fixes the issue case.
+
+## Covered unit test cases
+- [x] yes
+- [x] no
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/feature_template.yml b/.github/PULL_REQUEST_TEMPLATE/feature_template.yml
new file mode 100644
index 0000000..ab3978b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/feature_template.yml
@@ -0,0 +1,12 @@
+## Feature description
+Clearly and concisely describe the feature.
+
+## Solution description
+Describe your code changes in detail for reviewers.
+
+## Areas affected and ensured
+List out the areas affected by your code changes.
+
+## Covered unit test cases
+- [x] yes
+- [x] no
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d544d32
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,67 @@
+name: "log"
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ pull_request:
+ paths:
+ - '.swiftlint.yml'
+ - ".github/workflows/**"
+ - "Package.swift"
+ - "Source/**"
+ - "Tests/**"
+
+concurrency:
+ group: ci
+ cancel-in-progress: true
+
+jobs:
+ SwiftLint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: GitHub Action for SwiftLint
+ uses: norio-nomura/action-swiftlint@3.2.1
+ with:
+ args: --strict
+ env:
+ DIFF_BASE: ${{ github.base_ref }}
+ Latest:
+ name: Test Latest (iOS, macOS, tvOS, watchOS)
+ runs-on: macOS-12
+ env:
+ DEVELOPER_DIR: "/Applications/Xcode_14.1.app/Contents/Developer"
+ timeout-minutes: 10
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - destination: "OS=16.1,name=iPhone 14 Pro"
+ name: "iOS"
+ scheme: "Log"
+ sdk: iphonesimulator
+ - destination: "OS=16.1,name=Apple TV"
+ name: "tvOS"
+ scheme: "Log"
+ sdk: appletvsimulator
+ - destination: "OS=9.1,name=Apple Watch Series 8 (45mm)"
+ name: "watchOS"
+ scheme: "Log"
+ sdk: watchsimulator
+ - destination: "platform=macOS"
+ name: "macOS"
+ scheme: "Log"
+ sdk: macosx
+ steps:
+ - uses: actions/checkout@v3
+ - name: ${{ matrix.name }}
+ run: xcodebuild test -scheme "${{ matrix.scheme }}" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "./${{ matrix.sdk }}.xcresult" | xcpretty -r junit
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v3.1.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ xcode: true
+ xcode_archive_path: "./${{ matrix.sdk }}.xcresult"
+
\ No newline at end of file
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
new file mode 100644
index 0000000..1a0ab52
--- /dev/null
+++ b/.github/workflows/danger.yml
@@ -0,0 +1,29 @@
+name: Danger
+
+on:
+ pull_request:
+ types: [synchronize, opened, reopened, labeled, unlabeled, edited]
+
+env:
+ LC_CTYPE: en_US.UTF-8
+ LANG: en_US.UTF-8
+
+jobs:
+ run-danger:
+ runs-on: ubuntu-latest
+ steps:
+ - name: ruby setup
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 2.7
+ bundler-cache: true
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Setup gems
+ run: |
+ gem install bundler
+ bundle install --clean --path vendor/bundle
+ - name: danger
+ env:
+ DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
+ run: bundle exec danger --verbose
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 330d167..3b29812 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,90 +1,9 @@
-# Xcode
-#
-# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-
-## User settings
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
xcuserdata/
-
-## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
-*.xcscmblueprint
-*.xccheckout
-
-## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
-build/
DerivedData/
-*.moved-aside
-*.pbxuser
-!default.pbxuser
-*.mode1v3
-!default.mode1v3
-*.mode2v3
-!default.mode2v3
-*.perspectivev3
-!default.perspectivev3
-
-## Obj-C/Swift specific
-*.hmap
-
-## App packaging
-*.ipa
-*.dSYM.zip
-*.dSYM
-
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
-# Swift Package Manager
-#
-# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
-# Packages/
-# Package.pins
-# Package.resolved
-# *.xcodeproj
-#
-# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
-# hence it is not needed unless you have added a package configuration file to your project
-# .swiftpm
-
-.build/
-
-# CocoaPods
-#
-# We recommend against adding the Pods directory to your .gitignore. However
-# you should judge for yourself, the pros and cons are mentioned at:
-# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
-#
-# Pods/
-#
-# Add this line if you want to avoid checking in source code from the Xcode workspace
-# *.xcworkspace
-
-# Carthage
-#
-# Add this line if you want to avoid checking in source code from Carthage dependencies.
-# Carthage/Checkouts
-
-Carthage/Build/
-
-# Accio dependency management
-Dependencies/
-.accio/
-
-# fastlane
-#
-# It is recommended to not store the screenshots in the git repo.
-# Instead, use fastlane to re-generate the screenshots whenever they are needed.
-# For more information about the recommended setup visit:
-# https://docs.fastlane.tools/best-practices/source-control/#source-control
-
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots/**/*.png
-fastlane/test_output
-
-# Code Injection
-#
-# After new code Injection tools there's a generated folder /iOSInjectionProject
-# https://github.com/johnno1962/injectionforxcode
-
-iOSInjectionProject/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..80a521d
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,64 @@
+# Stream rules
+
+--swiftversion 5.3
+
+# Use 'swiftformat --options' to list all of the possible options
+
+--header "\nlog\nCopyright © {created.year} Space Code. All rights reserved.\n//"
+
+--enable blankLinesBetweenScopes
+--enable blankLinesAtStartOfScope
+--enable blankLinesAtEndOfScope
+--enable blankLinesAroundMark
+--enable anyObjectProtocol
+--enable consecutiveBlankLines
+--enable consecutiveSpaces
+--enable duplicateImports
+--enable elseOnSameLine
+--enable emptyBraces
+--enable initCoderUnavailable
+--enable leadingDelimiters
+--enable numberFormatting
+--enable preferKeyPath
+--enable redundantBreak
+--enable redundantExtensionACL
+--enable redundantFileprivate
+--enable redundantGet
+--enable redundantInit
+--enable redundantLet
+--enable redundantLetError
+--enable redundantNilInit
+--enable redundantObjc
+--enable redundantParens
+--enable redundantPattern
+--enable redundantRawValues
+--enable redundantReturn
+--enable redundantSelf
+--enable redundantVoidReturnType
+--enable semicolons
+--enable sortImports
+--enable sortSwitchCases
+--enable spaceAroundBraces
+--enable spaceAroundBrackets
+--enable spaceAroundComments
+--enable spaceAroundGenerics
+--enable spaceAroundOperators
+--enable spaceInsideBraces
+--enable spaceInsideBrackets
+--enable spaceInsideComments
+--enable spaceInsideGenerics
+--enable spaceInsideParens
+--enable strongOutlets
+--enable strongifiedSelf
+--enable todos
+--enable trailingClosures
+--enable unusedArguments
+--enable void
+--enable markTypes
+--enable isEmpty
+
+# format options
+
+--wraparguments before-first
+--wrapcollections before-first
+--maxwidth 140
\ No newline at end of file
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 0000000..6d90edc
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,134 @@
+excluded:
+ - Tests
+ - Package.swift
+ - .build
+
+# Rules
+
+disabled_rules:
+ - trailing_comma
+ - todo
+ - opening_brace
+
+opt_in_rules: # some rules are only opt-in
+ - anyobject_protocol
+ - array_init
+ - attributes
+ - closure_body_length
+ - closure_end_indentation
+ - closure_spacing
+ - collection_alignment
+ - contains_over_filter_count
+ - contains_over_filter_is_empty
+ - contains_over_first_not_nil
+ - contains_over_range_nil_comparison
+ - convenience_type
+ - discouraged_object_literal
+ - discouraged_optional_boolean
+ - empty_collection_literal
+ - empty_count
+ - empty_string
+ - empty_xctest_method
+ - enum_case_associated_values_count
+ - explicit_init
+ - fallthrough
+ - fatal_error_message
+ - file_name
+ - file_types_order
+ - first_where
+ - flatmap_over_map_reduce
+ - force_unwrapping
+ - ibinspectable_in_extension
+ - identical_operands
+ - implicit_return
+ - inert_defer
+ - joined_default_parameter
+ - last_where
+ - legacy_multiple
+ - legacy_random
+ - literal_expression_end_indentation
+ - lower_acl_than_parent
+ - multiline_arguments
+ - multiline_function_chains
+ - multiline_literal_brackets
+ - multiline_parameters
+ - multiline_parameters_brackets
+ - no_space_in_method_call
+ - operator_usage_whitespace
+ - optional_enum_case_matching
+ - orphaned_doc_comment
+ - overridden_super_call
+ - override_in_extension
+ - pattern_matching_keywords
+ - prefer_self_type_over_type_of_self
+ - prefer_zero_over_explicit_init
+ - prefixed_toplevel_constant
+ - private_action
+ - prohibited_super_call
+ - quick_discouraged_call
+ - quick_discouraged_focused_test
+ - quick_discouraged_pending_test
+ - reduce_into
+ - redundant_nil_coalescing
+ - redundant_objc_attribute
+ - redundant_type_annotation
+ - required_enum_case
+ - single_test_class
+ - sorted_first_last
+ - sorted_imports
+ - static_operator
+ - strict_fileprivate
+ - switch_case_on_newline
+ - toggle_bool
+ - unavailable_function
+ - unneeded_parentheses_in_closure_argument
+ - unowned_variable_capture
+ - untyped_error_in_catch
+ - vertical_parameter_alignment_on_call
+ - vertical_whitespace_closing_braces
+ - vertical_whitespace_opening_braces
+ - xct_specific_matcher
+ - yoda_condition
+
+force_cast: warning
+force_try: warning
+
+identifier_name:
+ excluded:
+ - id
+ - URL
+
+analyzer_rules:
+ - unused_import
+ - unused_declaration
+
+line_length:
+ warning: 130
+ error: 200
+
+type_body_length:
+ warning: 300
+ error: 400
+
+file_length:
+ warning: 500
+ error: 1200
+
+function_body_length:
+ warning: 30
+ error: 50
+
+large_tuple:
+ error: 3
+
+nesting:
+ type_level:
+ warning: 2
+ statement_level:
+ warning: 10
+
+
+type_name:
+ max_length:
+ warning: 40
+ error: 50
\ No newline at end of file
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Log.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Log.xcscheme
new file mode 100644
index 0000000..11ee50b
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Log.xcscheme
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..259b66b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+All notable changes to this project will be documented in this file.
+
+#### 1.x Releases
+- `1.0.x` Releases - [1.0.0](#100)
+
+## [1.0.0](https://github.com/space-code/log/releases/tag/1.0.0)
+Released on 2023-10-18.
+
+#### Added
+- Initial release of log.
+ - Added by [Nikita Vasilev](https://github.com/nik3212).
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
index 0000000..b266982
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1 @@
+danger.import_dangerfile(github: 'space-code/dangerfile')
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..20dff64
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem 'danger'
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..15dfc08
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,66 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.8.5)
+ public_suffix (>= 2.0.2, < 6.0)
+ base64 (0.1.1)
+ claide (1.1.0)
+ claide-plugins (0.9.2)
+ cork
+ nap
+ open4 (~> 1.3)
+ colored2 (3.1.2)
+ cork (0.3.0)
+ colored2 (~> 3.1)
+ danger (9.3.2)
+ claide (~> 1.0)
+ claide-plugins (>= 0.9.2)
+ colored2 (~> 3.1)
+ cork (~> 0.1)
+ faraday (>= 0.9.0, < 3.0)
+ faraday-http-cache (~> 2.0)
+ git (~> 1.13)
+ kramdown (~> 2.3)
+ kramdown-parser-gfm (~> 1.0)
+ no_proxy_fix
+ octokit (~> 6.0)
+ terminal-table (>= 1, < 4)
+ faraday (2.7.11)
+ base64
+ faraday-net_http (>= 2.0, < 3.1)
+ ruby2_keywords (>= 0.0.4)
+ faraday-http-cache (2.5.0)
+ faraday (>= 0.8)
+ faraday-net_http (3.0.2)
+ git (1.18.0)
+ addressable (~> 2.8)
+ rchardet (~> 1.8)
+ kramdown (2.4.0)
+ rexml
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ nap (1.1.0)
+ no_proxy_fix (0.1.2)
+ octokit (6.1.1)
+ faraday (>= 1, < 3)
+ sawyer (~> 0.9)
+ open4 (1.3.4)
+ public_suffix (5.0.3)
+ rchardet (1.8.0)
+ rexml (3.2.6)
+ ruby2_keywords (0.0.5)
+ sawyer (0.9.2)
+ addressable (>= 2.3.5)
+ faraday (>= 0.17.3, < 3)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ unicode-display_width (2.5.0)
+
+PLATFORMS
+ x86_64-darwin-22
+
+DEPENDENCIES
+ danger
+
+BUNDLED WITH
+ 2.4.10
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..856d64b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+all: bootstrap
+
+bootstrap: hook
+ mint bootstrap
+
+hook:
+ ln -sf ../../hooks/pre-commit .git/hooks/pre-commit
+ chmod +x .git/hooks/pre-commit
+
+mint:
+ mint bootstrap
+
+lint:
+ mint run swiftlint
+
+fmt:
+ mint run swiftformat Sources Tests
+
+.PHONY: all bootstrap hook mint lint fmt
\ No newline at end of file
diff --git a/Mintfile b/Mintfile
new file mode 100644
index 0000000..e2cdefa
--- /dev/null
+++ b/Mintfile
@@ -0,0 +1,2 @@
+nicklockwood/SwiftFormat@0.52.7
+realm/SwiftLint@0.53.0
\ No newline at end of file
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..7db16b1
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,23 @@
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "Log",
+ platforms: [
+ .macOS(.v10_15),
+ .iOS(.v13),
+ .watchOS(.v7),
+ .tvOS(.v13),
+ .visionOS(.v1),
+ ],
+ products: [
+ .library(name: "Log", targets: ["Log"]),
+ ],
+ dependencies: [],
+ targets: [
+ .target(name: "Log", dependencies: []),
+ .testTarget(name: "LogTests", dependencies: ["Log"]),
+ ]
+)
diff --git a/Package@swift-5.7.swift b/Package@swift-5.7.swift
new file mode 100644
index 0000000..cdef89b
--- /dev/null
+++ b/Package@swift-5.7.swift
@@ -0,0 +1,24 @@
+// swift-tools-version: 5.7
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+// swiftlint:disable all
+
+import PackageDescription
+
+let package = Package(
+ name: "Log",
+ platforms: [
+ .macOS(.v10_15),
+ .iOS(.v13),
+ .watchOS(.v7),
+ .tvOS(.v13),
+ ],
+ products: [
+ .library(name: "Log", targets: ["Log"]),
+ ],
+ dependencies: [],
+ targets: [
+ .target(name: "Log", dependencies: []),
+ .testTarget(name: "LogTests", dependencies: ["Log"]),
+ ]
+)
+// swiftlint:enable all
diff --git a/README.md b/README.md
index a04f6aa..03b197d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,114 @@
-# log
\ No newline at end of file
+![Log: A lightweight logging framework written in Swift](https://raw.githubusercontent.com/space-code/log/dev/Resources/log.png)
+
+
log
+
+
+
+
+
+
+
+
+
+
+## Description
+`Log` is a lightweight logging framework written in Swift.
+
+- [Usage](#usage)
+- [Requirements](#requirements)
+- [Installation](#installation)
+- [Communication](#communication)
+- [Contributing](#contributing)
+- [Author](#author)
+- [License](#license)
+
+## Usage
+
+### Create a logger instance
+
+First, you need to create an instance of `IPrinter` that prints messages to a specific output, such as XCode's console or the `Console` app.
+The `log` package provides predefined printers for printing messages in the XCode console (`ConsolePrinter`) and the system console (`OSPrinter`). You can also create your own printer. To do this, your object must conform to `IPrinterStrategy` and implement the necessary methods.
+
+```swift
+import Log
+
+let osPrinter = OSPrinter()
+let consolePrinter = ConsolePrinter()
+```
+
+Second, create a `Logger` instance and pass these printers as initialization parameters while defining a log level. The log level determines the level of log messages to print. If the log level is set to a specific level, all messages with different log levels will be ignored. To print all messages, use `.all`.
+
+```swift
+let log = Logger(
+ printers: [osPrinter, consolePrinter],
+ logLevel: .all
+)
+log.error(message: "test message")
+```
+
+### Formatting a message
+
+Each instance of `IPrinter` has an array of formatters that are responsible for formatting input messages. The `log` package provides predefined prefix and timestamp formatters. To use these, you need to pass them to an initializer of a printer.
+
+```swift
+let osPrinter = OSPrinter(formatters: [PrefixFormatter(name: "your prefix here")])
+...
+log.fault(message: "message") // "🚨🚨🚨 [your prefix here] => message"
+```
+
+Here is a list of predefined formatters:
+
+| **Formatters** | **Description** |
+|----------------------------|-------------------------------------------------------------------------------------|
+| **PrefixLogFormatter** | Add a specified prefix to a printed message |
+| **TimestampLogFormatter** | Add a timestamp before a printed message based on a date format |
+
+### Custom formatters
+
+If you want to create a custom message formatter, your object must conform to the `ILogFormatter` protocol and implement the necessary methods.
+
+```swift
+struct MyCustomMessageFormatter: ILogFormatter {
+ func format(message: String, with logLevel: LogLevel) -> String {
+ // your implementation here
+ }
+}
+```
+
+## Requirements
+- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ / visionOS 1.0+
+- Xcode 14.0
+- Swift 5.7
+
+## Installation
+### Swift Package Manager
+
+The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but `log` does support its use on supported platforms.
+
+Once you have your Swift package set up, adding `log` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
+
+```swift
+dependencies: [
+ .package(url: "https://github.com/space-code/log.git", .upToNextMajor(from: "1.0.0"))
+]
+```
+
+## Communication
+- If you **found a bug**, open an issue.
+- If you **have a feature request**, open an issue.
+- If you **want to contribute**, submit a pull request.
+
+## Contributing
+Bootstrapping development environment
+
+```
+make bootstrap
+```
+
+Please feel free to help out with this project! If you see something that could be made better or want a new feature, open up an issue or send a Pull Request!
+
+## Author
+Nikita Vasilev, nv3212@gmail.com
+
+## License
+log is available under the MIT license. See the LICENSE file for more info.
diff --git a/Resources/log.png b/Resources/log.png
new file mode 100644
index 0000000..70fc819
Binary files /dev/null and b/Resources/log.png differ
diff --git a/Sources/Log/Classes/Core/Formatters/Interfaces/ILogFormatter.swift b/Sources/Log/Classes/Core/Formatters/Interfaces/ILogFormatter.swift
new file mode 100644
index 0000000..3ebcd8e
--- /dev/null
+++ b/Sources/Log/Classes/Core/Formatters/Interfaces/ILogFormatter.swift
@@ -0,0 +1,16 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// Specifies the format to be used in the log message
+public protocol ILogFormatter {
+ /// Concatenates the specified attributes and generates the final log message
+ ///
+ /// - Parameters:
+ /// - message: A `String` value that contains the message.
+ /// - logLevel: A `LogLevel` value that contains the logging level.
+ func format(message: String, with logLevel: LogLevel) -> String
+}
diff --git a/Sources/Log/Classes/Core/Formatters/PrefixLogFormatter.swift b/Sources/Log/Classes/Core/Formatters/PrefixLogFormatter.swift
new file mode 100644
index 0000000..02232df
--- /dev/null
+++ b/Sources/Log/Classes/Core/Formatters/PrefixLogFormatter.swift
@@ -0,0 +1,36 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A log formatter that adds a custom prefix to log messages.
+public struct PrefixLogFormatter: ILogFormatter {
+ // MARK: Properties
+
+ /// The custom prefix to be added to log messages.
+ private let name: String
+
+ // MARK: Initialization
+
+ /// Creates a new `PrefixLogFormatter` instance with the specified prefix.
+ ///
+ /// - Parameter name: The custom prefix to be added to log messages.
+ public init(name: String) {
+ self.name = name
+ }
+
+ // MARK: ILogFormatter
+
+ public func format(message: String, with logLevel: LogLevel) -> String {
+ switch logLevel {
+ case .fault:
+ return "🚨🚨🚨 [\(name)] => \(message)"
+ case .error:
+ return "💣💥💣💥 [\(name)] => \(message)"
+ default:
+ return "[\(name)] => \(message)"
+ }
+ }
+}
diff --git a/Sources/Log/Classes/Core/Formatters/TimestampLogFormatter.swift b/Sources/Log/Classes/Core/Formatters/TimestampLogFormatter.swift
new file mode 100644
index 0000000..0e3c227
--- /dev/null
+++ b/Sources/Log/Classes/Core/Formatters/TimestampLogFormatter.swift
@@ -0,0 +1,37 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A log formatter that adds a timestamp to log messages.
+open class TimestampLogFormatter: ILogFormatter {
+ // MARK: Properties
+
+ /// The date formatter.
+ private lazy var dateFormatter: DateFormatter = {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = dateFormat
+ return dateFormatter
+ }()
+
+ /// The date format string.
+ private let dateFormat: String
+
+ // MARK: Initialization
+
+ /// Creates a new `PrefixFormatter` instance with the specified data formmater.
+ ///
+ /// - Parameter dateFormat: The date format string.
+ public init(dateFormat: String) {
+ self.dateFormat = dateFormat
+ }
+
+ // MARK: ILogFormatter
+
+ public func format(message: String, with _: LogLevel) -> String {
+ let timestamp = dateFormatter.string(from: Date())
+ return [timestamp, message].joined(separator: " ")
+ }
+}
diff --git a/Sources/Log/Classes/Core/Logger/ILogger.swift b/Sources/Log/Classes/Core/Logger/ILogger.swift
new file mode 100644
index 0000000..ac3053b
--- /dev/null
+++ b/Sources/Log/Classes/Core/Logger/ILogger.swift
@@ -0,0 +1,29 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A type that provides logging functionality.
+public protocol ILogger {
+ /// Dispatches the given message using the logger if the debug log level is set.
+ ///
+ /// - Parameter message: An autoclosure returning the message to log.
+ func debug(message: @autoclosure () -> String)
+
+ /// Dispatches the given message using the logger if the info log level is set.
+ ///
+ /// - Parameter message: An autoclosure returning the message to log.
+ func info(message: @autoclosure () -> String)
+
+ /// Dispatches the given message using the logger if the fault log level is set.
+ ///
+ /// - Parameter message: An autoclosure returning the message to log.
+ func fault(message: @autoclosure () -> String)
+
+ /// Dispatches the given message using the logger if the error log level is set.
+ ///
+ /// - Parameter message: An autoclosure returning the message to log.
+ func error(message: @autoclosure () -> String)
+}
diff --git a/Sources/Log/Classes/Core/Logger/Logger.swift b/Sources/Log/Classes/Core/Logger/Logger.swift
new file mode 100644
index 0000000..8405788
--- /dev/null
+++ b/Sources/Log/Classes/Core/Logger/Logger.swift
@@ -0,0 +1,70 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+// MARK: - Logger
+
+/// A class responsible for logging functionality.
+open class Logger {
+ // MARK: Properties
+
+ /// The current log level for this logger.
+ let logLevel: LogLevel
+ /// An array of printer strategies to handle the log output.
+ let printers: [IPrinterStrategy]
+
+ // MARK: Initialization
+
+ /// Initializes a new Logger instance.
+ ///
+ /// - Parameters:
+ /// - printers: An array of printer strategies.
+ /// - logLevel: The initial log level.
+ public init(
+ printers: [IPrinterStrategy],
+ logLevel: LogLevel
+ ) {
+ self.printers = printers
+ self.logLevel = logLevel
+ }
+
+ // MARK: Private
+
+ /// Passes the message to each receiver's printer.
+ ///
+ /// - Parameters:
+ /// - message: The message to dispatch.
+ /// - logLevel: The message's level.
+ 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 reciever.
+ ///
+ /// - Parameter logLevel: The log level to check.
+ private func isLoggerEnabled(for logLevel: LogLevel) -> Bool {
+ self.logLevel.contains(logLevel)
+ }
+}
+
+// MARK: ILogger
+
+extension Logger: ILogger {
+ public func debug(message: @autoclosure () -> String) {
+ log(message(), logLevel: .debug)
+ }
+
+ public func info(message: @autoclosure () -> String) {
+ log(message(), logLevel: .info)
+ }
+
+ public func fault(message: @autoclosure () -> String) {
+ log(message(), logLevel: .fault)
+ }
+
+ public func error(message: @autoclosure () -> String) {
+ log(message(), logLevel: .error)
+ }
+}
diff --git a/Sources/Log/Classes/Core/Logger/Strategies/IPrinterStrategy.swift b/Sources/Log/Classes/Core/Logger/Strategies/IPrinterStrategy.swift
new file mode 100644
index 0000000..d790438
--- /dev/null
+++ b/Sources/Log/Classes/Core/Logger/Strategies/IPrinterStrategy.swift
@@ -0,0 +1,19 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A protocol that defines the behavior of a logging printer strategy
+public protocol IPrinterStrategy {
+ /// An array of log formatters to customize log message output.
+ var formatters: [ILogFormatter] { get }
+
+ /// Logs a message with a specified log level.
+ ///
+ /// - Parameters:
+ /// - message: A `String` value that contains the message to dispatch.
+ /// - logLevel: A `LogLevel` value that contains the logging level.
+ func log(_ message: String, logLevel: LogLevel)
+}
diff --git a/Sources/Log/Classes/Core/Logger/Strategies/IStyleLogStrategy.swift b/Sources/Log/Classes/Core/Logger/Strategies/IStyleLogStrategy.swift
new file mode 100644
index 0000000..cb2fd16
--- /dev/null
+++ b/Sources/Log/Classes/Core/Logger/Strategies/IStyleLogStrategy.swift
@@ -0,0 +1,29 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+// MARK: - IStyleLogStrategy
+
+/// Specifies the format to be used in the log message
+protocol IStyleLogStrategy: IPrinterStrategy {
+ /// An array of log formatters to customize log message output.
+ var formatters: [ILogFormatter] { get }
+}
+
+extension IStyleLogStrategy {
+ /// Format the message using formatting rules.
+ ///
+ /// - Parameters:
+ /// - message: A `String` value that contains the message.
+ /// - logLevel: A `LogLevel` value that contains the logging level.
+ ///
+ /// - Returns: Formatted message.
+ func formatMessage(_ message: String, logLevel: LogLevel) -> String {
+ var message = message
+ formatters.forEach { message = $0.format(message: message, with: logLevel) }
+ return message
+ }
+}
diff --git a/Sources/Log/Classes/Core/Printers/ConsolePrinter.swift b/Sources/Log/Classes/Core/Printers/ConsolePrinter.swift
new file mode 100644
index 0000000..103e162
--- /dev/null
+++ b/Sources/Log/Classes/Core/Printers/ConsolePrinter.swift
@@ -0,0 +1,40 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+// MARK: - ConsolePrinter
+
+/// A class for logging messages to the console output.
+public final class ConsolePrinter {
+ // MARK: Properties
+
+ /// An array of log formatters used to customize log message output.
+ public let formatters: [ILogFormatter]
+
+ /// The console writer.
+ public let consoleWriter: IConsoleWriter
+
+ // MARK: Initialization
+
+ /// Creates a new `ConsolePrinter` instance.
+ ///
+ /// - Parameters:
+ /// - formatters: An array of log formatters for customizing log messages.
+ /// - consoleWriter: The console writer.
+ public init(formatters: [ILogFormatter], consoleWriter: IConsoleWriter = ConsoleWriter()) {
+ self.formatters = formatters
+ self.consoleWriter = consoleWriter
+ }
+}
+
+// MARK: IStyleLogStrategy
+
+extension ConsolePrinter: IStyleLogStrategy {
+ public func log(_ message: String, logLevel: LogLevel) {
+ let message = formatMessage(message, logLevel: logLevel)
+ consoleWriter.print(message)
+ }
+}
diff --git a/Sources/Log/Classes/Core/Printers/Interfaces/IPrinter.swift b/Sources/Log/Classes/Core/Printers/Interfaces/IPrinter.swift
new file mode 100644
index 0000000..7274b70
--- /dev/null
+++ b/Sources/Log/Classes/Core/Printers/Interfaces/IPrinter.swift
@@ -0,0 +1,16 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A protocol that defines the behavior for logging messages.
+public protocol IPrinter {
+ /// Logs a message with a specified log level.
+ ///
+ /// - Parameters:
+ /// - message: The message to log.
+ /// - logLevel: The log level indicating the importance of the log message.
+ func log(_ message: String, logLevel: LogLevel)
+}
diff --git a/Sources/Log/Classes/Core/Printers/OSPrinter.swift b/Sources/Log/Classes/Core/Printers/OSPrinter.swift
new file mode 100644
index 0000000..1964c56
--- /dev/null
+++ b/Sources/Log/Classes/Core/Printers/OSPrinter.swift
@@ -0,0 +1,76 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+import OSLog
+
+// MARK: - OSPrinter
+
+/// A class for logging messages using the OSLog system.
+public final class OSPrinter {
+ // MARK: Properties
+
+ /// An array of log formatters used to customize log message output.
+ public let formatters: [ILogFormatter]
+ /// The optional subsystem for categorizing log messages.
+ public let subsystem: String
+ /// The optional category for categorizing log messages.
+ public let category: String
+ /// The os writer.
+ public let osWriter: IOSWriter
+
+ /// An internal lazy property for initializing the OSLog instance.
+ private lazy var osLog: OSLog = .init(subsystem: subsystem, category: category)
+
+ // MARK: Initialization
+
+ /// Creates a new `OSPrinter` instance.
+ ///
+ /// - Parameters:
+ /// - subsystem: An optional subsystem for categorizing log messages.
+ /// - category: An optional category for categorizing log messages.
+ /// - formatters: An array of log formatters for customizing log messages.
+ /// - osWriter: An os writer.
+ public init(
+ subsystem: String = "os_printer",
+ category: String = "",
+ formatters: [ILogFormatter],
+ osWriter: IOSWriter = OSWriter()
+ ) {
+ self.subsystem = subsystem
+ self.category = category
+ self.formatters = formatters
+ self.osWriter = osWriter
+ }
+}
+
+// MARK: IStyleLogStrategy
+
+extension OSPrinter: IStyleLogStrategy {
+ public func log(_ message: String, logLevel: LogLevel) {
+ let message = formatMessage(message, logLevel: logLevel)
+ let type = sysLogPriority(logLevel)
+ osWriter.log("%s", log: osLog, type: type, message)
+ }
+}
+
+// MARK: - Extension
+
+extension OSPrinter {
+ private func sysLogPriority(_ logLevel: LogLevel) -> OSLogType {
+ switch logLevel {
+ case .debug:
+ return .debug
+ case .info:
+ return .info
+ case .fault:
+ return .fault
+ case .error:
+ return .error
+ default:
+ return .default
+ }
+ }
+}
diff --git a/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/ConsoleWriter.swift b/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/ConsoleWriter.swift
new file mode 100644
index 0000000..1549580
--- /dev/null
+++ b/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/ConsoleWriter.swift
@@ -0,0 +1,22 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+// MARK: - ConsoleWriter
+
+/// A class that conforms to the IConsoleWriter protocol and writes messages to the console output.
+public final class ConsoleWriter: IConsoleWriter {
+ // MARK: Initialization
+
+ /// Initializes a new ConsoleWriter instance.
+ public init() {}
+
+ // MARK: IConsoleWriter
+
+ public func print(_ message: String) {
+ Swift.print(message)
+ }
+}
diff --git a/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/IConsoleWriter.swift b/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/IConsoleWriter.swift
new file mode 100644
index 0000000..09d5a93
--- /dev/null
+++ b/Sources/Log/Classes/Helpers/Writers/ConsoleWriter/IConsoleWriter.swift
@@ -0,0 +1,14 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+/// A protocol for writing messages to the console.
+public protocol IConsoleWriter {
+ /// Prints a message to the console output.
+ ///
+ /// - Parameter message: The message to be printed as a String.
+ func print(_ message: String)
+}
diff --git a/Sources/Log/Classes/Helpers/Writers/OSWriter/IOSWriter.swift b/Sources/Log/Classes/Helpers/Writers/OSWriter/IOSWriter.swift
new file mode 100644
index 0000000..ec2fcab
--- /dev/null
+++ b/Sources/Log/Classes/Helpers/Writers/OSWriter/IOSWriter.swift
@@ -0,0 +1,19 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+import OSLog
+
+/// A protocol for writing log messages to the Apple OSLog system.
+public protocol IOSWriter {
+ /// Writes a log message to the specified OSLog.
+ ///
+ /// - Parameters:
+ /// - message: A StaticString containing the log message format.
+ /// - log: An OSLog object representing the log subsystem and category.
+ /// - type: An OSLogType indicating the log message type.
+ /// - args: A variadic list of CVarArg values to fill in the message format.
+ func log(_ message: StaticString, log: OSLog, type: OSLogType, _ args: CVarArg...)
+}
diff --git a/Sources/Log/Classes/Helpers/Writers/OSWriter/OSWriter.swift b/Sources/Log/Classes/Helpers/Writers/OSWriter/OSWriter.swift
new file mode 100644
index 0000000..a4ad3ad
--- /dev/null
+++ b/Sources/Log/Classes/Helpers/Writers/OSWriter/OSWriter.swift
@@ -0,0 +1,23 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+import OSLog
+
+// MARK: - OSWriter
+
+/// A class that conforms to the IOSWriter protocol and writes log messages to the Apple OSLog system.
+public final class OSWriter: IOSWriter {
+ // MARK: Initialization
+
+ /// Creates a new `OSWriter` instance.
+ public init() {}
+
+ // MARK: IOSWriter
+
+ public func log(_ message: StaticString, log: OSLog, type: OSLogType, _ args: CVarArg...) {
+ os_log(message, log: log, type: type, args)
+ }
+}
diff --git a/Sources/Log/Classes/Models/LogLevel.swift b/Sources/Log/Classes/Models/LogLevel.swift
new file mode 100644
index 0000000..5a68b78
--- /dev/null
+++ b/Sources/Log/Classes/Models/LogLevel.swift
@@ -0,0 +1,49 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+
+// MARK: - LogLevel
+
+/// A set that includes all log levels.
+public struct LogLevel: OptionSet {
+ // MARK: Initialization
+
+ /// Creates a new log level.
+ ///
+ /// - Parameter rawValue: The value that indicates a log level.
+ public init(rawValue: RawValue) {
+ self.rawValue = rawValue
+ }
+
+ // MARK: Types
+
+ public typealias RawValue = UInt
+
+ /// Creates a new default `.off` instance with a bitmask of `1 << 0`.
+ public static let off = LogLevel(rawValue: offBitmask)
+ /// Creates a new default `.debug` instance with a bitmask of `1 << 1`.
+ public static let debug = LogLevel(rawValue: debugBitmask)
+ /// Creates a new default `.info` instance with a bitmask of `1 << 2`.
+ public static let info = LogLevel(rawValue: infoBitmask)
+ /// Creates a new default `.error` instance with a bitmask of `1 << 3`.
+ public static let error = LogLevel(rawValue: errorBitmask)
+ /// Creates a new default `.fault` instance with a bitmask of `1 << 4`.
+ public static let fault = LogLevel(rawValue: faultBitmask)
+ /// Creates a new default `.error` instance.
+ public static let all = LogLevel(rawValue: allBitmask)
+
+ /// Returns the raw bitmask value of the LogLevel and satisfies the `RawRepresentable` protocol.
+ public let rawValue: RawValue
+
+ // MARK: Private
+
+ private static let offBitmask: RawValue = 1 << 0
+ private static let debugBitmask: RawValue = 1 << 1
+ private static let infoBitmask: RawValue = 1 << 2
+ private static let errorBitmask: RawValue = 1 << 3
+ private static let faultBitmask: RawValue = 1 << 4
+ private static let allBitmask: RawValue = 0b1111_1111_1111_1111_1111_1111_1111_1111
+}
diff --git a/Tests/LogTests/Mocks/ConsoleWriterMock.swift b/Tests/LogTests/Mocks/ConsoleWriterMock.swift
new file mode 100644
index 0000000..7b0cec8
--- /dev/null
+++ b/Tests/LogTests/Mocks/ConsoleWriterMock.swift
@@ -0,0 +1,21 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+import Log
+
+final class ConsoleWriterMock: IConsoleWriter {
+ var invokedPrint = false
+ var invokedPrintCount = 0
+ var invokedPrintParameters: (message: String, Void)?
+ var invokedPrintParametersList = [(message: String, Void)]()
+
+ func print(_ message: String) {
+ invokedPrint = true
+ invokedPrintCount += 1
+ invokedPrintParameters = (message, ())
+ invokedPrintParametersList.append((message, ()))
+ }
+}
diff --git a/Tests/LogTests/Mocks/LogFormatterMock.swift b/Tests/LogTests/Mocks/LogFormatterMock.swift
new file mode 100644
index 0000000..9fadfec
--- /dev/null
+++ b/Tests/LogTests/Mocks/LogFormatterMock.swift
@@ -0,0 +1,23 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Foundation
+import Log
+
+final class LogFormatterMock: ILogFormatter {
+ var invokedFormat = false
+ var invokedFormatCount = 0
+ var invokedFormatParameters: (message: String, logLevel: LogLevel)?
+ var invokedFormatParametersList = [(message: String, logLevel: LogLevel)]()
+ var stubbedFormatResult: String! = ""
+
+ func format(message: String, with logLevel: LogLevel) -> String {
+ invokedFormat = true
+ invokedFormatCount += 1
+ invokedFormatParameters = (message, logLevel)
+ invokedFormatParametersList.append((message, logLevel))
+ return stubbedFormatResult
+ }
+}
diff --git a/Tests/LogTests/Mocks/OSWriterMock.swift b/Tests/LogTests/Mocks/OSWriterMock.swift
new file mode 100644
index 0000000..165dc09
--- /dev/null
+++ b/Tests/LogTests/Mocks/OSWriterMock.swift
@@ -0,0 +1,21 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import OSLog
+
+final class OSWriterMock: IOSWriter {
+ var invokedLog = false
+ var invokedLogCount = 0
+ var invokedLogParameters: (message: StaticString, log: OSLog, type: OSLogType, args: CVarArg)?
+ var invokedLogParametersList = [(message: StaticString, log: OSLog, type: OSLogType, args: CVarArg)]()
+
+ func log(_ message: StaticString, log: OSLog, type: OSLogType, _ args: CVarArg...) {
+ invokedLog = true
+ invokedLogCount += 1
+ invokedLogParameters = (message, log, type, args)
+ invokedLogParametersList.append((message, log, type, args))
+ }
+}
diff --git a/Tests/LogTests/Mocks/PrinterStrategyMock.swift b/Tests/LogTests/Mocks/PrinterStrategyMock.swift
new file mode 100644
index 0000000..cfb7961
--- /dev/null
+++ b/Tests/LogTests/Mocks/PrinterStrategyMock.swift
@@ -0,0 +1,30 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+
+final class PrinterStrategyMock: IPrinterStrategy {
+ var invokedFormattersGetter = false
+ var invokedFormattersGetterCount = 0
+ var stubbedFormatters: [ILogFormatter]! = []
+
+ var formatters: [ILogFormatter] {
+ invokedFormattersGetter = true
+ invokedFormattersGetterCount += 1
+ return stubbedFormatters
+ }
+
+ var invokedLog = false
+ var invokedLogCount = 0
+ var invokedLogParameters: (message: String, logLevel: LogLevel)?
+ var invokedLogParametersList = [(message: String, logLevel: LogLevel)]()
+
+ func log(_ message: String, logLevel: LogLevel) {
+ invokedLog = true
+ invokedLogCount += 1
+ invokedLogParameters = (message, logLevel)
+ invokedLogParametersList.append((message, logLevel))
+ }
+}
diff --git a/Tests/LogTests/UnitTests/ConsolePrinterTests.swift b/Tests/LogTests/UnitTests/ConsolePrinterTests.swift
new file mode 100644
index 0000000..4478dfd
--- /dev/null
+++ b/Tests/LogTests/UnitTests/ConsolePrinterTests.swift
@@ -0,0 +1,53 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import XCTest
+
+// MARK: - ConsolePrinterTests
+
+final class ConsolePrinterTests: XCTestCase {
+ // MARK: Properties
+
+ private var formatterMock: LogFormatterMock!
+ private var consoleWriterMock: ConsoleWriterMock!
+
+ private var sut: ConsolePrinter!
+
+ // MARK: XCTestCase
+
+ override func setUp() {
+ super.setUp()
+ formatterMock = LogFormatterMock()
+ consoleWriterMock = ConsoleWriterMock()
+ sut = ConsolePrinter(formatters: [formatterMock], consoleWriter: consoleWriterMock)
+ }
+
+ override func tearDown() {
+ formatterMock = nil
+ consoleWriterMock = nil
+ sut = nil
+ super.tearDown()
+ }
+
+ // MARK: Tests
+
+ func test_thatConsolePrinterLogsMessage() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .all)
+
+ // then
+ XCTAssertEqual(consoleWriterMock.invokedPrintParameters?.message, .message)
+ }
+}
+
+// MARK: - Constants
+
+private extension String {
+ static let message = "message"
+}
diff --git a/Tests/LogTests/UnitTests/LogTests.swift b/Tests/LogTests/UnitTests/LogTests.swift
new file mode 100644
index 0000000..4284ac4
--- /dev/null
+++ b/Tests/LogTests/UnitTests/LogTests.swift
@@ -0,0 +1,136 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import XCTest
+
+// MARK: - LogTests
+
+final class LogTests: XCTestCase {
+ // MARK: Properties
+
+ private var printerMock: PrinterStrategyMock!
+
+ // MARK: XCTestCase
+
+ override func setUp() {
+ super.setUp()
+ printerMock = PrinterStrategyMock()
+ }
+
+ override func tearDown() {
+ printerMock = nil
+ super.tearDown()
+ }
+
+ // MARK: Tests
+
+ func test_thatLoggerLogsInfoMessage() {
+ // given
+ let sut = prepareSut()
+
+ // when
+ sut.info(message: .message)
+
+ // then
+ XCTAssertEqual(printerMock.invokedLogParameters?.message, .message)
+ XCTAssertEqual(printerMock.invokedLogParameters?.logLevel, .info)
+ }
+
+ func test_thatLoggerLogsDebugMessage() {
+ // given
+ let sut = prepareSut()
+
+ // when
+ sut.debug(message: .message)
+
+ // then
+ XCTAssertEqual(printerMock.invokedLogParameters?.message, .message)
+ XCTAssertEqual(printerMock.invokedLogParameters?.logLevel, .debug)
+ }
+
+ func test_thatLoggerLogsErrorMessage() {
+ // given
+ let sut = prepareSut()
+
+ // when
+ sut.error(message: .message)
+
+ // then
+ XCTAssertEqual(printerMock.invokedLogParameters?.message, .message)
+ XCTAssertEqual(printerMock.invokedLogParameters?.logLevel, .error)
+ }
+
+ func test_thatLoggerLogsFaultMessage() {
+ // given
+ let sut = prepareSut()
+
+ // when
+ sut.fault(message: .message)
+
+ // then
+ XCTAssertEqual(printerMock.invokedLogParameters?.message, .message)
+ XCTAssertEqual(printerMock.invokedLogParameters?.logLevel, .fault)
+ }
+
+ func test_thatLoggerDoesNotLogAnything_whenLogLevelIsOff() {
+ // given
+ let sut = prepareSut(logLevel: .off)
+
+ // when
+ sut.info(message: .message)
+
+ // then
+ XCTAssertNil(printerMock.invokedLogParameters?.message)
+ }
+
+ func test_thatLoggerDoesNotLogDebugMessage_whenLogLevelIsInfo() {
+ // given
+ let sut = prepareSut(logLevel: .info)
+
+ // when
+ sut.debug(message: .message)
+
+ // then
+ XCTAssertNil(printerMock.invokedLogParameters?.message)
+ }
+
+ func test_thatLoggerDoesNotLogInfoMessage_whenLogLevelIsError() {
+ // given
+ let sut = prepareSut(logLevel: .error)
+
+ // when
+ sut.info(message: .message)
+
+ // then
+ XCTAssertNil(printerMock.invokedLogParameters?.message)
+ }
+
+ func test_thatLoggerDoesNotLogErrorMessage_whenLogLevelIsFault() {
+ // given
+ let sut = prepareSut(logLevel: .fault)
+
+ // when
+ sut.error(message: .message)
+
+ // then
+ XCTAssertNil(printerMock.invokedLogParameters?.message)
+ }
+
+ // MARK: Private
+
+ private func prepareSut(logLevel: LogLevel = .all) -> Logger {
+ Logger(
+ printers: [printerMock],
+ logLevel: logLevel
+ )
+ }
+}
+
+// MARK: - Constants
+
+private extension String {
+ static let message = "message"
+}
diff --git a/Tests/LogTests/UnitTests/OSPrinterTests.swift b/Tests/LogTests/UnitTests/OSPrinterTests.swift
new file mode 100644
index 0000000..f4175a5
--- /dev/null
+++ b/Tests/LogTests/UnitTests/OSPrinterTests.swift
@@ -0,0 +1,104 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import XCTest
+
+// MARK: - OSPrinterTests
+
+final class OSPrinterTests: XCTestCase {
+ // MARK: Properties
+
+ private var formatterMock: LogFormatterMock!
+ private var osWriterMock: OSWriterMock!
+
+ private var sut: OSPrinter!
+
+ // MARK: XCTestCase
+
+ override func setUp() {
+ super.setUp()
+ formatterMock = LogFormatterMock()
+ osWriterMock = OSWriterMock()
+ sut = OSPrinter(
+ subsystem: .subsystem,
+ category: .category,
+ formatters: [formatterMock],
+ osWriter: osWriterMock
+ )
+ }
+
+ override func tearDown() {
+ formatterMock = nil
+ osWriterMock = nil
+ sut = nil
+ super.tearDown()
+ }
+
+ // MARK: Tests
+
+ func test_thatConsolePrinterLogsMessage() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .all)
+
+ // then
+ XCTAssertEqual((osWriterMock.invokedLogParameters?.args as? [String])?.first, .message)
+ }
+
+ func test_thatConsolePrinterLogsMessage_whenLogLevelIsDebug() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .debug)
+
+ // then
+ XCTAssertEqual((osWriterMock.invokedLogParameters?.args as? [String])?.first, .message)
+ }
+
+ func test_thatConsolePrinterLogsMessage_whenLogLevelIsInfo() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .info)
+
+ // then
+ XCTAssertEqual((osWriterMock.invokedLogParameters?.args as? [String])?.first, .message)
+ }
+
+ func test_thatConsolePrinterLogsMessage_whenLogLevelIsError() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .error)
+
+ // then
+ XCTAssertEqual((osWriterMock.invokedLogParameters?.args as? [String])?.first, .message)
+ }
+
+ func test_thatConsolePrinterLogsMessage_whenLogLevelIsFault() {
+ // given
+ formatterMock.stubbedFormatResult = .message
+
+ // when
+ sut.log(.message, logLevel: .fault)
+
+ // then
+ XCTAssertEqual((osWriterMock.invokedLogParameters?.args as? [String])?.first, .message)
+ }
+}
+
+// MARK: - Constants
+
+private extension String {
+ static let subsystem = "subsystem"
+ static let category = "category"
+ static let message = "message"
+}
diff --git a/Tests/LogTests/UnitTests/PrefixFormatterTests.swift b/Tests/LogTests/UnitTests/PrefixFormatterTests.swift
new file mode 100644
index 0000000..c2ffd33
--- /dev/null
+++ b/Tests/LogTests/UnitTests/PrefixFormatterTests.swift
@@ -0,0 +1,76 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import XCTest
+
+// MARK: - PrefixFormatterTests
+
+final class PrefixFormatterTests: XCTestCase {
+ // MARK: Properties
+
+ private var sut: PrefixLogFormatter!
+
+ // MARK: XCTestCase
+
+ override func setUp() {
+ super.setUp()
+ sut = PrefixLogFormatter(name: .name)
+ }
+
+ override func tearDown() {
+ sut = nil
+ super.tearDown()
+ }
+
+ // MARK: Tests
+
+ func test_thatPrefixFormatterFormatsMessage_whenLogLevelIsFault() {
+ // when
+ let message = sut.format(message: .message, with: .fault)
+
+ // then
+ XCTAssertEqual(message, "🚨🚨🚨 [name] => message")
+ }
+
+ func test_thatPrefixFormatterFormatsMessage_whenLogLevelIsError() {
+ // when
+ let message = sut.format(message: .message, with: .error)
+
+ // then
+ XCTAssertEqual(message, "💣💥💣💥 [name] => message")
+ }
+
+ func test_thatPrefixFormatterFormatsMessage_whenLogLevelIsDebug() {
+ // when
+ let message = sut.format(message: .message, with: .debug)
+
+ // then
+ XCTAssertEqual(message, "[name] => message")
+ }
+
+ func test_thatPrefixFormatterFormatsMessage_whenLogLevelIsInfo() {
+ // when
+ let message = sut.format(message: .message, with: .info)
+
+ // then
+ XCTAssertEqual(message, "[name] => message")
+ }
+
+ func test_thatPrefixFormatterFormatsMessage_whenLogLevelIsAll() {
+ // when
+ let message = sut.format(message: .message, with: .all)
+
+ // then
+ XCTAssertEqual(message, "[name] => message")
+ }
+}
+
+// MARK: - Constants
+
+private extension String {
+ static let message = "message"
+ static let name = "name"
+}
diff --git a/Tests/LogTests/UnitTests/TimestampLogFormatterTests.swift b/Tests/LogTests/UnitTests/TimestampLogFormatterTests.swift
new file mode 100644
index 0000000..ff4deba
--- /dev/null
+++ b/Tests/LogTests/UnitTests/TimestampLogFormatterTests.swift
@@ -0,0 +1,49 @@
+//
+// log
+// Copyright © 2023 Space Code. All rights reserved.
+//
+
+import Log
+import XCTest
+
+// MARK: - TimestampLogFormatterTests
+
+final class TimestampLogFormatterTests: XCTestCase {
+ // MARK: Properties
+
+ private var sut: TimestampLogFormatter!
+
+ // MARK: XCTestCase
+
+ override func setUp() {
+ super.setUp()
+ sut = TimestampLogFormatter(dateFormat: .dateFormat)
+ }
+
+ override func tearDown() {
+ sut = nil
+ super.tearDown()
+ }
+
+ // MARK: Tests
+
+ func test_thatTimestampFormaterFormatsLogMessage() {
+ // given
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = .dateFormat
+ let date = dateFormatter.string(from: Date())
+
+ // when
+ let message = sut.format(message: .message, with: .all)
+
+ // then
+ XCTAssertEqual(message, "\(date) \(String.message)")
+ }
+}
+
+// MARK: - Constants
+
+private extension String {
+ static let message = "message"
+ static let dateFormat = "dd.MM.yyyy"
+}
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..b415604
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,48 @@
+codecov:
+ # Require CI to pass to show coverage, default yes
+ require_ci_to_pass: yes
+ notify:
+ # Codecov should wait for all CI statuses to complete, default yes
+ wait_for_ci: yes
+
+coverage:
+ # Coverage precision range 0-5, default 2
+ precision: 2
+
+ # Direction to round the coverage value - up, down, nearest, default down
+ round: nearest
+
+ # Value range for red...green, default 70...100
+ range: "70...90"
+
+ status:
+ # Overall project coverage, compare against pull request base
+ project:
+ default:
+ # The required coverage value
+ target: 50%
+
+ # The leniency in hitting the target. Allow coverage to drop by X%
+ threshold: 5%
+
+ # Only measure lines adjusted in the pull request or single commit, if the commit in not in the pr
+ patch:
+ default:
+ # The required coverage value
+ target: 85%
+
+ # Allow coverage to drop by X%
+ threshold: 5%
+ changes: no
+
+comment:
+ # Pull request Codecov comment format.
+ # diff: coverage diff of the pull request
+ # files: a list of files impacted by the pull request (coverage changes, file is new or removed)
+ layout: "diff, files"
+
+ # Update Codecov comment, if exists. Otherwise post new
+ behavior: default
+
+ # If true, only post the Codecov comment if coverage changes
+ require_changes: false
\ No newline at end of file
diff --git a/hooks/pre-commit b/hooks/pre-commit
new file mode 100755
index 0000000..956fdcb
--- /dev/null
+++ b/hooks/pre-commit
@@ -0,0 +1,38 @@
+#!/bin/bash
+git diff --diff-filter=d --staged --name-only | grep -e '\.swift$' | while read line; do
+ if [[ $line == *"/Generated"* ]]; then
+ echo "IGNORING GENERATED FILE: " "$line";
+ else
+ mint run swiftformat swiftformat "${line}";
+ git add "$line";
+ fi
+done
+
+LINT=$(which mint)
+if [[ -e "${LINT}" ]]; then
+ # Export files in SCRIPT_INPUT_FILE_$count to lint against later
+ count=0
+ while IFS= read -r file_path; do
+ export SCRIPT_INPUT_FILE_$count="$file_path"
+ count=$((count + 1))
+ done < <(git diff --name-only --cached --diff-filter=d | grep ".swift$")
+ export SCRIPT_INPUT_FILE_COUNT=$count
+
+ if [ "$count" -eq 0 ]; then
+ echo "No files to lint!"
+ exit 0
+ fi
+
+ echo "Found $count lintable files! Linting now.."
+ mint run swiftlint --use-script-input-files --strict --config .swiftlint.yml
+ RESULT=$? # swiftline exit value is number of errors
+
+ if [ $RESULT -eq 0 ]; then
+ echo "🎉 Well done. No violation."
+ fi
+ exit $RESULT
+else
+ echo "⚠️ WARNING: SwiftLint not found"
+ echo "⚠️ You might want to edit .git/hooks/pre-commit to locate your swiftlint"
+ exit 0
+fi
\ No newline at end of file