Skip to content

Commit 5e2fe96

Browse files
committed
First commit
0 parents  commit 5e2fe96

File tree

13 files changed

+1248
-0
lines changed

13 files changed

+1248
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
9+
.swiftpm/xcode/

LICENSE

Whitespace-only changes.

Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "SwiftLogOSLogHandler",
8+
platforms: [ .iOS(.v14), .macOS(.v10_13)],
9+
products: [
10+
// Products define the executables and libraries a package produces, making them visible to other packages.
11+
.library(
12+
name: "SwiftLogOSLogHandler",
13+
targets: ["SwiftLogOSLogHandler"]),
14+
],
15+
dependencies: [
16+
.package(
17+
url: "https://github.com/apple/swift-log.git",
18+
from: "1.6.1"
19+
)
20+
],
21+
targets: [
22+
// Targets are the basic building blocks of a package, defining a module or a test suite.
23+
// Targets can depend on other targets in this package and products from dependencies.
24+
.target(
25+
name: "SwiftLogOSLogHandler",
26+
dependencies: [
27+
.product(name: "Logging", package: "swift-log")
28+
]
29+
),
30+
.testTarget(
31+
name: "SwiftLogOSLogHandlerTests",
32+
dependencies: ["SwiftLogOSLogHandler"]
33+
),
34+
]
35+
)

README.MD

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# SwiftLogOSLogHandler
2+
3+
**SwiftLogOSLogHandler** is a logging backend for Apple’s [swift-log](https://github.com/apple/swift-log). It integrates with Apple’s [OSLog](https://developer.apple.com/documentation/os/logging) Unified Logging system and provides advanced string interpolation for flexible, compile-time-controlled logging.
4+
5+
---
6+
7+
## Features
8+
9+
- **Apple Unified Logging Integration**: Leverage OSLog for optimized, structured, and categorized logging.
10+
- **Advanced String Interpolation**: Customize log messages with options like privacy, formatting, alignment, and more.
11+
12+
---
13+
14+
## Installation
15+
16+
### Swift Package Manager
17+
18+
To include **SwiftLogOSLogHandler** in your project, add it to your `Package.swift` file:
19+
20+
```swift
21+
let package = Package(
22+
name: "YourProject",
23+
platforms: [
24+
.iOS(.v14),
25+
.macOS(.v10_13)
26+
],
27+
dependencies: [
28+
.package(
29+
url: "git@github.com:TechArtists/ios-swift-log-os-log-handler.git",
30+
from: "1.0.0"
31+
)
32+
],
33+
targets: [
34+
.target(
35+
name: "YourTarget",
36+
dependencies: [
37+
.product(name: "SwiftLogOSLogHandler", package: "SwiftLogOSLogHandler")
38+
]
39+
)
40+
]
41+
)
42+
```
43+
Alternatively, to add the package using Xcode:
44+
45+
1. Navigate to File > Add Packages.
46+
2. Enter the repository URL: `git@github.com:TechArtists/ios-swift-log-os-log-handler.git`.
47+
3. Add the package to your target.
48+
## Usage
49+
50+
To effectively utilize logging in your application, initialize and manage your loggers using the `init(label: String, factory: (String) -> any LogHandler)` method of the `Logger`. This method allows for versatile logging configurations, making it easy to combine `SwiftLogOSLogHandler` with other logging handlers, such as file-based handlers, using the `MultiplexLogHandler`.
51+
52+
```swift
53+
import Logging
54+
import Foundation
55+
import SwiftLogFileLogHandler
56+
import SwiftLogOSLogHandler
57+
58+
enum Loggers {
59+
static let main = Logging.Logger(label: "main") { label in
60+
MultiplexLogHandler([
61+
SwiftLogOSLogHandler(label: label, shouldLogCallsiteMetadata: true),
62+
SwiftLogFileLogHandler(label: label)
63+
])
64+
}
65+
66+
static let onboarding = Logging.Logger(label: "onboarding") { label in
67+
MultiplexLogHandler([
68+
SwiftLogOSLogHandler(label: label),
69+
SwiftLogFileLogHandler(label: label)
70+
])
71+
}
72+
}
73+
74+
Loggers.main.info("Welcome Main Logger")
75+
Loggers.onboarding.info("Welcome Onboarding logger")
76+
77+
// Output
78+
// Main: Welcome Main Logger
79+
// Onboarding: Welcome Onboarding logger
80+
81+
```
82+
83+
## Differences Between `SwiftLogOSLogHandler` and `OS_log`
84+
85+
While `SwiftLogOSLogHandler` provides a convenient bridge to integrate Apple's OSLog system with the Swift logging API, there are some important distinctions to consider when choosing between using this library and using `OS_log` directly:
86+
87+
### Performance
88+
89+
- **OSlog Optimizations**: The native `OSlog` API is optimized for performance, offering efficient logging capabilities that are tailored specifically for Apple's operating systems. This includes direct support for various log levels, privacy controls, and more, with minimal overhead.
90+
- **SwiftLogOSLogHandler Efficiency**: Using `SwiftLogOSLogHandler` introduces some overhead compared to directly using `OSlog`, due to the additional layer that bridges Swift's logging system with `OSlog`. However, this trade-off is generally acceptable for many applications that benefit from the streamlined logging API provided by `swift-log`.
91+
92+
### Call Site Accuracy
93+
94+
- **OS_log Call Site**: The native `OSlog` accurately captures the call site information (file, function, line) and associates this metadata directly with each log entry.
95+
- **SwiftLogOSLogHandler Call Site**: Due to the way Swift’s logging system interacts with `OS_log`, the call site information will not reflect the actual source of the log message but rather the location within the handler itself. To compensate for this, `SwiftLogOSLogHandler` offers a `shouldLogCallsiteMetadata` parameter. When enabled, this option appends call site metadata directly into your log messages in the format: `[File: \(file), Function: \(function), Line: \(line)]`:
96+
97+
```swift
98+
let main = SwiftLogOSLogHandler(label: "example", shouldLogCallsiteMetadata: true)
99+
100+
// Example Log Message
101+
main.info("Important action performed")
102+
103+
// Output
104+
// Important action performed [File: Example.swift, Function: performAction, Line: 42]
105+
```
106+
107+
### Advanced String Interpolation with `taMessage`
108+
109+
The `taMessage` parameter allows for enhanced string interpolation in log messages, including:
110+
- **Privacy Options**: Use `.public` or `.private` to control the visibility of log message content.
111+
- **Formatting**: Apply formatting options such as `.fixed` for numeric precision.
112+
- **Alignment**: Define alignment and column width for text.
113+
114+
**Example Usage**:
115+
116+
```swift
117+
let testString = "Performance Test"
118+
let cpuUsage = 89.57
119+
120+
Loggers.main.info(taMessage: "\(testString, privacy: .public)")
121+
Loggers.main.error(taMessage: "High CPU usage: \(cpuUsage, format: .fixed(precision: 2), align: .left(columns: 10))%")
122+
Loggers.main.critical(taMessage: "System health critical. \(UUID(), privacy: .private) CPU: \(cpuUsage, privacy: .public)")
123+
Loggers.main.notice(taMessage: "Running maintenance. Task ID: \(UUID(), privacy: .public)")
124+
```
125+
126+
**Example Output**:
127+
128+
```
129+
Performance Test [File: SystemMonitor.swift, Function: checkPerformance, Line: 120]
130+
High CPU usage: <redacted>%
131+
System health critical. <redacted> CPU: 89.57
132+
Running maintenance. Task ID: 5E6A10C8-45F0-4923-8C28-3A6C8D17F7AA
133+
```
134+
135+
### Logging with Bootstrapping
136+
137+
To ensure all logs are effectively captured and routed through the desired logging backend, you need to bootstrap the logging system. Bootstrapping associates a specific handler, such as `SwiftLogOSLogHandler`, to all `Logger` instances created thereafter.
138+
139+
Here’s how to configure OSLog as the universal backend by bootstrapping the logging system:
140+
141+
```swift
142+
import Logging
143+
import SwiftLogOSLogHandler
144+
145+
// Bootstrap the logging system with OSLog as the handler.
146+
LoggingSystem.bootstrap { label in
147+
SwiftLogOSLogHandler(label: label)
148+
}
149+
150+
// Create a Logger instance after bootstrapping.
151+
let loggerMain = Logger(label: "Main")
152+
153+
// Log messages using the logger instance.
154+
// These messages will now be routed and handled by the bootstrapped SwiftLogOSLogHandler.
155+
loggerMain.info("Application started successfully.")
156+
loggerMain.warning("Low disk space detected.")
157+
```
158+
159+
**OSLog Output**:
160+
```
161+
Main: info: Application started successfully.
162+
Main: warning: Low disk space detected.
163+
```
164+
165+
### Important Considerations
166+
167+
- **Bootstrap Before Using Loggers**: Ensure that you bootstrap the logging system *before* creating any `Logger` instances. Loggers created prior to bootstrapping will not be associated with the specified backend and may not output logs as expected.
168+
169+
- **Universal Handling**: Once you bootstrap with `SwiftLogOSLogHandler`, all subsequent `Logger` instances in your application will use OSLog, ensuring consistent and centralized logging behavior.
170+
171+
## License
172+
173+
This project is licensed under the MIT License. See the LICENSE file for more details.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// LogInterpolation+Formatting.swift
3+
// TALogger
4+
//
5+
// Created by Robert Tataru on 01.10.2024.
6+
//
7+
8+
9+
public enum LogBoolFormatting: Sendable {
10+
/// Displays an interpolated boolean value as true or false.
11+
case truth
12+
/// Displays an interpolated boolean value as yes or no.
13+
case answer
14+
}
15+
16+
public enum LogStringAlignment: Sendable {
17+
case none
18+
case left(columns: Int)
19+
case right(columns: Int)
20+
}
21+
22+
public enum LogFloatFormatting: Sendable {
23+
case fixed(precision: Int, explicitPositiveSign: Bool)
24+
25+
public static func fixed(explicitPositiveSign: Bool = false, precision: Int = 6) -> LogFloatFormatting {
26+
.fixed(precision: precision, explicitPositiveSign: explicitPositiveSign)
27+
}
28+
}
29+
30+
public enum LogIntegerFormatting: Sendable {
31+
case decimal(minDigits: Int, explicitPositiveSign: Bool)
32+
33+
public static func decimal(explicitPositiveSign: Bool = false, minDigits: Int = 0) -> Self {
34+
.decimal(minDigits: minDigits, explicitPositiveSign: explicitPositiveSign)
35+
}
36+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// LogPrivacy.swift
3+
// TALogger
4+
//
5+
// Created by Robert Tataru on 01.10.2024.
6+
//
7+
8+
public enum LogPrivacy: Equatable, Sendable {
9+
public enum Mask: Equatable, Sendable {
10+
case hash
11+
case none
12+
}
13+
14+
case `public`
15+
case `private`(mask: Mask = .none)
16+
17+
#if DEBUG
18+
nonisolated(unsafe) internal static var disableRedaction: Bool = true
19+
#endif
20+
21+
internal static let redacted: String = "<redacted>"
22+
23+
/// Returns the string representation of the value based on the privacy settings.
24+
///
25+
/// - Parameter value: The value to be processed.
26+
/// - Returns: A string that is either the original value, its hash, or a redacted placeholder.
27+
public func value(for value: Any) -> String {
28+
#if DEBUG
29+
if Self.disableRedaction {
30+
return String(describing: value)
31+
}
32+
#endif
33+
34+
switch self {
35+
case .public:
36+
return String(describing: value)
37+
case .private(let mask):
38+
switch mask {
39+
case .hash:
40+
return "\(String(describing: value).hash)"
41+
case .none:
42+
return Self.redacted
43+
}
44+
}
45+
}
46+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Untitled.swift
3+
// TALogger
4+
//
5+
// Created by Robert Tataru on 01.10.2024.
6+
//
7+
8+
public extension TAMessage.LogStringInterpolation {
9+
public mutating func appendInterpolation(_ argumentString: @Sendable @autoclosure @escaping () -> String, align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) {
10+
addInterpolationType(.string(argumentString, alignment: align, privacy: privacy))
11+
}
12+
13+
public mutating func appendInterpolation<T>(_ value: @Sendable @autoclosure @escaping () -> T, align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) where T: CustomStringConvertible {
14+
addInterpolationType(.stringConvertible(value, alignment: align, privacy: privacy))
15+
}
16+
17+
public mutating func appendInterpolation<T: SignedInteger>(_ number: @Sendable @autoclosure @escaping () -> T, format: LogIntegerFormatting = .decimal(), align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) {
18+
addInterpolationType(.signedInt({ Int64(number()) }, format: format, alignment: align, privacy: privacy))
19+
}
20+
21+
public mutating func appendInterpolation<T: UnsignedInteger>(_ number: @Sendable @autoclosure @escaping () -> T, format: LogIntegerFormatting = .decimal(), align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) {
22+
addInterpolationType(.unsignedInt({ UInt64(number()) }, format: format, alignment: align, privacy: privacy))
23+
}
24+
25+
public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> Float, format: LogFloatFormatting = .fixed(), align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) {
26+
addInterpolationType(.float(number, format: format, alignment: align, privacy: privacy))
27+
}
28+
29+
public mutating func appendInterpolation(_ number: @Sendable @autoclosure @escaping () -> Double, format: LogFloatFormatting = .fixed(), align: LogStringAlignment = .none, privacy: LogPrivacy = .private()) {
30+
addInterpolationType(.double(number, format: format, alignment: align, privacy: privacy))
31+
}
32+
33+
public mutating func appendInterpolation(_ boolean: @Sendable @autoclosure @escaping () -> Bool, format: LogBoolFormatting = .truth, privacy: LogPrivacy = .private()) {
34+
addInterpolationType(.bool(boolean, format: format, privacy: privacy))
35+
}
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// LogStringInterpolation.swift
3+
// TALogger
4+
//
5+
// Created by Robert Tataru on 01.10.2024.
6+
//
7+
import Foundation
8+
9+
extension TAMessage {
10+
public struct LogStringInterpolation: StringInterpolationProtocol, Sendable {
11+
12+
public enum InterpolationType : Sendable{
13+
case literal(String)
14+
case string(@Sendable () -> String, alignment: LogStringAlignment, privacy: LogPrivacy)
15+
case stringConvertible(@Sendable () -> CustomStringConvertible, alignment: LogStringAlignment, privacy: LogPrivacy)
16+
case signedInt(@Sendable () -> Int64, format: LogIntegerFormatting, alignment: LogStringAlignment, privacy: LogPrivacy)
17+
case unsignedInt(@Sendable () -> UInt64, format: LogIntegerFormatting, alignment: LogStringAlignment, privacy: LogPrivacy)
18+
case double(@Sendable () -> Double, format: LogFloatFormatting, alignment: LogStringAlignment, privacy: LogPrivacy)
19+
case bool(@Sendable () -> Bool, format: LogBoolFormatting, privacy: LogPrivacy)
20+
case float(@Sendable () -> Float, format: LogFloatFormatting, alignment: LogStringAlignment, privacy: LogPrivacy)
21+
}
22+
23+
private(set) var storage: [InterpolationType] = []
24+
25+
public mutating func addInterpolationType(_ type: InterpolationType) {
26+
storage.append(type)
27+
}
28+
29+
public init(literalCapacity: Int, interpolationCount: Int) {}
30+
31+
public mutating func appendLiteral(_ literal: String) {
32+
storage.append(.literal(literal))
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)