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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ jobs:
-Dsonar.objc.file.suffixes=-
-Dsonar.coverageReportPaths=sonarqube-generic-coverage.xml
-Dsonar.verbose=true
-Dsonar.sources=Split
-Dsonar.sources=Split,Sources
-Dsonar.exclusions=**/Tests/**,**/*.generated.swift
-Dsonar.coverage.exclusions=**/Tests/**,**/*.generated.swift
-Dsonar.qualitygate.wait=true
Expand Down Expand Up @@ -179,7 +179,7 @@ jobs:
-Dsonar.objc.file.suffixes=-
-Dsonar.coverageReportPaths=sonarqube-generic-coverage.xml
-Dsonar.verbose=true
-Dsonar.sources=Split
-Dsonar.sources=Split,Sources
-Dsonar.exclusions=**/Tests/**,**/*.generated.swift
-Dsonar.coverage.exclusions=**/Tests/**,**/*.generated.swift
-Dsonar.qualitygate.wait=true
Expand Down
21 changes: 18 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,32 @@ let package = Package(
name: "Split",
platforms: [.iOS(.v9), .macOS(.v10_11), .watchOS(.v7), .tvOS(.v9)],
products: [
.library(name: "Split", targets: ["Split"])
],
.library(name: "Split", targets: ["Split"]),

.library(name: "SplitCommons", targets: ["Logging"]),],
targets: [
.target(
name: "Split",
dependencies: ["Logging"],
path: "Split",
exclude: [
"Common/Yaml/LICENSE",
"Info.plist",
"Split.h"
]
)
),

.target(
name: "Logging",
dependencies: [],
path: "Sources/Logging",
exclude: ["Tests", "README.md"]
),
.testTarget(
name: "LoggingTests",
dependencies: ["Logging"],
path: "Sources/Logging/Tests"
),
// #INJECT_TARGET
]
)
36 changes: 36 additions & 0 deletions Sources/Logging/DateProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// DateProvider.swift
// Logging
//
// Created by Split SDK Team
//

import Foundation

/// Protocol for providing date/time functionality to the logging system
public protocol DateProvider {
/// Returns current time in milliseconds since epoch
func nowMillis() -> Int64

/// Returns a formatted timestamp string for logging
func nowLabel() -> String
}

/// Default implementation that uses Foundation's Date
public struct DefaultDateProvider: DateProvider {
public init() {}

private static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy HH:mm:ss.SSS"
return formatter
}()

public func nowMillis() -> Int64 {
return Int64(Date().timeIntervalSince1970 * 1000)
}

public func nowLabel() -> String {
return Self.formatter.string(from: Date())
}
}
34 changes: 34 additions & 0 deletions Sources/Logging/LogLevel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// LogLevel.swift
// Logging
//

import Foundation

/// Log level enumeration for the Logging module
public enum LogLevel: String {
case verbose = "VERBOSE"
case debug = "DEBUG"
case info = "INFO"
case warning = "WARNING"
case error = "ERROR"
case none = "NONE"

/// Returns the numeric order of the log level (lower = more verbose)
public func order() -> Int {
switch self {
case .verbose:
return 0
case .debug:
return 1
case .info:
return 2
case .warning:
return 3
case .error:
return 4
case .none:
return 5
}
}
}
20 changes: 20 additions & 0 deletions Sources/Logging/LogPrinter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// LogPrinter.swift
// Logging
//

import Foundation

/// Protocol to enable testing for Logger class
public protocol LogPrinter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we wrap this in IF DEBUG macros?

func stdout(_ items: Any...)
}

/// Default implementation that prints to stdout
public class DefaultLogPrinter: LogPrinter {
public init() {}

public func stdout(_ items: Any...) {
print(items)
}
}
54 changes: 54 additions & 0 deletions Sources/Logging/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Logger.swift
// Logging
//

import Foundation

/// Main logger class for the Logging module
public class Logger: @unchecked Sendable {
public var printer: LogPrinter = DefaultLogPrinter()
public var dateProvider: DateProvider = DefaultDateProvider()
private let tag: String = "SplitSDK"

public var level: LogLevel = .none

public static let shared: Logger = {
return Logger()
}()

private init() {}

private func log(level: LogLevel, msg: String, _ ctx: Any ...) {
if level.order() < self.level.order() {
return
}

let timeLabel = dateProvider.nowLabel()
if ctx.count == 0 {
printer.stdout(timeLabel, level.rawValue, tag, msg)
} else {
printer.stdout(timeLabel, level.rawValue, tag, msg, ctx[0])
}
}

public static func v(_ message: String, _ context: Any ...) {
shared.log(level: .verbose, msg: message, context)
}

public static func d(_ message: String, _ context: Any ...) {
shared.log(level: .debug, msg: message, context)
}

public static func i(_ message: String, _ context: Any ...) {
shared.log(level: .info, msg: message, context)
}

public static func w(_ message: String, _ context: Any ...) {
shared.log(level: .warning, msg: message, context)
}

public static func e(_ message: String, _ context: Any ...) {
shared.log(level: .error, msg: message, context)
}
}
8 changes: 8 additions & 0 deletions Sources/Logging/Logging.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

public struct LoggingInternal {
public init() {}
public func action() {
print("Logging ready.")
}
}
73 changes: 73 additions & 0 deletions Sources/Logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Logging

## What this module provides

- `Logger`: shared logger with convenience methods `Logger.v/d/i/w/e`
- `LogLevel`: logging level enum
- `LogPrinter`: output interface (default prints to stdout)
- `TimeChecker`: helper to measure and log time intervals
- `DateProvider`: timestamp + label formatting (ships with a `DefaultDateProvider` backed by `Foundation.Date`)

## Usage

### 1) Configure (optional)

The module ships with a `DefaultDateProvider` that uses `Foundation.Date`, so it works out of the box. You can still supply a custom `DateProvider` if you need different formatting or a custom clock:

```swift
import Logging

// Optional: override the default provider
Logger.shared.dateProvider = MyCustomDateProvider()
Logger.shared.level = .info
```

### 2) Log

```swift
import Logging

Logger.i("SDK initialized")
Logger.w("Something looks off", ["context": "value"])
Logger.e("Something failed")
```

### Optional: customize output

```swift
import Logging

final class MyPrinter: LogPrinter {
func stdout(_ items: Any...) {
// route to OSLog, a file, your analytics, etc.
}
}

Logger.shared.printer = MyPrinter()
```

### Optional: TimeChecker

`TimeChecker` uses `Logger.shared.dateProvider` by default.

```swift
import Logging

TimeChecker.start()
// ... do work ...
TimeChecker.logInterval("Finished work")
```

## Notes

- If the consumer has its own log levels, do the mapping in the consumer module (e.g. map `YourLogLevel` → `LogLevel`).

## Running tests

### Swift Package Manager

From the repository root:

```bash
swift test --filter LoggingTests
```
30 changes: 30 additions & 0 deletions Sources/Logging/Tests/LogPrinterStub.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// LogPrinterStub.swift
// LoggingTests
//
// Created by Javier Avrudsky on 08-Jul-2022.
// Copyright © 2022 Split. All rights reserved.
//

import Foundation
@testable import Logging

class LogPrinterStub: LogPrinter, @unchecked Sendable {

private(set) var logs = [String]()

private let queue = DispatchQueue(label: "Logging.LogPrinterStub",
target: .global())

func stdout(_ items: Any...) {
queue.sync {
self.logs.append(items.map { "\($0)" }.joined(separator: ","))
}
}

func clear() {
queue.sync {
self.logs.removeAll()
}
}
}
Loading
Loading