This commit is contained in:
Caleb
2025-11-21 12:00:40 +08:00
commit 18ea900c12
1189 changed files with 134605 additions and 0 deletions

29
Example/Pods/Logboard/LICENSE.md generated Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, shogo4405
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

96
Example/Pods/Logboard/README.md generated Normal file
View File

@@ -0,0 +1,96 @@
# Logboard
[![Release](https://img.shields.io/github/v/release/shogo4405/Logboard)](https://github.com/shogo4405/Logboard/releases/latest)
[![Platform Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fshogo4405%2FLogboard%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/shogo4405/Logboard)
[![Swift Compatibility](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fshogo4405%2FLogboard%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/shogo4405/Logboard)
[![GitHub license](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://raw.githubusercontent.com/shogo4405/Logboard/master/LICENSE.md)
* Simple logging framework for your framework project.
* [API Documentation](https://shogo4405.github.io/Logboard/documentation/logboard/)
## Usage
```swift
let logger = LBLogger.with("identifier")
logger.level = .trace
logger.trace("trace")
logger.debug("debug")
logger.info("hoge")
logger.warn("sample")
logger.error("error")
```
## Requirements
|-|iOS|macOS|tvOS|watchOS|visionOS|Xcode|Swift|
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
|2.5.0+|13.0+|10.15+|13.0+|6.0|-|15.3+|5.8|
|2.4.1+|12.0+|10.13+|12.0+|4.0|1.0+|15.0+|5.3|
## Installation
### CocoaPods
```rb
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
def import_pods
pod 'Logboard', '~> 2.4.1'
end
target 'Your Target' do
platform :ios, '12.0'
import_pods
end
```
### Carthage
```
github "shogo4405/Logboard" ~> 2.4.1
```
## Appenders
### ConsoleAppender
Use print function. You can see Xcode's console.
```swift
let logger = LBLogger.with("identifier")
let console = ConsoleAppender()
logger.appender = console
```
### MultiAppender
```swift
let logger = LBLogger.with("identifier")
let multi = MultiAppender()
multi.appenders.append(ConsoleAppender())
multi.appenders.append(SocketAppender())
logger.appender = multi
```
### SocketAppender
```swift
let logger = LBLogger.with("identifier")
let socket = SocketAppender()
socket.connect("toHost", 22222)
logger.appender = socket
```
## Network Console
iOS, macOS, tvOS, watchOS Debugging Tool, Logging console via Network.
![screenshot](https://user-images.githubusercontent.com/810189/183241560-5ceb2d7e-9421-4eb7-babb-370ce1429645.gif)
### Download
```
git clone https://github.com/shogo4405/Logboard.git
cd Logboard/Console
carthage update --platform macOS --use-xcframewokrs
```
### Build
Open xcode 'Console' and [Product] -> [Archive].
### SocketAppender
```
let logger = LBLogger.with("identifier")
let socket = SocketAppender()
socket.connect("toHost", 22222)
logger.appender = socket
```
## License
BSD-3-Clause

View File

@@ -0,0 +1,16 @@
import Foundation
/// The ConsoleAppender class can output your xcode console with print function.
public class ConsoleAppender: LBLoggerAppender {
/// Creates a ConsoleAppender instance.
public init() {
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int) {
print(LBLogger.dateFormatter.string(from: Date()), "[\(level)]", "[\(logboard.identifier)]", "[\(filename(file.description)):\(line)]", function, ">", message.map({ String(describing: $0) }).joined(separator: ""))
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int) {
print(LBLogger.dateFormatter.string(from: Date()), "[\(level)]", "[\(logboard.identifier)]", "[\(filename(file.description)):\(line)]", function, ">", String(format: format, arguments))
}
}

View File

@@ -0,0 +1,42 @@
import Foundation
extension LBLogger {
/// The logging message model.
public struct Data {
/// The date.
public let date: Date
/// The logging level.
public let level: Level
/// The identifier.
public let identifier: String
/// The filename.
public let file: String
/// The line number.
public let line: Int
/// The function name.
public let function: String
/// The message.
public let message: String
/// Create a Logboard.Data with data.
public init?(_ data: Foundation.Data) {
guard let strings: [String.SubSequence] = String(bytes: data, encoding: .utf8)?.split(separator: "\t"), 7 <= strings.count else {
return nil
}
date = LBLogger.dateFormatter.date(from: String(strings[0])) ?? Date()
level = Level(string: String(strings[1])) ?? .trace
identifier = String(strings[2])
file = String(strings[3])
line = Int(String(strings[4])) ?? 0
function = String(strings[5])
message = strings[6...].joined(separator: "\t")
}
}
}
extension LBLogger.Data: CustomStringConvertible {
// MARK: CustomStringConvertible
public var description: String {
return "\(LBLogger.dateFormatter.string(from: date)) [\(level)] [\(identifier)] [\(filename(file)):\(line)] \(function) > \(message)"
}
}

View File

@@ -0,0 +1,150 @@
import Foundation
func filename(_ file: String) -> String {
return file.components(separatedBy: "/").last ?? file
}
/// The LBLogger class is writing string messages to the LogboardAppender.
public class LBLogger {
/// The default dateFormatter values that is yyyy-dd-MM HH:mm:ss.SSS.
static public var dateFormatter: DateFormatter = {
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-dd-MM HH:mm:ss.SSS"
return dateFormatter
}()
/// The logging level.
public enum Level: Int, CustomStringConvertible {
/// The trace log level.
case trace = 0
/// The debug log level.
case debug = 1
/// The infomative log level.
case info = 2
/// The warning log level.
case warn = 3
/// The error log level.
case error = 4
/// Ctrate a logging level from string.
public init?(string: String) {
switch string {
case "Trace":
self = .trace
case "Debug":
self = .debug
case "Info":
self = .info
case "Warn":
self = .warn
case "Error":
self = .error
default:
return nil
}
}
/// The description represents a string expression.
public var description: String {
switch self {
case .trace:
return "Trace"
case .debug:
return "Debug"
case .info:
return "Info"
case .warn:
return "Warn"
case .error:
return "Error"
}
}
}
private static var instances: [String: LBLogger] = [:]
/// Create or get a Logboard instance.
public static func with(_ identifier: String) -> LBLogger {
if instances[identifier] == nil {
instances[identifier] = LBLogger(identifier)
}
return instances[identifier]!
}
/// The identifier is the subsystem name.
public let identifier: String
/// Specifies the logging level.
public var level: LBLogger.Level = .info
/// Specifies logging appenders.
public var appender: any LBLoggerAppender = ConsoleAppender()
/// Create a logger with the identifier.
public init(_ identifier: String) {
self.identifier = identifier
}
/// Is logging enabled for the supplied level or not.
public func isEnabledFor(level: LBLogger.Level) -> Bool {
return self.level.rawValue <= level.rawValue
}
/// Writes a trace message to the appender.
public func trace(_ message: Any..., file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .trace) else { return }
appender.append(self, level: .trace, message: message, file: file, function: function, line: line)
}
/// Writes a trace message to the appender with a format string.
public func trace(format: String, arguments: any CVarArg, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .trace) else { return }
appender.append(self, level: .trace, format: format, arguments: arguments, file: file, function: function, line: line)
}
/// Writes a debug message to the appender.
public func debug(_ message: Any..., file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .debug) else { return }
appender.append(self, level: .debug, message: message, file: file, function: function, line: line)
}
/// Writes a debug message to the appender with a format string.
public func debug(format: String, arguments: any CVarArg, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .debug) else { return }
appender.append(self, level: .debug, format: format, arguments: arguments, file: file, function: function, line: line)
}
/// Writes a informative message to the appender.
public func info(_ message: Any..., file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .info) else { return }
appender.append(self, level: .info, message: message, file: file, function: function, line: line)
}
/// Writes a informative message to the appender with a format string.
public func info(format: String, arguments: any CVarArg, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .info) else { return }
appender.append(self, level: .info, format: format, arguments: arguments, file: file, function: function, line: line)
}
/// Writes a warning message to the appender.
public func warn(_ message: Any..., file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .warn) else { return }
appender.append(self, level: .warn, message: message, file: file, function: function, line: line)
}
/// Writes a warning message to the appender with a format string.
public func warn(format: String, arguments: any CVarArg, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .warn) else { return }
appender.append(self, level: .warn, format: format, arguments: arguments, file: file, function: function, line: line)
}
/// Writes a error message to the appender.
public func error(_ message: Any..., file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .error) else { return }
appender.append(self, level: .error, message: message, file: file, function: function, line: line)
}
/// Writes a error message to the appender with a format string.
public func error(format: String, arguments: any CVarArg, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
guard isEnabledFor(level: .error) else { return }
appender.append(self, level: .error, format: format, arguments: arguments, file: file, function: function, line: line)
}
}

View File

@@ -0,0 +1,10 @@
import Foundation
/// An interface that manages the logging appending.
public protocol LBLoggerAppender {
/// Appends a logging message string.
func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int)
/// Appends a logging message with a format sting.
func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int)
}

View File

@@ -0,0 +1,30 @@
import Foundation
/// The MultiAppender class delegates appenders.
/// ## Example code:
/// ```
/// let multi = MultiAppender()
/// multi.appenders.append(ConsoleAppender())
/// multi.appenders.append(SocketAppender())
/// logger.appender = multi
/// ```
public class MultiAppender: LBLoggerAppender {
/// The appenders.
public var appenders: [any LBLoggerAppender] = []
/// Creates a MultIAppender instance.
public init() {
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int) {
for appender in appenders {
appender.append(logboard, level: level, message: message, file: file, function: function, line: line)
}
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int) {
for appender in appenders {
appender.append(logboard, level: level, format: format, arguments: arguments, file: file, function: function, line: line)
}
}
}

View File

@@ -0,0 +1,12 @@
import Foundation
/// The NullAppender class does output no message.
public class NullAppender: LBLoggerAppender {
public static let shared = NullAppender()
public func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int) {
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int) {
}
}

View File

@@ -0,0 +1,60 @@
import Foundation
#if canImport(OSLog)
import OSLog
#endif
/// The OSLoggerAppender class can output your Console.app with os_log function.
/// - seealso: https://developer.apple.com/documentation/os/logging/generating_log_messages_from_your_code
@available(iOS 14.0, *)
@available(macOS 11.0, *)
@available(tvOS 14.0, *)
@available(watchOS 7.0, *)
public class OSLoggerAppender: LBLoggerAppender {
private let logger: Logger
/// Creates a logger using the specified subsystem and category.
public init(sybsystem: String, category: String) {
logger = Logger(subsystem: sybsystem, category: category)
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int) {
let message =
"[\(filename(file.description)):\(line)]" +
function.description +
">" +
message.map({ String(describing: $0) }).joined(separator: "")
switch level {
case .trace:
logger.trace("\(message, privacy: .public)")
case .debug:
logger.debug("\(message, privacy: .public)")
case .info:
logger.info("\(message, privacy: .public)")
case .warn:
logger.warning("\(message, privacy: .public)")
case .error:
logger.error("\(message, privacy: .public)")
}
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int) {
let message =
"[\(filename(file.description)):\(line)]" +
function.description +
">" +
String(format: format, arguments)
switch level {
case .trace:
logger.trace("\(message, privacy: .public)")
case .debug:
logger.debug("\(message, privacy: .public)")
case .info:
logger.info("\(message, privacy: .public)")
case .warn:
logger.warning("\(message, privacy: .public)")
case .error:
logger.error("\(message, privacy: .public)")
}
}
}

View File

@@ -0,0 +1,231 @@
import Foundation
/// The SocketAppender class writes a message to LogboardConsole service.
public class SocketAppender: LBLoggerAppender {
private var socket: NetSocket = NetSocket()
/// Creates a SocketAppende instance.
public init() {
}
/// Connects the Logboard Console service.
public func connect(_ name: String, port: Int) {
socket.connect(withName: name, port: port)
}
/// Closes a conneciton.
public func close() {
socket.close(isDisconnected: false)
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, message: [Any], file: StaticString, function: StaticString, line: Int) {
let strings: [String] = [
LBLogger.dateFormatter.string(from: Date()),
level.description,
logboard.identifier,
file.description,
String(line),
function.description,
message.map({ String(describing: $0) }).joined(separator: "")
]
if let data: Data = (strings.joined(separator: "\t") + "\r\n\r\n").data(using: .utf8) {
socket.doOutput(data: data)
}
}
public func append(_ logboard: LBLogger, level: LBLogger.Level, format: String, arguments: any CVarArg, file: StaticString, function: StaticString, line: Int) {
let strings: [String] = [
LBLogger.dateFormatter.string(from: Date()),
level.description,
logboard.identifier,
file.description,
String(line),
function.description,
String(format: format, arguments)
]
if let data: Data = (strings.joined(separator: "\t") + "\r\n\r\n").data(using: .utf8) {
socket.doOutput(data: data)
}
}
}
class NetSocket: NSObject {
static let defaultTimeout: Int64 = 15 // sec
static let defaultWindowSizeC: Int = Int(UInt16.max)
var inputBuffer: Data = Data()
var timeout: Int64 = NetSocket.defaultTimeout
var connected: Bool = false
var windowSizeC: Int = NetSocket.defaultWindowSizeC
var securityLevel: StreamSocketSecurityLevel = .none
var inputStream: InputStream?
var outputStream: OutputStream?
var inputQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.NetSocket.input")
private var buffer: UnsafeMutablePointer<UInt8>?
private var runloop: RunLoop?
private let outputQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.NetSocket.output")
private var timeoutHandler: (() -> Void)?
func connect(withName: String, port: Int) {
inputQueue.async {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(
kCFAllocatorDefault,
withName as CFString,
UInt32(port),
&readStream,
&writeStream
)
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
self.initConnection()
}
}
@discardableResult
final func doOutput(data: Data) -> Int {
outputQueue.async {
data.withUnsafeBytes { bytes in
guard let bytes = bytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
self.doOutputProcess(bytes, maxLength: data.count)
}
}
return data.count
}
final func doOutputProcess(_ buffer: UnsafePointer<UInt8>, maxLength: Int) {
guard let outputStream = outputStream else {
return
}
var total: Int = 0
while total < maxLength {
let length: Int = outputStream.write(buffer.advanced(by: total), maxLength: maxLength - total)
if length <= 0 {
break
}
total += length
}
}
func close(isDisconnected: Bool) {
outputQueue.async {
guard let runloop: RunLoop = self.runloop else {
return
}
self.deinitConnection(isDisconnected: isDisconnected)
self.runloop = nil
CFRunLoopStop(runloop.getCFRunLoop())
}
}
func close() {
close(isDisconnected: false)
}
func listen() {
}
func initConnection() {
buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: windowSizeC)
buffer?.initialize(repeating: 0, count: windowSizeC)
timeoutHandler = didTimeout
inputBuffer.removeAll(keepingCapacity: false)
guard let inputStream: InputStream = inputStream, let outputStream: OutputStream = outputStream else {
return
}
runloop = .current
inputStream.delegate = self
inputStream.schedule(in: runloop!, forMode: RunLoop.Mode.default)
inputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey)
outputStream.delegate = self
outputStream.schedule(in: runloop!, forMode: RunLoop.Mode.default)
outputStream.setProperty(securityLevel.rawValue, forKey: .socketSecurityLevelKey)
inputStream.open()
outputStream.open()
if 0 < timeout {
outputQueue.asyncAfter(deadline: .now() + Double(timeout * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
guard let timeoutHandler: (() -> Void) = self.timeoutHandler else {
return
}
timeoutHandler()
}
}
runloop?.run()
connected = false
}
func deinitConnection(isDisconnected: Bool) {
inputStream?.close()
inputStream?.remove(from: runloop!, forMode: RunLoop.Mode.default)
inputStream?.delegate = nil
inputStream = nil
outputStream?.close()
outputStream?.remove(from: runloop!, forMode: RunLoop.Mode.default)
outputStream?.delegate = nil
outputStream = nil
buffer?.deinitialize(count: windowSizeC)
buffer?.deallocate()
buffer = nil
}
func didTimeout() {
}
private func doInput() {
guard let inputStream: InputStream = inputStream, let buffer: UnsafeMutablePointer<UInt8> = buffer else {
return
}
let length: Int = inputStream.read(buffer, maxLength: windowSizeC)
if 0 < length {
inputBuffer.append(buffer, count: length)
listen()
}
}
}
extension NetSocket: StreamDelegate {
// MARK: StreamDelegate
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
// 1 = 1 << 0
case .openCompleted:
guard let inputStream = inputStream, let outputStream = outputStream,
inputStream.streamStatus == .open && outputStream.streamStatus == .open else {
break
}
if aStream == inputStream {
timeoutHandler = nil
connected = true
}
// 2 = 1 << 1
case .hasBytesAvailable:
if aStream == inputStream {
doInput()
}
// 4 = 1 << 2
case .hasSpaceAvailable:
break
// 8 = 1 << 3
case .errorOccurred:
close(isDisconnected: true)
// 16 = 1 << 4
case .endEncountered:
close(isDisconnected: true)
default:
break
}
}
}