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

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
}
}
}