diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3213ca9..d3d3db0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build -on: [push, pull_request] +on: [push] jobs: build: @@ -14,3 +14,13 @@ jobs: - name: Build run: swift build -v + + build-android: + name: Build Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Test Swift Package on Android" + uses: skiptools/swift-android-action@v2 + with: + run-tests: false diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml index f004099..468cd9a 100644 --- a/.github/workflows/swiftlint.yml +++ b/.github/workflows/swiftlint.yml @@ -1,7 +1,7 @@ name: Swiftlint -on: [push, pull_request] +on: [push] jobs: swiftlint: @@ -14,4 +14,4 @@ jobs: - name: GitHub Action for SwiftLint with --strict uses: norio-nomura/action-swiftlint@3.1.0 with: - args: --strict \ No newline at end of file + args: --strict diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9092dea..67b62e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: Test -on: [push, pull_request] +on: [push] jobs: test: @@ -13,4 +13,12 @@ jobs: uses: actions/checkout@v4 - name: Test - run: swift test -v --enable-test-discovery --enable-code-coverage --sanitize=thread \ No newline at end of file + run: swift test -v --enable-test-discovery --enable-code-coverage --sanitize=thread + + test-android: + name: Test Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Test Swift Package on Android" + uses: skiptools/swift-android-action@v2 diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata index 706eede..919434a 100644 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..6e0eda8 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "86d3c80e950364bab61f36d013b45300638a34fdb39bd15bb3b25d4c933a82e4", + "pins" : [ + { + "identity" : "swift-android-native", + "kind" : "remoteSourceControl", + "location" : "https://github.com/skiptools/swift-android-native.git", + "state" : { + "revision" : "c0d6a8422a04bea5f953e8f09efb5344515545ab", + "version" : "1.1.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index 2bfda60..d2de48e 100644 --- a/Package.swift +++ b/Package.swift @@ -6,6 +6,7 @@ import PackageDescription //swiftlint:disable all let package = Package( name: "LogKit", + platforms: [.macOS(.v11), .iOS(.v14), .macCatalyst(.v14), .tvOS(.v14), .watchOS(.v7), .visionOS(.v1)], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( @@ -19,14 +20,16 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + .package(url: "https://github.com/skiptools/swift-android-native.git", from: "1.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "LogKit", - dependencies: []), + dependencies: [ + .product(name: "AndroidLogging", package: "swift-android-native", condition: .when(platforms: [.android])) + ]), .testTarget( name: "LogKitTests", dependencies: ["LogKit"]) diff --git a/README.md b/README.md index 6fbcad3..93ac180 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # LogKit -![Build](https://github.com/marekpridal/LogKit/actions/workflows/build.yml/badge.svg) ![Test](https://github.com/marekpridal/LogKit/actions/workflows/test.yml/badge.svg) ![Publish](https://github.com/marekpridal/LogKit/actions/workflows/Publish.yml/badge.svg) ![platforms](https://img.shields.io/badge/platform-iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20macOS-333333) [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) ![GitHub](https://img.shields.io/github/license/marekpridal/LogKit) ![GitHub All Releases](https://img.shields.io/github/downloads/marekpridal/LogKit/total) +![Build](https://github.com/marekpridal/LogKit/actions/workflows/build.yml/badge.svg) ![Test](https://github.com/marekpridal/LogKit/actions/workflows/test.yml/badge.svg) ![Publish](https://github.com/marekpridal/LogKit/actions/workflows/Publish.yml/badge.svg) ![platforms](https://img.shields.io/badge/platform-iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20macOS%20%7C%20visionOS%20%7C%20Android-333333) [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) ![GitHub](https://img.shields.io/github/license/marekpridal/LogKit) ![GitHub All Releases](https://img.shields.io/github/downloads/marekpridal/LogKit/total) LogKit is logging framework to simplify work with `os.log` API provided by Apple. -## Requirements -- iOS 10.0+ -- watchOS 3.0+ -- macOS 10.12+ -- tvOS 10.0+ +## Support +- iOS 14.0+ +- watchOS 7.0+ +- macOS 11+ +- tvOS 14.0+ +- visionOS 1.0+ +- Android ## Installation @@ -32,7 +34,7 @@ import PackageDescription let package = Package( name: "LogKitExample", dependencies: [ - .package(url: "https://github.com/marekpridal/LogKit", from: "2.0.0") + .package(url: "https://github.com/marekpridal/LogKit", from: "3.0.0") ], targets: [ .target(name: "LogKitExample", dependencies: ["LogKit"]) @@ -139,4 +141,4 @@ Log.payment(transactions: transactions) // [] // ---------------------------- -``` \ No newline at end of file +``` diff --git a/Sources/LogKit/LogKit.swift b/Sources/LogKit/LogKit.swift index aba683e..5d5bfc2 100644 --- a/Sources/LogKit/LogKit.swift +++ b/Sources/LogKit/LogKit.swift @@ -6,10 +6,15 @@ // import Foundation -import os.log +#if os(Android) || os(Windows) || os(Linux) +import AndroidLogging +import FoundationNetworking +#else +import OSLog import StoreKit +#endif -@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) +@available(OSX 11, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public enum Log { public enum Category: String, CaseIterable, Sendable { case `deinit` @@ -23,42 +28,46 @@ public enum Log { case dependencyInjection } +#if canImport(Darwin) nonisolated(unsafe) public static var subsystem = Bundle.main.bundleIdentifier! +#elseif os(Android) + nonisolated(unsafe) public static var subsystem = "com.logkit.android" +#endif nonisolated(unsafe) public static var enabledLogging: [Category] = Category.allCases - +#if canImport(Darwin) public static func `deinit`(of object: AnyObject) { guard enabledLogging.contains(.deinit) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.deinit.rawValue) - os_log("Deinit of %{PRIVATE}@", log: deinitLog, type: .info, object.debugDescription ?? "") + let logger = Logger(subsystem: subsystem, category: Category.deinit.rawValue) + logger.info("Deinit of \(object.debugDescription ?? "")") } - +#endif public static func function(_ function: String, in file: String) { guard enabledLogging.contains(.function) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.function.rawValue) - os_log("%{PRIVATE}@ %{PRIVATE}@", log: deinitLog, type: .debug, function, file) + let logger = Logger(subsystem: subsystem, category: Category.function.rawValue) + logger.debug("\(function) \(file)") } public static func `default`(_ string: String) { guard enabledLogging.contains(.default) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.default.rawValue) - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, string) + let logger = Logger(subsystem: subsystem, category: Category.default.rawValue) + logger.debug("\(string)") } public static func requestCalled(function: String) { guard enabledLogging.contains(.networking) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.networking.rawValue) - os_log("%{PRIVATE}@ already called", log: deinitLog, type: .debug, function) + let logger = Logger(subsystem: subsystem, category: Category.networking.rawValue) + logger.debug("\(function) already called") } public static func expiration(date: Date) { guard enabledLogging.contains(.expiration) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.expiration.rawValue) - os_log("[GMT] Valid until %{PRIVATE}@", log: deinitLog, type: .debug, date.debugDescription) + let logger = Logger(subsystem: subsystem, category: Category.expiration.rawValue) + logger.debug("[GMT] Valid until \(date.debugDescription)") } public static func request(_ request: URLRequest) { guard enabledLogging.contains(.networking) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.networking.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.networking.rawValue) var message = "\n---REQUEST------------------\n" message.append("URL -> \((request.url?.absoluteString ?? ""))\n") message.append("METHOD -> \(request.httpMethod ?? "")\n") @@ -71,12 +80,12 @@ public enum Log { message.append("BODY -> \(String(data: body, encoding: .utf8) ?? "")\n") } message.append("----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, message) + logger.debug("\(message)") } public static func response(_ response: URLResponse?, data: Data?) { guard enabledLogging.contains(.networking) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.networking.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.networking.rawValue) guard let response = response, let data = data else { return } var message = "\n---RESPONSE------------------\n" message.append("URL -> \(response.url?.absoluteString ?? "")\n") @@ -89,89 +98,84 @@ public enum Log { message.append("}\n") message.append("Response data -> \(String(data: data, encoding: .utf8) ?? "")\n") message.append("----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, message) + logger.debug("\(message)") } public static func function(_ function: String, text: String) { guard enabledLogging.contains(.function) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.function.rawValue) - os_log("%{PRIVATE}@ %{PRIVATE}@", log: deinitLog, type: .debug, function, text) + let logger = Logger(subsystem: subsystem, category: Category.function.rawValue) + logger.debug("\(function) \(text)") } public static func inAppPurchase(_ string: String) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, string) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + logger.debug("\(string)") } public static func error(_ error: Error) { guard enabledLogging.contains(.error) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.error.rawValue) - os_log("%{PRIVATE}@", log: deinitLog, type: .error, error.localizedDescription) + let logger = Logger(subsystem: subsystem, category: Category.error.rawValue) + logger.debug("\(error.localizedDescription)") } public static func database(_ string: String) { guard enabledLogging.contains(.database) else { return } - let log = OSLog(subsystem: subsystem, category: LogKit.Log.Category.database.rawValue) - os_log("%{PRIVATE}@", log: log, type: .debug, string) + let logger = Logger(subsystem: subsystem, category: Category.database.rawValue) + logger.debug("\(string)") } public static func dependencyInjection(_ string: String) { guard enabledLogging.contains(.dependencyInjection) else { return } - let log = OSLog(subsystem: subsystem, category: LogKit.Log.Category.dependencyInjection.rawValue) - os_log("%{PRIVATE}@", log: log, type: .debug, string) + let logger = Logger(subsystem: subsystem, category: Category.dependencyInjection.rawValue) + logger.debug("\(string)") } - - @available(watchOS 6.2, *) +#if canImport(Darwin) public static func products(request: SKProductsRequest) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) var requestMessage = "\n---REQUEST------------------\n" requestMessage.append("\(request)") requestMessage.append("\n----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, requestMessage) + logger.debug("\(requestMessage)") } - @available(watchOS 6.2, *) public static func products(response: SKProductsResponse) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) var responseMessage = "\n---RESPONSE------------------\n" responseMessage.append("Invalid product identifiers \(response.invalidProductIdentifiers)") responseMessage.append("\n----------------------------\n") responseMessage.append("Products \(response.products)") responseMessage.append("\n----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, responseMessage) + logger.debug("\(responseMessage)") } - @available(watchOS 6.2, *) public static func products(request: SKRequest) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) var requestMessage = "\n---REQUEST------------------\n" requestMessage.append("\(request)") requestMessage.append("\n----------------------------\n") - Log.default(requestMessage) - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, requestMessage) + logger.debug("\(requestMessage)") } - @available(watchOS 6.2, *) public static func paymentQueue(_ queue: SKPaymentQueue) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) var requestMessage = "\n---QUEUE------------------\n" requestMessage.append("\(queue)") requestMessage.append("\n----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, requestMessage) + logger.debug("\(requestMessage)") } - @available(watchOS 6.2, *) public static func payment(transactions: [SKPaymentTransaction]) { guard enabledLogging.contains(.inAppPurchase) else { return } - let deinitLog = OSLog(subsystem: subsystem, category: Category.inAppPurchase.rawValue) + let logger = Logger(subsystem: subsystem, category: Category.inAppPurchase.rawValue) var responseMessage = "\n---UPDATED TRANSACTIONS------------------\n" responseMessage.append("\(transactions)") responseMessage.append("\n----------------------------\n") - os_log("%{PRIVATE}@", log: deinitLog, type: .debug, responseMessage) + logger.debug("\(responseMessage)") } +#endif } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index c3e2227..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1 +0,0 @@ -fatalError("Running tests like this is unsupported. Run the tests again by using `swift test --enable-test-discovery`") diff --git a/Tests/LogKitTests/LogKitTests.swift b/Tests/LogKitTests/LogKitTests.swift index 89778e6..d520ca2 100644 --- a/Tests/LogKitTests/LogKitTests.swift +++ b/Tests/LogKitTests/LogKitTests.swift @@ -1,8 +1,11 @@ +#if !os(Android) && !os(Windows) && !os(Linux) import StoreKit +#else +import FoundationNetworking +#endif import XCTest @testable import LogKit -@available(OSX 10.12, *) final class LogKitTests: XCTestCase { private var log: LogKit.Log.Type { let log = Log.self @@ -21,11 +24,11 @@ final class LogKitTests: XCTestCase { private struct Foo: Encodable { let bar: String } - +#if !os(Android) && !os(Windows) && !os(Linux) func testLogDeinit() { log.deinit(of: self) } - +#endif func testLogFunctionIn() { log.function(#function, in: #file) log.function(#function, text: "text") @@ -44,9 +47,7 @@ final class LogKitTests: XCTestCase { } func testLogRequest() { - log.request(URLRequest(url: URL(string: "https://github.com/marekpridal/LogKit")!, - cachePolicy: .useProtocolCachePolicy, - timeoutInterval: 30)) + log.request(URLRequest(url: URL(string: "https://github.com/marekpridal/LogKit")!)) } func testLogResponse() { @@ -66,7 +67,7 @@ final class LogKitTests: XCTestCase { func testLogError() { log.error(NSError(domain: "logkit.tests", code: 0, userInfo: nil)) } - +#if !os(Android) && !os(Windows) && !os(Linux) func testLogSKRequest() { log.products(request: .init(productIdentifiers: ["logkit_product_identifier"])) } @@ -86,4 +87,5 @@ final class LogKitTests: XCTestCase { func testLogPayment() { log.payment(transactions: [.init()]) } +#endif } diff --git a/assets/xcframework-asset.png b/assets/xcframework-asset.png deleted file mode 100644 index b830337..0000000 Binary files a/assets/xcframework-asset.png and /dev/null differ