Skip to content

Commit

Permalink
Added text() func to LogItem and LogScope
Browse files Browse the repository at this point in the history
  • Loading branch information
ikhvorost committed Aug 21, 2024
1 parent ee617cf commit 9d0a5b6
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 154 deletions.
2 changes: 1 addition & 1 deletion Sources/DLog/DLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions Sources/DLog/LogConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""

Expand Down
145 changes: 24 additions & 121 deletions Sources/DLog/Text.swift → Sources/DLog/LogItem+Text.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Text.swift
// LogItem+Text.swift
//
// Created by Iurii Khvorost <[email protected]> on 2020/08/03.
// Copyright © 2020 Iurii Khvorost. All rights reserved.
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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) }
Expand All @@ -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]) }
Expand All @@ -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)] = [
Expand All @@ -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..<scope.level)
.map { ScopeStack.shared.exists(level: $0) ? "| " : " " }
.joined()
return "\(text)\(start ? "" : "")"
}
var text = "[\(scope.name)] \(ms ?? "")"

switch style {
case .emoji, .plain:
break

case .colored:
sign = { "\(scope.config.sign)".color(.dim) }
time = time.color(.dim)
level = { String(format: "[%02d]", scope.level).color(.dim) }
category = { scope.category.color(.textBlue) }
text = "[\(scope.name.color(.textMagenta))] \((ms ?? "").color(.dim))"
}

let items: [(LogOptions, () -> 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)
}
}
12 changes: 2 additions & 10 deletions Sources/DLog/LogOutput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions Sources/DLog/LogScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<self.level)
.map { ScopeStack.shared.exists(level: $0) ? "| " : " " }
.joined()
return "\(text)\(start ? "" : "")"
}
var text = "[\(name)] \(ms ?? "")"

switch config.style {
case .emoji, .plain:
break

case .colored:
sign = { "\(self.config.sign)".color(.dim) }
time = time.color(.dim)
level = { String(format: "[%02d]", self.level).color(.dim) }
category = { self.category.color(.textBlue) }
text = "[\(name.color(.textMagenta))] \((ms ?? "").color(.dim))"
}

let items: [(LogOptions, () -> 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)"
}
}
37 changes: 31 additions & 6 deletions Sources/DLog/Standard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<FILE>

Expand All @@ -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<FILE> = Darwin.stdout, source: LogOutput = .textPlain) {
public init(stream: UnsafeMutablePointer<FILE> = 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())
}
}
Loading

0 comments on commit 9d0a5b6

Please sign in to comment.