Skip to content

Commit

Permalink
LogInterval with data
Browse files Browse the repository at this point in the history
  • Loading branch information
ikhvorost committed Oct 22, 2024
1 parent 8dacea9 commit d5604e3
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 111 deletions.
14 changes: 10 additions & 4 deletions Sources/DLog/Log.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,9 @@ public class Log: NSObject {

let interval = LogInterval(logger: logger, message: message, staticName: staticName, category: category, config: config, stack: stack(), metadata: metadata.data, location: location)
if let block = closure {
interval.begin()
interval.begin(fileID: location.fileID, file: location.file, function: location.function, line: location.line)
block()
interval.end()
interval.end(fileID: location.fileID, file: location.file, function: location.function, line: location.line)
}
return interval
}
Expand Down Expand Up @@ -366,13 +366,17 @@ extension Log {
"[\(type.title)]"

case .colored:
"[\(type.title)]".color(tag.colors)
" \(type.title) ".color(tag.colors)

case .emoji:
"\(type.icon) [\(type.title)]"
}
}

func data() -> Metadata? {
nil
}

func messageText() -> String {
let tag = LogItem.tags[self.type]!
return switch config.style {
Expand All @@ -391,6 +395,7 @@ extension Log {
var category = "[\(self.category)]"
var location = "<\(self.location.fileName):\(self.location.line)>"
var metadata = self.metadata.json()
var data = data()?.json() ?? ""

switch config.style {
case .plain, .emoji:
Expand All @@ -405,6 +410,7 @@ extension Log {
category = category.color(.textBlue)
location = location.color([.dim, tag.textColor])
metadata = metadata.color(.dim)
data = data.color(.dim)
}

let items: [(LogOptions, String)] = [
Expand All @@ -416,9 +422,9 @@ extension Log {
(.type, typeText()),
(.location, location),
(.metadata, metadata),
(.data, data),
(.message, messageText()),
]

return LogItem.logPrefix(items: items, options: config.options)
}
}
Expand Down
7 changes: 5 additions & 2 deletions Sources/DLog/LogConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,14 @@ public struct LogOptions: OptionSet {
/// Metadata
public static let metadata = Self(7)

// Data
public static let data = Self(8)

/// Compact: `.sign` and `.time`
public static let compact: Self = [.sign, .time]

/// Regular: `.sign`, `.time`, `.category`, `.padding`, `.type`, `.location` and `.metadata`
public static let regular: Self = [.sign, .time, .category, .padding, .type, .location, .metadata]
/// Regular: `.sign`, `.time`, `.category`, `.padding`, `.type`, `.location`, `.metadata` and `.data`
public static let regular: Self = [.sign, .time, .category, .padding, .type, .location, .metadata, .data]
}

/// Style of text to output.
Expand Down
187 changes: 113 additions & 74 deletions Sources/DLog/LogInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import Foundation
import os.log


/// Indicates which info from intervals should be used.
public struct IntervalOptions: OptionSet {
/// The corresponding value of the raw type.
Expand Down Expand Up @@ -72,7 +73,7 @@ public struct IntervalConfig {
}

/// Accumulated interval statistics
public struct IntervalStatistics {
public struct IntervalStats {
/// A number of total calls
public let count: Int

Expand All @@ -89,43 +90,78 @@ public struct IntervalStatistics {
public let average: TimeInterval
}

fileprivate class StatisticsStore {
static let shared = StatisticsStore()
fileprivate class Store {
static let shared = Store()

private var intervals = [Int : IntervalStatistics]()
private var stats = [Int : IntervalStats]()

subscript(id: Int) -> IntervalStatistics {
subscript(id: Int) -> IntervalStats {
get {
synchronized(self) {
if let data = intervals[id] {
return data
}
let data = IntervalStatistics(count: 0, total: 0, min: 0, max: 0, average: 0)
intervals[id] = data
return data
}
stats[id] ?? IntervalStats(count: 0, total: 0, min: 0, max: 0, average: 0)
}

set {
synchronized(self) {
intervals[id] = newValue
}
stats[id] = newValue
}
}

}

/// An object that represents a time interval triggered by the user.
///
/// Interval logs a point of interest in your code as running time statistics for debugging performance.
///
public class LogInterval: LogItem {
private let logger: DLog
public class LogInterval {

public class Item: Log.Item {
public let staticName: StaticString?
public let duration: TimeInterval
public let stats: IntervalStats

init(time: Date, category: String, stack: [Bool]?, type: LogType, location: LogLocation, metadata: Metadata, message: String, config: LogConfig, staticName: StaticString?, duration: TimeInterval, stats: IntervalStats) {
self.staticName = staticName
self.duration = duration
self.stats = stats

super.init(time: time, category: category, stack: stack, type: type, location: location, metadata: metadata, message: message, config: config)
}

override func typeText() -> String {
let text = super.typeText()
return text.replacingOccurrences(of: "INTERVAL", with: "INTERVAL:\(message)")
}

override func data() -> Metadata? {
let items: [(IntervalOptions, String, Any)] = [
(.duration, "duration", stringFromTimeInterval(duration)),
(.count, "count", stats.count),
(.total, "total", stringFromTimeInterval(stats.total)),
(.min, "min", stringFromTimeInterval(stats.min)),
(.max, "max", stringFromTimeInterval(stats.max)),
(.average, "average", stringFromTimeInterval(stats.average)),
]
return Metadata.metadata(from: items, options: config.intervalConfig.options)
}

override func messageText() -> String {
""
}
}

private weak var logger: DLog?

private let category: String
private let config: LogConfig
private let stack: [Bool]?
private let metadata: Metadata
private var location: LogLocation
private let id: Int
private let name: String
@Atomic
private var begun = false
private var start: Date?

let staticName: StaticString?
public let message: String
public let staticName: StaticString?

/// A time duration
public private(set) var duration: TimeInterval = 0

// SignpostID
@Atomic
Expand All @@ -135,21 +171,28 @@ public class LogInterval: LogItem {
get { _signpostID as? OSSignpostID }
}

/// A time duration
@objc
public private(set) var duration: TimeInterval = 0

/// Accumulated interval statistics
public var statistics: IntervalStatistics { StatisticsStore.shared[id] }
public var stats: IntervalStats {
synchronized(Store.shared) {
Store.shared[id]
}
}

init(logger: DLog, name: String, staticName: StaticString?, category: String, config: LogConfig, scope: LogScope?, metadata: Metadata, location: LogLocation) {
init(logger: DLog, message: String, staticName: StaticString?, category: String, config: LogConfig, stack: [Bool]?, metadata: Metadata, location: LogLocation) {
self.logger = logger
self.name = name
self.id = "\(location.file):\(location.function):\(location.line)".hash
self.message = message
self.staticName = staticName
self.category = category
self.config = config
self.stack = stack
self.metadata = metadata
self.location = location

let message = { LogMessage(stringLiteral: name) }
super.init(message: message, type: .interval, category: category, config: config, scope: scope?.item, metadata: {[metadata]}, location: location)
self.id = "\(message):\(location.file):\(location.function):\(location.line)".hash
}

private func item(type: LogType, stats: IntervalStats) -> Item {
Item(time: Date(), category: category, stack: stack, type: type, location: location, metadata: metadata, message: message, config: config, staticName: staticName, duration: duration, stats: stats)
}

/// Start a time interval.
Expand All @@ -163,14 +206,20 @@ public class LogInterval: LogItem {
/// interval.end()
///
@objc
public func begin() {
guard !begun else { return }
begun.toggle()

time = Date()
duration = 0

logger.output?.begin(interval: self)
public func begin(fileID: String = #fileID, file: String = #file, function: String = #function, line: UInt = #line) {
synchronized(Store.shared) {
guard start == nil else {
return
}

start = Date()
duration = 0
location = LogLocation(fileID, file, function, line)

let stats = Store.shared[id]
let item = item(type: .intervalBegin, stats: stats)
logger?.output?.begin(interval: item)
}
}

/// Finish a time interval.
Expand All @@ -184,38 +233,28 @@ public class LogInterval: LogItem {
/// interval.end()
///
@objc
public func end() {
guard begun else { return }
begun.toggle()

duration = -time.timeIntervalSinceNow
time = Date()

// Statistics
let stats = self.statistics
let count = stats.count + 1
let total = stats.total + duration
let newStats = IntervalStatistics(
count: count,
total: total,
min: stats.min == 0 || stats.min > duration ? duration : stats.min,
max: stats.max == 0 || stats.max < duration ? duration : stats.max,
average: total / Double(count))

StatisticsStore.shared[id] = newStats

// Metadata
let items: [(IntervalOptions, String, () -> Any)] = [
(.average, "average", { stringFromTimeInterval(newStats.average) }),
(.count, "count", { newStats.count }),
(.duration, "duration", { stringFromTimeInterval(self.duration) }),
(.max, "max", { stringFromTimeInterval(newStats.max) }),
(.min, "min", { stringFromTimeInterval(newStats.min) }),
(.total, "total", { stringFromTimeInterval(newStats.total) }),
]
let metadata = Metadata.metadata(from: items, options: config.intervalConfig.options)
self.metadata = _metadata() + [metadata]

logger.output?.end(interval: self)
public func end(fileID: String = #fileID, file: String = #file, function: String = #function, line: UInt = #line) {
synchronized(Store.shared) {
guard start != nil else {
return
}
duration = -(start?.timeIntervalSinceNow ?? 0)
location = LogLocation(fileID, file, function, line)

// Stats
let stats = Store.shared[id]
let count = stats.count + 1
let total = stats.total + duration
let newStats = IntervalStats(
count: count,
total: total,
min: stats.min == 0 || stats.min > duration ? duration : stats.min,
max: stats.max == 0 || stats.max < duration ? duration : stats.max,
average: total / Double(count))
Store.shared[id] = newStats

let item = item(type: .intervalEnd, stats: newStats)
logger?.output?.end(interval: item)
}
}
}
11 changes: 7 additions & 4 deletions Sources/DLog/LogItem+Text.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ extension LogType {
.error : "⚠️",
.assert : "🅰️",
.fault : "🆘",
.interval : "🕒",
.intervalBegin : "🕛",
.intervalEnd : "🕑",
.scopeEnter: "⬇️",
.scopeLeave: "⬆️",
]
Expand All @@ -105,7 +106,8 @@ extension LogType {
.error : "ERROR",
.assert : "ASSERT",
.fault : "FAULT",
.interval : "INTERVAL",
.intervalBegin : "INTERVAL",
.intervalEnd : "INTERVAL",
.scopeEnter : "SCOPE",
.scopeLeave : "SCOPE",
]
Expand All @@ -131,7 +133,8 @@ extension LogItem {
.error : Tag(textColor: .textYellow, colors: [.backgroundYellow, .textBlack]),
.fault : Tag(textColor: .textRed, colors: [.backgroundRed, .textWhite, .blink]),
.assert : Tag(textColor: .textRed, colors: [.backgroundRed, .textWhite]),
.interval : Tag(textColor: .textGreen, colors: [.backgroundGreen, .textBlack]),
.intervalBegin : Tag(textColor: .textGreen, colors: [.backgroundGreen, .textBlack]),
.intervalEnd : Tag(textColor: .textGreen, colors: [.backgroundGreen, .textBlack]),
.scopeEnter : Tag(textColor: .textMagenta, colors: [.backgroundMagenta, .textBlack]),
.scopeLeave : Tag(textColor: .textMagenta, colors: [.backgroundMagenta, .textBlack]),
]
Expand All @@ -144,7 +147,7 @@ extension LogItem {

static func logPrefix(items: [(type: LogOptions, text: String)], options: LogOptions) -> String {
items.compactMap {
guard options.contains($0.type) || $0.type == .message else {
guard !$0.text.isEmpty && ($0.type == .message || options.contains($0.type)) else {
return nil
}
return $0.text.trimTrailingWhitespace()
Expand Down
3 changes: 2 additions & 1 deletion Sources/DLog/LogItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ public enum LogType: Int {
case fault

/// The interval log level.
case interval
case intervalBegin
case intervalEnd

case scopeEnter
case scopeLeave
Expand Down
5 changes: 2 additions & 3 deletions Sources/DLog/LogMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public typealias Metadata = [String : Any]

extension Metadata {

static func metadata<Option: OptionSet>(from items: [(Option, String, () -> Any)], options: Option) -> Metadata {
static func metadata<Option: OptionSet>(from items: [(Option, String, Any)], options: Option) -> Metadata {
let keyValues: [(String, Any)] = items
.compactMap { (option: Option, key: String, f:() -> Any) in
.compactMap { (option: Option, key: String, value: Any) in
// Option
assert(option is Option.Element)
guard options.contains(option as! Option.Element) else {
Expand All @@ -41,7 +41,6 @@ extension Metadata {
assert(key.isEmpty == false)

// Value
let value = f()
if let text = value as? String, text.isEmpty {
return nil
}
Expand Down
Loading

0 comments on commit d5604e3

Please sign in to comment.