Skip to content

Commit

Permalink
initial LiveLogInspector view controller & view
Browse files Browse the repository at this point in the history
  • Loading branch information
Evan Maloney committed Mar 2, 2017
1 parent f8443c9 commit 4d7a00b
Show file tree
Hide file tree
Showing 6 changed files with 936 additions and 11 deletions.
18 changes: 18 additions & 0 deletions CleanroomLogger.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
3B2AFD551E562EEB00C115F7 /* BufferedLogRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD541E562EEB00C115F7 /* BufferedLogRecorder.swift */; };
3B2AFD581E579E9500C115F7 /* KeyedMessageBufferExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD561E579E7100C115F7 /* KeyedMessageBufferExtension.swift */; };
3B2AFD5A1E579EEC00C115F7 /* LoggerTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD591E579EEC00C115F7 /* LoggerTestCase.swift */; };
3B2AFD5F1E57A81400C115F7 /* LiveLogInspectorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD5E1E57A81400C115F7 /* LiveLogInspectorConfiguration.swift */; };
3B2AFD611E59254400C115F7 /* LiveLogInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD601E59254400C115F7 /* LiveLogInspectorView.swift */; };
3B2AFD631E59256D00C115F7 /* LiveLogInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD621E59256D00C115F7 /* LiveLogInspectorViewController.swift */; };
3B2AFD651E5CEA8E00C115F7 /* CallbackRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2AFD641E5CEA8E00C115F7 /* CallbackRegistry.swift */; };
3B475D271DB47B8A0047D397 /* BasicLogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B475D001DB47B8A0047D397 /* BasicLogConfiguration.swift */; };
3B475D281DB47B8A0047D397 /* CallingThreadLogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B475D011DB47B8A0047D397 /* CallingThreadLogFormatter.swift */; };
3B475D291DB47B8A0047D397 /* CallSiteLogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B475D021DB47B8A0047D397 /* CallSiteLogFormatter.swift */; };
Expand Down Expand Up @@ -80,6 +84,10 @@
3B2AFD541E562EEB00C115F7 /* BufferedLogRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferedLogRecorder.swift; sourceTree = "<group>"; };
3B2AFD561E579E7100C115F7 /* KeyedMessageBufferExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KeyedMessageBufferExtension.swift; path = CleanroomLoggerTests/KeyedMessageBufferExtension.swift; sourceTree = "<group>"; };
3B2AFD591E579EEC00C115F7 /* LoggerTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoggerTestCase.swift; path = CleanroomLoggerTests/LoggerTestCase.swift; sourceTree = "<group>"; };
3B2AFD5E1E57A81400C115F7 /* LiveLogInspectorConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLogInspectorConfiguration.swift; sourceTree = "<group>"; };
3B2AFD601E59254400C115F7 /* LiveLogInspectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLogInspectorView.swift; sourceTree = "<group>"; };
3B2AFD621E59256D00C115F7 /* LiveLogInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLogInspectorViewController.swift; sourceTree = "<group>"; };
3B2AFD641E5CEA8E00C115F7 /* CallbackRegistry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallbackRegistry.swift; sourceTree = "<group>"; };
3B475D001DB47B8A0047D397 /* BasicLogConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicLogConfiguration.swift; sourceTree = "<group>"; };
3B475D011DB47B8A0047D397 /* CallingThreadLogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallingThreadLogFormatter.swift; sourceTree = "<group>"; };
3B475D021DB47B8A0047D397 /* CallSiteLogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallSiteLogFormatter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -152,6 +160,7 @@
children = (
3B475D001DB47B8A0047D397 /* BasicLogConfiguration.swift */,
3B2AFD541E562EEB00C115F7 /* BufferedLogRecorder.swift */,
3B2AFD641E5CEA8E00C115F7 /* CallbackRegistry.swift */,
3B475D011DB47B8A0047D397 /* CallingThreadLogFormatter.swift */,
3B475D021DB47B8A0047D397 /* CallSiteLogFormatter.swift */,
3B475D061DB47B8A0047D397 /* ConcatenatingLogFormatter.swift */,
Expand All @@ -160,6 +169,9 @@
3B475D091DB47B8A0047D397 /* FieldBasedLogFormatter.swift */,
3B475D0A1DB47B8A0047D397 /* FileLogRecorder.swift */,
3B475D0B1DB47B8A0047D397 /* LiteralLogFormatter.swift */,
3B2AFD5E1E57A81400C115F7 /* LiveLogInspectorConfiguration.swift */,
3B2AFD601E59254400C115F7 /* LiveLogInspectorView.swift */,
3B2AFD621E59256D00C115F7 /* LiveLogInspectorViewController.swift */,
3B475D0C1DB47B8A0047D397 /* Log.swift */,
3B475D0D1DB47B8A0047D397 /* LogChannel.swift */,
3B475D0E1DB47B8A0047D397 /* LogConfiguration.swift */,
Expand Down Expand Up @@ -375,6 +387,7 @@
3B4EE3561E1BED6D002A92BC /* XcodeTraceLogFormatter.swift in Sources */,
3B17126B1E20A7AB00682E89 /* PayloadValueLogFormatter.swift in Sources */,
3B475D4B1DB47B8A0047D397 /* XcodeLogConfiguration.swift in Sources */,
3B2AFD631E59256D00C115F7 /* LiveLogInspectorViewController.swift in Sources */,
3B1712671E20A50600682E89 /* PayloadTraceLogFormatter.swift in Sources */,
3B4EE3521E1AEF49002A92BC /* OSLogTypeTranslator.swift in Sources */,
3B475D431DB47B8A0047D397 /* RotatingLogFileRecorder.swift in Sources */,
Expand All @@ -399,7 +412,10 @@
3B4EE3601E1C3E84002A92BC /* ProcessIDLogFormatter.swift in Sources */,
3B475D321DB47B8A0047D397 /* LiteralLogFormatter.swift in Sources */,
3B475D3B1DB47B8A0047D397 /* LogRecorderBase.swift in Sources */,
3B2AFD5F1E57A81400C115F7 /* LiveLogInspectorConfiguration.swift in Sources */,
3B2AFD651E5CEA8E00C115F7 /* CallbackRegistry.swift in Sources */,
3B2811491E1D65E100878EC2 /* StandardErrorLogRecorder.swift in Sources */,
3B2AFD611E59254400C115F7 /* LiveLogInspectorView.swift in Sources */,
3B1712691E20A66000682E89 /* PayloadMessageLogFormatter.swift in Sources */,
3B475D461DB47B8A0047D397 /* StandardLogFormatter.swift in Sources */,
3B475D3D1DB47B8A0047D397 /* ParsableLogFormatter.swift in Sources */,
Expand Down Expand Up @@ -535,6 +551,7 @@
DYLIB_CURRENT_VERSION = 27;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
Expand All @@ -549,6 +566,7 @@
DYLIB_CURRENT_VERSION = 27;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
Expand Down
87 changes: 76 additions & 11 deletions Sources/BufferedLogRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ import Dispatch
open class BufferedLogRecorder<BufferItem>: LogRecorderBase
{
/** The maximum number if items that will be stored in the receiver's
buffer */
buffer. */
open let bufferLimit: Int

/** If `true`, the items in the buffer are stored in reverse chronological
order: the first item in the buffer array will be the newest, while the
last item will be the oldest. Otherwise, the array will be ordered from
oldest to newest. */
open let reverseChronological: Bool

/** The function used to create a `BufferItem` given a `LogEntry` and a
formatted message string. */
open let createBufferItem: (LogEntry, String) -> BufferItem
Expand All @@ -39,6 +45,21 @@ open class BufferedLogRecorder<BufferItem>: LogRecorderBase
`LogEntry` values recorded by the receiver. */
open private(set) var buffer: [BufferItem]

private var didRecordItemCallbacks: CallbackRegistry<(_ recorder: BufferedLogRecorder<BufferItem>, _ item: BufferItem, _ didTruncateBuffer: Bool) -> Void>
private var didClearBufferCallbacks: CallbackRegistry<(_ recorder: BufferedLogRecorder<BufferItem>) -> Void>

/** A callback function that gets executed on the main thread once for each
call to `record()`. The caller and the recorded `BufferItem` are passed as
parameters, along with a flag indicating whether the buffer was truncated
due to hitting the `bufferLimit`. When this function is called, the item
will have already been added to the buffer array. */
// open var didRecordBufferItem: (_ recorder: BufferedLogRecorder<BufferItem>, _ item: BufferItem, _ didTruncateBuffer: Bool) -> Void = { _, _, _ in }

/** A callback function that gets executed on the main thread whenever the
buffer is cleared. The caller is passed as the parameter. When this
function is called, the buffer will have already been cleared. */
// open var didClearBuffer: (_ recorder: BufferedLogRecorder<BufferItem>) -> Void = { _ in }

/**
Initializes a new `BufferedLogRecorder`.
Expand All @@ -56,22 +77,48 @@ open class BufferedLogRecorder<BufferItem>: LogRecorderBase
consumption will grow endlessly unless you manually clear the buffer
periodically.
- parameter reverseChronological: If `true`, the items in the buffer will
be stored in reverse chronological order: the first item in the buffer
array will be the newest, while the last item will be the oldest.
Otherwise, the array will be ordered from oldest to newest.
- parameter queue: The `DispatchQueue` to use for the recorder. If `nil`,
a new queue will be created.
- parameter createBufferItem: The function used to create `BufferItem`
instances for each `LogEntry` and formatted message string passed to the
receiver's `record`()` function.
*/
public init(formatters: [LogFormatter], bufferLimit: Int = 10_000, queue: DispatchQueue? = nil, createBufferItem: @escaping (LogEntry, String) -> BufferItem)
public init(formatters: [LogFormatter], bufferLimit: Int = 10_000, reverseChronological: Bool = false, queue: DispatchQueue? = nil, createBufferItem: @escaping (LogEntry, String) -> BufferItem)
{
self.buffer = []
self.bufferLimit = bufferLimit
self.reverseChronological = reverseChronological
self.createBufferItem = createBufferItem

self.didRecordItemCallbacks = CallbackRegistry<(_ recorder: BufferedLogRecorder<BufferItem>, _ item: BufferItem, _ didTruncateBuffer: Bool) -> Void>()
self.didClearBufferCallbacks = CallbackRegistry<(_ recorder: BufferedLogRecorder<BufferItem>) -> Void>()

super.init(formatters: formatters, queue: queue)
}


open func addCallback(didRecordBufferItem: @escaping (_ recorder: BufferedLogRecorder<BufferItem>, _ item: BufferItem, _ didTruncateBuffer: Bool) -> Void)
-> CallbackHandle
{
return didRecordItemCallbacks.addCallback(didRecordBufferItem)
}

open func addCallback(didClearBuffer: @escaping (_ recorder: BufferedLogRecorder<BufferItem>) -> Void)
-> CallbackHandle
{
return didClearBufferCallbacks.addCallback(didClearBuffer)
}

open func removeCallback(handle: CallbackHandle)
{
handle.stopCallbacks()
}

/**
Called by the `LogReceptacle` to record the formatted log message.
Expand All @@ -92,10 +139,24 @@ open class BufferedLogRecorder<BufferItem>: LogRecorderBase
{
let item = createBufferItem(entry, message)

buffer.append(item)
var didTruncate = false
if bufferLimit > 0 && buffer.count + 1 > bufferLimit {
if reverseChronological {
buffer.removeLast()
} else {
buffer.removeFirst()
}
didTruncate = true
}

if reverseChronological {
buffer.insert(item, at: 0)
} else {
buffer.append(item)
}

if bufferLimit > 0 && buffer.count > bufferLimit {
buffer.remove(at: 0)
for callback in didRecordItemCallbacks.callbacks() {
callback(self, item, didTruncate)
}
}

Expand All @@ -107,10 +168,14 @@ open class BufferedLogRecorder<BufferItem>: LogRecorderBase
*/
public func clear()
{
// ensures consistent access to buffer, preventing race conditions
// queue.sync {
self.buffer = []
// }
// ensures consistent access to buffer
queue.sync {
self.buffer.removeAll(keepingCapacity: true)

for callback in didClearBufferCallbacks.callbacks() {
callback(self)
}
}
}
}

Expand Down
85 changes: 85 additions & 0 deletions Sources/CallbackRegistry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// CallbackRegistry.swift
// CleanroomLogger
//
// Created by Evan Maloney on 2/21/17.
// Copyright © 2017 Gilt Groupe. All rights reserved.
//

import Foundation

/**
Represents a callback function that has been registered for a given operation.
The callback function may be called as long as the associated `CallbackHandle`
instance has at least one strong reference to it. Once a `CallbackHandle`
has been deallocated or its `stopCallbacks()` function is called, the
associated callback function will no longer be invoked.
*/
public class CallbackHandle
{
internal var objectID: ObjectIdentifier {
return _objectID
}
private var _objectID: ObjectIdentifier!

private weak var removableFrom: CallbackRemovable?

fileprivate init(removableFrom: CallbackRemovable)
{
self.removableFrom = removableFrom
_objectID = ObjectIdentifier(self)
}

deinit {
stopCallbacks()
}

/**
Prevent further invocations of the callback function represented by the
receiver.
*/
public func stopCallbacks()
{
removableFrom?.removeCallback(handle: self)
removableFrom = nil
}
}

private protocol CallbackRemovable: class
{
func removeCallback(handle: CallbackHandle)
}

internal class CallbackRegistry<CallbackSignature>: CallbackRemovable
{
private let lock = NSLock()
private var objectIdsToCallbacks = [ObjectIdentifier: CallbackSignature]()

func addCallback(_ callback: CallbackSignature)
-> CallbackHandle
{
let handle = CallbackHandle(removableFrom: self)
lock.lock()
objectIdsToCallbacks[handle.objectID] = callback
lock.unlock()
return handle
}

func removeCallback(handle: CallbackHandle)
{
let id = handle.objectID
lock.lock()
objectIdsToCallbacks.removeValue(forKey: id)
lock.unlock()
}

func callbacks()
-> [CallbackSignature]
{
lock.lock()
let callbacks = objectIdsToCallbacks.values
lock.unlock()
return [CallbackSignature](callbacks)
}
}
40 changes: 40 additions & 0 deletions Sources/LiveLogInspectorConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// LiveLogInspectorConfiguration.swift
// CleanroomLogger
//
// Created by Evan Maloney on 2/17/17.
// Copyright © 2017 Gilt Groupe. All rights reserved.
//

#if os(iOS) || os(tvOS)

import Foundation
import UIKit

open class LiveLogInspectorConfiguration: BasicLogConfiguration
{
private let bufferingRecorder: BufferedLogEntryMessageRecorder

open var inspectorViewController: LiveLogInspectorViewController {
let inspector = _inspectorViewController ?? LiveLogInspectorViewController(recorder: bufferingRecorder)
_inspectorViewController = inspector
return inspector
}
private weak var _inspectorViewController: LiveLogInspectorViewController?

public init(minimumSeverity: LogSeverity = .verbose, filters: [LogFilter] = [], synchronousMode: Bool = false)
{
bufferingRecorder = BufferedLogEntryMessageRecorder(formatters: [PayloadLogFormatter()])

super.init(minimumSeverity: minimumSeverity, filters: filters, recorders: [bufferingRecorder], synchronousMode: synchronousMode)
}

open func createInspectorView()
-> LiveLogInspectorView
{
return LiveLogInspectorView(recorder: bufferingRecorder)
}
}

#endif

Loading

0 comments on commit 4d7a00b

Please sign in to comment.