Skip to content

Commit

Permalink
Refactored metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
ikhvorost committed Dec 18, 2023
1 parent b9b2c6b commit c64b343
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 228 deletions.
2 changes: 1 addition & 1 deletion Sources/DLog/DLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public class DLog: LogProtocol {
out.intervalEnd(interval: interval)
}

func log(message: @escaping () -> LogMessage, type: LogType, category: String, config: LogConfig, scope: LogScope?, metadata: Metadata, file: String, function: String, line: UInt) -> String? {
func log(message: @escaping () -> LogMessage, type: LogType, category: String, config: LogConfig, scope: LogScope?, metadata: @autoclosure @escaping () -> [Metadata], file: String, function: String, line: UInt) -> String? {
guard let out = output else { return nil }
let item = LogItem(type: type, category: category, config: config, scope: scope, metadata: metadata, file: file, funcName: function, line: line, message: message)
return out.log(item: item)
Expand Down
52 changes: 26 additions & 26 deletions Sources/DLog/LogInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class LogInterval: LogItem {
private let logger: DLog
private let id: Int
private let name: String
private let _metadata: Metadata
@Atomic
private var begun = false

Expand All @@ -97,28 +98,15 @@ public class LogInterval: LogItem {
/// Accumulated interval statistics
public var statistics: IntervalStatistics { StatisticsStore.shared[id] }

/// Text of this log message.
public override var text: String {
let statistics = self.statistics
let items: [(IntervalOptions, String, () -> Any)] = [
(.duration, "duration", { stringFromTimeInterval(self.duration) }),
(.count, "count", { statistics.count }),
(.total, "total", { stringFromTimeInterval(statistics.total) }),
(.min, "min", { stringFromTimeInterval(statistics.min) }),
(.max, "max", { stringFromTimeInterval(statistics.max) }),
(.average, "average", { stringFromTimeInterval(statistics.average) })
]
let dict = dictionary(from: items, options: config.intervalConfig.options)
let text = [dict.json(), name].joinedCompact()
return text
}

init(logger: DLog, name: String, staticName: StaticString?, category: String, config: LogConfig, scope: LogScope?, metadata: Metadata, file: String, funcName: String, line: UInt) {
self.logger = logger
self.name = name
self.id = "\(file):\(funcName):\(line)".hash
self.staticName = staticName
super.init(type: .interval, category: category, config: config, scope: scope, metadata: metadata, file: file, funcName: funcName, line: line, message: nil)
self._metadata = metadata

let message = { LogMessage(stringLiteral: name) }
super.init(type: .interval, category: category, config: config, scope: scope, metadata: {[metadata]}, file: file, funcName: funcName, line: line, message: message)
}

/// Start a time interval.
Expand Down Expand Up @@ -161,18 +149,30 @@ public class LogInterval: LogItem {
time = Date()

// Statistics
var record = self.statistics
record.count += 1
record.total += duration
if record.min == 0 || record.min > duration {
record.min = duration
var statistics = self.statistics
statistics.count += 1
statistics.total += duration
if statistics.min == 0 || statistics.min > duration {
statistics.min = duration
}
if record.max == 0 || record.max < duration {
record.max = duration
if statistics.max == 0 || statistics.max < duration {
statistics.max = duration
}
record.average = record.total / Double(record.count)
statistics.average = statistics.total / Double(statistics.count)

StatisticsStore.shared[id] = statistics

StatisticsStore.shared[id] = record
// Metadata
let items: [(IntervalOptions, String, () -> Any)] = [
(.duration, "duration", { stringFromTimeInterval(self.duration) }),
(.count, "count", { statistics.count }),
(.total, "total", { stringFromTimeInterval(statistics.total) }),
(.min, "min", { stringFromTimeInterval(statistics.min) }),
(.max, "max", { stringFromTimeInterval(statistics.max) }),
(.average, "average", { stringFromTimeInterval(statistics.average) })
]
let dict = dictionary(from: items, options: config.intervalConfig.options)
metadata = [_metadata, dict]

logger.end(interval: self)
}
Expand Down
16 changes: 7 additions & 9 deletions Sources/DLog/LogItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,27 +87,25 @@ public class LogItem: NSObject {
/// The line number of code this log message originates from.
public let line: UInt

private var message: (() -> LogMessage)?

private let _message: () -> LogMessage
/// Text of this log message.
public var text: String {
return message?().text ?? ""
}
public lazy var message: String = { _message().text }()

let config: LogConfig

private let _metadata: () -> [Metadata]
/// Metadata of log message
public let metadata: Metadata
public lazy var metadata: [Metadata] = { _metadata() }()

init(type: LogType, category: String, config: LogConfig, scope: LogScope?, metadata: Metadata, file: String, funcName: String, line: UInt, message: (() -> LogMessage)?) {
init(type: LogType, category: String, config: LogConfig, scope: LogScope?, metadata: @escaping () -> [Metadata], file: String, funcName: String, line: UInt, message: @escaping () -> LogMessage) {
self.type = type
self.category = category
self.config = config
self.scope = scope
self.metadata = metadata
self._metadata = metadata
self.fileName = (file as NSString).lastPathComponent
self.funcName = funcName
self.line = line
self.message = message
self._message = message
}
}
26 changes: 11 additions & 15 deletions Sources/DLog/LogMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,46 +27,42 @@ import Foundation
public typealias Metadata = [String : Any]

extension Metadata {
func json(parenthesis: Bool = false, pretty: Bool = false) -> String {
func json(quotes: Bool = false, pretty: Bool = false) -> String {
let options: JSONSerialization.WritingOptions = pretty ? [.sortedKeys, .prettyPrinted] : [.sortedKeys]
guard self.isEmpty == false,
let data = try? JSONSerialization.data(withJSONObject: self, options: options),
let json = String(data: data, encoding: .utf8) else {
return ""
}
var result = json
if parenthesis {
let start = json.index(after: json.startIndex)
let end = json.index(before: json.endIndex)
result = "(\(json[start..<end]))"
}
return result.replacingOccurrences(of: "\"", with: "") // Remove quotes
return quotes ? json : json.replacingOccurrences(of: "\"", with: "")
}
}

/// Contextual metadata
@objcMembers
public class LogMetadata: NSObject {
var data = Metadata()
private var _data = Metadata()

init(data: Metadata = Metadata()) {
self.data = data
_data = data
}

var data: Metadata {
synchronized(self) { _data }
}

/// Gets and sets a value by a string key to metadata.
public subscript(name: String) -> Any? {
get {
synchronized(self) { data[name] }
synchronized(self) { _data[name] }
}
set {
synchronized(self) { data[name] = newValue }
synchronized(self) { _data[name] = newValue }
}
}

/// Clears metadata
public func clear() {
synchronized(self) {
data.removeAll()
}
synchronized(self) { _data.removeAll() }
}
}
37 changes: 19 additions & 18 deletions Sources/DLog/LogProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func log(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .log, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .log, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs trace information to help debug problems during the development of your code.
Expand All @@ -80,17 +80,18 @@ public class LogProtocol: NSObject {
/// - Returns: Returns an optional string value indicating whether a log message is generated and processed.
///
@discardableResult
public func trace(_ message: @escaping @autoclosure () -> LogMessage? = nil,
file: String = #file, function: String = #function, line: UInt = #line,
addresses: [NSNumber] = Thread.callStackReturnAddresses) -> String? {
let msg: () -> LogMessage = {
let info = traceMetadata(text: message()?.text,
function: function,
addresses: addresses.dropFirst(),
traceConfig: self.config.traceConfig)
return LogMessage(stringLiteral: info)
public func trace(_ message: @escaping @autoclosure () -> LogMessage = "", file: String = #file, function: String = #function, line: UInt = #line) -> String? {
let processInfo = ProcessInfo.processInfo
let thread = Thread.current
let stackAddresses = Thread.callStackReturnAddresses.dropFirst()

let metadata: () -> [Metadata] = {
[
self.metadata.data,
traceMetadata(function: function, processInfo: processInfo, thread: thread, stackAddresses: stackAddresses, traceConfig: self.config.traceConfig)
]
}
return logger.log(message: msg, type: .trace, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .trace, category: category, config: config, scope: _scope, metadata: metadata(), file: file, function: function, line: line)
}

/// Logs a message to help debug problems during the development of your code.
Expand All @@ -110,7 +111,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func debug(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .debug, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .debug, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs a message that is helpful, but not essential, to diagnose issues with your code.
Expand All @@ -130,7 +131,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func info(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .info, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .info, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs a warning that occurred during the execution of your code.
Expand All @@ -150,7 +151,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func warning(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .warning, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .warning, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs an error that occurred during the execution of your code.
Expand All @@ -170,7 +171,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func error(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .error, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .error, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs a traditional C-style assert notice with an optional message.
Expand All @@ -192,7 +193,7 @@ public class LogProtocol: NSObject {
@discardableResult
public func assert(_ condition: @autoclosure () -> Bool, _ message: @escaping @autoclosure () -> LogMessage = "", file: String = #file, function: String = #function, line: UInt = #line) -> String? {
guard logger != .disabled && !condition() else { return nil }
return logger.log(message: message, type: .assert, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .assert, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Logs a bug or fault that occurred during the execution of your code.
Expand All @@ -212,7 +213,7 @@ public class LogProtocol: NSObject {
///
@discardableResult
public func fault(_ message: @escaping @autoclosure () -> LogMessage, file: String = #file, function: String = #function, line: UInt = #line) -> String? {
return logger.log(message: message, type: .fault, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, function: function, line: line)
return logger.log(message: message, type: .fault, category: category, config: config, scope: _scope, metadata: [self.metadata.data], file: file, function: function, line: line)
}

/// Creates a scope object that can assign log messages to itself.
Expand Down Expand Up @@ -245,7 +246,7 @@ public class LogProtocol: NSObject {
}

private func interval(name: String, staticName: StaticString?, file: String, function: String, line: UInt, closure: (() -> Void)?) -> LogInterval {
let interval = LogInterval(logger: logger, name: name, staticName: staticName, category: category, config: config, scope: _scope, metadata: metadata.data, file: file, funcName: function, line: line)
let interval = LogInterval(logger: logger, name: name, staticName: staticName, category: category, config: config, scope: _scope, metadata: self.metadata.data, file: file, funcName: function, line: line)
if let block = closure {
interval.begin()
block()
Expand Down
2 changes: 1 addition & 1 deletion Sources/DLog/OSLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public class OSLog : LogOutput {

assert(Self.types[item.type] != nil)
let type = Self.types[item.type]!
os_log("%{public}@ %{public}@", dso: Dynamic.dso, log: log, type: type, location, item.text)
os_log("%{public}@ %{public}@", dso: Dynamic.dso, log: log, type: type, location, item.message)

return super.log(item: item)
}
Expand Down
40 changes: 30 additions & 10 deletions Sources/DLog/Text.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public class Text : LogOutput {
var time = { Self.dateFormatter.string(from: item.time) }
var level = { String(format: "[%02d]", item.scope?.level ?? 0) }
var category = { "[\(item.category)]" }
let padding: () -> String = {
let padding = {
guard let scope = item.scope, scope.level > 0 else { return "" }
return (1...scope.level)
.map {
Expand All @@ -190,8 +190,17 @@ public class Text : LogOutput {
}
var type = { "[\(item.type.title)]" }
var location = { "<\(item.fileName):\(item.line)>" }
var metadata = { item.metadata.json(parenthesis: true) }
var text = item.text
var metadata = {
item.metadata
.filter { $0.isEmpty == false }
.map {
let pretty = item.type == .trace && item.config.traceConfig.style == .pretty
return $0.json(pretty: pretty)
}
.joined(separator: " ")
}

var message = item.message

switch style {
case .plain:
Expand All @@ -201,14 +210,25 @@ public class Text : LogOutput {
assert(Self.tags[item.type] != nil)
let tag = Self.tags[item.type]!

sign = { "\(item.config.sign)".color(.dim) }
time = { Self.dateFormatter.string(from: item.time).color(.dim) }
level = { String(format: "[%02d]", item.scope?.level ?? 0).color(.dim) }
let s = sign
sign = { s().color(.dim) }

let t = time
time = { t().color(.dim) }

let l = level
level = { l().color(.dim) }

category = { item.category.color(.textBlue) }
type = { " \(item.type.title) ".color(tag.colors) }
location = { "<\(item.fileName):\(item.line)>".color([.dim, tag.textColor]) }
metadata = { item.metadata.json(parenthesis: true).color(.dim) }
text = text.color(tag.textColor)

let loc = location
location = { loc().color([.dim, tag.textColor]) }

let m = metadata
metadata = { m().color(.dim) }

message = message.color(tag.textColor)

case .emoji:
type = { "\(item.type.icon) [\(item.type.title)]" }
Expand All @@ -225,7 +245,7 @@ public class Text : LogOutput {
(.metadata, metadata)
]
let prefix = logPrefix(items: items, options: item.config.options)
return [prefix, text].joinedCompact()
return [prefix, message].joinedCompact()
}

private func textScope(scope: LogScope) -> String {
Expand Down
Loading

0 comments on commit c64b343

Please sign in to comment.