From 9d0a5b67af08d8f931cb09a0b57bf7e5cc2fb33c Mon Sep 17 00:00:00 2001 From: Iurii Khvorost Date: Wed, 21 Aug 2024 17:29:12 +0300 Subject: [PATCH] Added text() func to LogItem and LogScope --- Sources/DLog/DLog.swift | 2 +- Sources/DLog/LogConfig.swift | 14 ++ .../DLog/{Text.swift => LogItem+Text.swift} | 145 +++--------------- Sources/DLog/LogOutput.swift | 12 +- Sources/DLog/LogScope.swift | 41 +++++ Sources/DLog/Standard.swift | 37 ++++- Tests/DLogTests/DLogTests.swift | 28 ++-- Tests/DLogTestsObjC/DLogTestsObjC.m | 3 + 8 files changed, 128 insertions(+), 154 deletions(-) rename Sources/DLog/{Text.swift => LogItem+Text.swift} (59%) diff --git a/Sources/DLog/DLog.swift b/Sources/DLog/DLog.swift index e4f9b10..9ddb1a1 100644 --- a/Sources/DLog/DLog.swift +++ b/Sources/DLog/DLog.swift @@ -86,7 +86,7 @@ public class DLog: Log { /// - Parameters: /// - output: A target output object. If it is omitted the logger uses `stdout` by default. /// - public init(_ output: LogOutput? = .textEmoji, config: LogConfig = LogConfig(), metadata: Metadata = Metadata()) { + public init(_ output: LogOutput? = .stdout, config: LogConfig = LogConfig(), metadata: Metadata = Metadata()) { self.output = output super.init(logger: nil, category: "DLOG", config: config, metadata: metadata) self.logger = self diff --git a/Sources/DLog/LogConfig.swift b/Sources/DLog/LogConfig.swift index 26ada7a..5a5c8f5 100644 --- a/Sources/DLog/LogConfig.swift +++ b/Sources/DLog/LogConfig.swift @@ -77,8 +77,22 @@ public struct LogOptions: OptionSet { public static let regular: Self = [.sign, .time, .category, .padding, .type, .location, .metadata] } +/// Style of text to output. +public enum Style { + /// Universal plain text. + case plain + + /// Text with type icons for info, debug etc. (useful for XCode console). + case emoji + + /// Colored text with ANSI escape codes (useful for Terminal and files). + case colored +} + /// Contains configuration values regarding to the logger public struct LogConfig { + public var style: Style = .emoji + /// Start sign of the logger public var sign: Character = "•" diff --git a/Sources/DLog/Text.swift b/Sources/DLog/LogItem+Text.swift similarity index 59% rename from Sources/DLog/Text.swift rename to Sources/DLog/LogItem+Text.swift index 28ccc05..2686944 100644 --- a/Sources/DLog/Text.swift +++ b/Sources/DLog/LogItem+Text.swift @@ -1,5 +1,5 @@ // -// Text.swift +// LogItem+Text.swift // // Created by Iurii Khvorost on 2020/08/03. // Copyright © 2020 Iurii Khvorost. All rights reserved. @@ -33,7 +33,7 @@ fileprivate extension Array where Element == String { } } -private enum ANSIEscapeCode: String { +enum ANSIEscapeCode: String { case reset = "\u{001b}[0m" case clear = "\u{001b}c" @@ -63,7 +63,7 @@ private enum ANSIEscapeCode: String { case backgroundWhite = "\u{001b}[47m" } -fileprivate extension String { +extension String { func color(_ codes: [ANSIEscapeCode]) -> String { return codes.map { $0.rawValue }.joined() + self + ANSIEscapeCode.reset.rawValue } @@ -111,11 +111,7 @@ private extension LogType { } } -/// A source output that generates text representation of log messages. -/// -/// It doesn’t deliver text to any target outputs (stdout, file etc.) and usually other outputs use it. -/// -public class Text : LogOutput { +extension LogItem { private struct Tag { let textColor: ANSIEscapeCode @@ -134,39 +130,13 @@ public class Text : LogOutput { .interval : Tag(textColor: .textGreen, colors: [.backgroundGreen, .textBlack]), ] - /// Style of text to output. - public enum Style { - /// Universal plain text. - case plain - - /// Text with type icons for info, debug etc. (useful for XCode console). - case emoji - - /// Colored text with ANSI escape codes (useful for Terminal and files). - case colored - } - - private let style: Style - - /// Creates `Text` source output object. - /// - /// let logger = DLog(Text(style: .emoji)) - /// logger.info("It's emoji text") - /// - /// - Parameters: - /// - style: Style of text to output (defaults to `.plain`). - /// - public init(style: Style) { - self.style = style - } - - private static let dateFormatter: DateFormatter = { + static let dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "HH:mm:ss.SSS" return dateFormatter }() - private func logPrefix(items: [(LogOptions, () -> String)], options: LogOptions) -> String { + static func logPrefix(items: [(LogOptions, () -> String)], options: LogOptions) -> String { items.compactMap { guard options.contains($0.0) else { return nil @@ -177,13 +147,13 @@ public class Text : LogOutput { .joinedCompact() } - private func textMessage(item: LogItem) -> String { - var sign = { "\(item.config.sign)" } - var time = { Self.dateFormatter.string(from: item.time) } - var level = { String(format: "[%02d]", item.scope?.level ?? 0) } - var category = { "[\(item.category)]" } + func text() -> String { + var sign = { "\(self.config.sign)" } + var time = { Self.dateFormatter.string(from: self.time) } + var level = { String(format: "[%02d]", self.scope?.level ?? 0) } + var category = { "[\(self.category)]" } let padding = { - guard let scope = item.scope, scope.level > 0 else { return "" } + guard let scope = self.scope, scope.level > 0 else { return "" } return (1...scope.level) .map { ScopeStack.shared.exists(level: $0) @@ -192,27 +162,27 @@ public class Text : LogOutput { } .joined() } - var type = { "[\(item.type.title)]" } - var location = { "<\(item.location.fileName):\(item.location.line)>" } + var type = { "[\(self.type.title)]" } + var location = { "<\(self.location.fileName):\(self.location.line)>" } var metadata = { - item.metadata + self.metadata .filter { $0.isEmpty == false } .map { - let pretty = item.type == .trace && item.config.traceConfig.style == .pretty + let pretty = self.type == .trace && self.config.traceConfig.style == .pretty return $0.json(pretty: pretty) } .joined(separator: " ") } - var message = item.message + var message = message - switch style { + switch config.style { case .plain: break case .colored: - assert(Self.tags[item.type] != nil) - let tag = Self.tags[item.type]! + assert(Self.tags[self.type] != nil) + let tag = Self.tags[self.type]! let s = sign sign = { s().color(.dim) } @@ -223,8 +193,8 @@ public class Text : LogOutput { let l = level level = { l().color(.dim) } - category = { item.category.color(.textBlue) } - type = { " \(item.type.title) ".color(tag.colors) } + category = { self.category.color(.textBlue) } + type = { " \(self.type.title) ".color(tag.colors) } let loc = location location = { loc().color([.dim, tag.textColor]) } @@ -235,7 +205,7 @@ public class Text : LogOutput { message = message.color(tag.textColor) case .emoji: - type = { "\(item.type.icon) [\(item.type.title)]" } + type = { "\(self.type.icon) [\(self.type.title)]" } } let items: [(LogOptions, () -> String)] = [ @@ -248,74 +218,7 @@ public class Text : LogOutput { (.location, location), (.metadata, metadata) ] - let prefix = logPrefix(items: items, options: item.config.options) + let prefix = Self.logPrefix(items: items, options: config.options) return [prefix, message].joinedCompact() } - - private func textScope(scope: LogScope) -> String { - let start = scope.duration == 0 - - var sign = { "\(scope.config.sign)" } - var time = start - ? Self.dateFormatter.string(from: scope.time) - : Self.dateFormatter.string(from: scope.time.addingTimeInterval(scope.duration)) - let ms = !start ? "(\(stringFromTimeInterval(scope.duration)))" : nil - var category = { "[\(scope.category)]" } - var level = { String(format: "[%02d]", scope.level) } - let padding: () -> String = { - let text = (1.. String)] = [ - (.sign, sign), - (.time, { time }), - (.level, level), - (.category, category), - (.padding, padding), - ] - let prefix = logPrefix(items: items, options: scope.config.options) - return prefix.isEmpty ? text : "\(prefix) \(text)" - } - - // MARK: - LogOutput - - override func log(item: LogItem) { - super.log(item: item) - textMessage(item: item) - } - - override func enter(scope: LogScope) { - super.enter(scope: scope) - textScope(scope: scope) - } - - override func leave(scope: LogScope) { - super.leave(scope: scope) - textScope(scope: scope) - } - - override func begin(interval: LogInterval) { - super.begin(interval: interval) - } - - override func end(interval: LogInterval) { - super.end(interval: interval) - textMessage(item: interval) - } } diff --git a/Sources/DLog/LogOutput.swift b/Sources/DLog/LogOutput.swift index c428556..60a6171 100644 --- a/Sources/DLog/LogOutput.swift +++ b/Sources/DLog/LogOutput.swift @@ -27,15 +27,7 @@ import Foundation /// A base output class. @objcMembers -public class LogOutput : NSObject { - /// Creates `Text` output with plain style. - public static var textPlain: Text { Text(style: .plain) } - - /// Creates `Text` output with emoji style. - public static var textEmoji: Text { Text(style: .emoji) } - - /// Creates `Text` output with colored style (ANSI escape codes). - public static var textColored: Text { Text(style: .colored) } +public class LogOutput: NSObject { /// Creates `Standard` output for `stdout` stream. @objc(stdOut) @@ -72,7 +64,7 @@ public class LogOutput : NSObject { public static func net(_ name: String) -> Net { Net(name: name) } #endif - /// A source output. + /// Next output. fileprivate var next: LogOutput? func log(item: LogItem) { diff --git a/Sources/DLog/LogScope.swift b/Sources/DLog/LogScope.swift index 713990f..66d4f8b 100644 --- a/Sources/DLog/LogScope.swift +++ b/Sources/DLog/LogScope.swift @@ -133,4 +133,45 @@ public class LogScope: Log { logger.output?.leave(scope: self) } } + + func text() -> String { + let start = duration == 0 + + var sign = { "\(self.config.sign)" } + var time = start + ? LogItem.dateFormatter.string(from: time) + : LogItem.dateFormatter.string(from: time.addingTimeInterval(duration)) + let ms = !start ? "(\(stringFromTimeInterval(duration)))" : nil + var category = { "[\(self.category)]" } + var level = { String(format: "[%02d]", self.level) } + let padding: () -> String = { + let text = (1.. String)] = [ + (.sign, sign), + (.time, { time }), + (.level, level), + (.category, category), + (.padding, padding), + ] + let prefix = LogItem.logPrefix(items: items, options: config.options) + return prefix.isEmpty ? text : "\(prefix) \(text)" + } } diff --git a/Sources/DLog/Standard.swift b/Sources/DLog/Standard.swift index d31dc0f..ada1aff 100644 --- a/Sources/DLog/Standard.swift +++ b/Sources/DLog/Standard.swift @@ -28,7 +28,7 @@ import Foundation /// A target output that can output text messages to POSIX streams. /// -public class Standard: NSObject { +public class Standard: LogOutput { let stream: UnsafeMutablePointer @@ -41,14 +41,39 @@ public class Standard: NSObject { /// - stream: POSIX stream: `Darwin.stdout`, `Darwin.stderr`. /// - source: A source output (defaults to `.textPlain`). /// - public init(stream: UnsafeMutablePointer = Darwin.stdout, source: LogOutput = .textPlain) { + public init(stream: UnsafeMutablePointer = Darwin.stdout) { self.stream = stream } - private func echo(_ text: String?) -> String? { - if let str = text, !str.isEmpty { - fputs(str + "\n", stream) + private func echo(_ text: String) { + if !text.isEmpty { + fputs(text + "\n", stream) } - return text + } + + // MARK: - LogOutput + + override func log(item: LogItem) { + super.log(item: item) + echo(item.text()) + } + + override func enter(scope: LogScope) { + super.enter(scope: scope) + echo(scope.text()) + } + + override func leave(scope: LogScope) { + super.leave(scope: scope) + echo(scope.text()) + } + +// override func begin(interval: LogInterval) { +// super.begin(interval: interval) +// } + + override func end(interval: LogInterval) { + super.end(interval: interval) + echo(interval.text()) } } diff --git a/Tests/DLogTests/DLogTests.swift b/Tests/DLogTests/DLogTests.swift index 7e42ada..bec87c1 100644 --- a/Tests/DLogTests/DLogTests.swift +++ b/Tests/DLogTests/DLogTests.swift @@ -31,16 +31,6 @@ extension String { } } -extension DispatchSemaphore { - static func Lock() -> DispatchSemaphore { - return DispatchSemaphore(value: 0) - } - - static func Mutex() -> DispatchSemaphore { - return DispatchSemaphore(value: 1) - } -} - extension XCTestCase { func wait(count: Int, timeout: TimeInterval = 1, repeat r: Int = 1, name: String = #function, closure: ([XCTestExpectation]) -> Void) { @@ -70,10 +60,6 @@ func delay(_ sec: TimeInterval = 0.25) { Thread.sleep(forTimeInterval: sec) } -func asyncAfter(_ sec: Double = 0.25, closure: @escaping (() -> Void) ) { - DispatchQueue.global().asyncAfter(deadline: .now() + sec, execute: closure) -} - /// Get text standard output func readStream(file: Int32, stream: UnsafeMutablePointer, block: () -> Void) -> String? { var result: String? @@ -139,6 +125,17 @@ let Interval = #"\{average:\#(SECS),duration:\#(SECS)\}"# let Empty = ">$" +final class DLogTests: XCTestCase { + + func test_Log() { + let log = DLog(.stdout) + log.scope("Test") { scope in + scope.trace() + } + } +} + +/* fileprivate func testAll(_ logger: Log, categoryTag: String = CategoryTag, metadata: String = "") { let padding = #"[\|\├\s]+"# @@ -164,8 +161,7 @@ fileprivate func testAll(_ logger: Log, categoryTag: String = CategoryTag, metad XCTAssert(read_stdout { logger.scope("scope") { _ in delay() } }?.match(#"\#(categoryTag)\#(padding)└ \[scope\] \(\#(SECS)\)"#) == true) XCTAssert(read_stdout { logger.interval("signpost") { delay() } }?.match(#"\#(categoryTag)\#(padding)\[INTERVAL\] \#(Location)\#(metadata) \#(Interval) signpost$"#) == true) } - -/* + final class DLogTests: XCTestCase { // MARK: Tests - diff --git a/Tests/DLogTestsObjC/DLogTestsObjC.m b/Tests/DLogTestsObjC/DLogTestsObjC.m index ce1e145..1904934 100644 --- a/Tests/DLogTestsObjC/DLogTestsObjC.m +++ b/Tests/DLogTestsObjC/DLogTestsObjC.m @@ -89,6 +89,8 @@ + (void)sleep { #pragma mark - Tests +/* + static void testAll(Log* logger, NSString *category) { XCTAssertNotNil(logger); @@ -317,3 +319,4 @@ - (void)test_metadata { } @end +*/