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

+ +

+License +Platform +5.7 +CI + +CodeCov +

+ +## 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