Skip to content

Commit

Permalink
✨ Add log viewing to debugger
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaatttt committed Oct 26, 2023
1 parent 321489b commit 36c9c05
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Sources/AppcuesKit/Appcues+Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public extension Appcues {

var urlSession: URLSession = NetworkClient.defaultURLSession

var logger: OSLog = .disabled
var logger: Logging = OSLog.disabled

var anonymousIDFactory: () -> String = {
UIDevice.identifier
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// OSLog+Convenience.swift
// Logging.swift
// AppcuesKit
//
// Created by Matt on 2021-10-08.
Expand All @@ -8,13 +8,21 @@

import os.log

internal protocol Logging {
func debug(_ message: StaticString, _ args: CVarArg...)
func info(_ message: StaticString, _ args: CVarArg...)
func log(_ message: StaticString, _ args: CVarArg...)
func error(_ message: StaticString, _ args: CVarArg...)
func fault(_ message: StaticString, _ args: CVarArg...)
}

// Convenience methods to make the logging call site a bit tidier:
// `logger.error("%{private}s", data)`
// `logger.error("%{private}@", data)`
// vs
// `os_log("%{private}s", log: .default, type: .error, data)`
// `os_log("%{private}@", log: .default, type: .error, data)`
//
// This also saves us having to `import os.log` everywhere.
extension OSLog {
extension OSLog: Logging {

/// Create an appcues-specific logger.
convenience init(appcuesCategory category: String) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/AppcuesKit/Data/Models/Activity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import os.log

/// API request body for registering user activity.
internal struct Activity {
let logger: OSLog
let logger: Logging

let requestID = UUID()
var events: [Event]?
Expand All @@ -32,7 +32,7 @@ internal struct Activity {
groupID: String? = nil,
groupUpdate: [String: Any]? = nil,
userSignature: String? = nil,
logger: OSLog = .disabled
logger: Logging = OSLog.disabled
) {
self.accountID = accountID
self.sessionID = sessionID
Expand Down
7 changes: 3 additions & 4 deletions Sources/AppcuesKit/Data/Models/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,22 @@ import os.log

/// API request structure for an activity event.
internal struct Event {
let logger: OSLog
let logger: Logging

let name: String
let timestamp: Date
let attributes: [String: Any]?
let context: [String: Any]?

init(name: String, timestamp: Date = Date(), attributes: [String: Any]? = nil, context: [String: Any]? = nil, logger: OSLog = .disabled) {
init(name: String, timestamp: Date = Date(), attributes: [String: Any]? = nil, context: [String: Any]? = nil, logger: Logging = OSLog.disabled) {
self.name = name
self.timestamp = timestamp
self.attributes = attributes
self.context = context
self.logger = logger

}

init(screen screenTitle: String, attributes: [String: Any]? = nil, context: [String: Any]? = nil, logger: OSLog = .disabled) {
init(screen screenTitle: String, attributes: [String: Any]? = nil, context: [String: Any]? = nil, logger: Logging = OSLog.disabled) {
name = "appcues:screen_view"
timestamp = Date()

Expand Down
4 changes: 2 additions & 2 deletions Sources/AppcuesKit/Data/Networking/DynamicCodingKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal struct DynamicCodingKeys: CodingKey {
extension KeyedEncodingContainer where K == DynamicCodingKeys {

/// Encodes the given dictionary to primitive types permitted by the Appcues API, skipping invalid types.
mutating func encodeSkippingInvalid(_ dict: [String: Any]?, logger: OSLog = .disabled) throws {
mutating func encodeSkippingInvalid(_ dict: [String: Any]?, logger: Logging = OSLog.disabled) throws {
var encodingErrorKeys: [String] = []

try dict?.forEach { key, value in
Expand Down Expand Up @@ -76,7 +76,7 @@ extension KeyedEncodingContainer where K == DynamicCodingKeys {
These keys have been omitted. Only String, Number, Date, URL and Bool types allowed.
""",
self.codingPath.pretty,
encodingErrorKeys.description
encodingErrorKeys.sorted().description
)
}
}
Expand Down
95 changes: 95 additions & 0 deletions Sources/AppcuesKit/Presentation/Debugger/DebugLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// DebugLogger.swift
// AppcuesKit
//
// Created by Matt on 2023-10-25.
// Copyright © 2023 Appcues. All rights reserved.
//

import SwiftUI

@available(iOS 13.0, *)
internal class DebugLogger: ObservableObject, Logging {
let previousLogger: Logging?

@Published var log: [Log] = []

init(previousLogger: Logging?) {
self.previousLogger = previousLogger
}

func debug(_ message: StaticString, _ args: CVarArg...) {
previousLogger?.debug(message, args)
log(message, type: .debug, args)
}

func info(_ message: StaticString, _ args: CVarArg...) {
previousLogger?.info(message, args)
log(message, type: .info, args)
}

func log(_ message: StaticString, _ args: CVarArg...) {
previousLogger?.log(message, args)
log(message, type: .log, args)
}

func error(_ message: StaticString, _ args: CVarArg...) {
previousLogger?.error(message, args)
log(message, type: .error, args)
}

func fault(_ message: StaticString, _ args: CVarArg...) {
previousLogger?.fault(message, args)
log(message, type: .fault, args)
}

private func log(_ message: StaticString, type: Level, _ args: [CVarArg]) {
guard Thread.isMainThread else {
DispatchQueue.main.async { self.log(message, type: type, args) }
return
}

// Convert the os_log StaticString to a normal format String
let normalizedMessage = message.description
.replacingOccurrences(of: "{public}", with: "")
.replacingOccurrences(of: "{private}", with: "")
let item = Log(
level: type,
message: String(format: normalizedMessage, args)
)

log.append(item)
}
}

@available(iOS 13.0, *)
extension DebugLogger {
struct Log: Identifiable {
let id = UUID()
let timestamp = Date()
let level: Level
let message: String
}

enum Level: CaseIterable {
case debug, info, log, error, fault

var description: String {
switch self {
case .debug: return "Debug"
case .info: return "Info"
case .log: return "Log"
case .error: return "Error"
case .fault: return "Fault"
}
}

var color: Color {
switch self {
case .debug, .info: return .secondary
case .log: return .primary
case .error, .fault: return .red
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ internal class DebugViewController: UIViewController {
let mode: DebugMode

let viewModel: DebugViewModel
let logger: DebugLogger
let apiVerifier: APIVerifier
let deepLinkVerifier: DeepLinkVerifier

init(viewModel: DebugViewModel, apiVerifier: APIVerifier, deepLinkVerifier: DeepLinkVerifier, mode: DebugMode) {
init(viewModel: DebugViewModel, logger: DebugLogger, apiVerifier: APIVerifier, deepLinkVerifier: DeepLinkVerifier, mode: DebugMode) {
self.viewModel = viewModel
self.logger = logger
self.apiVerifier = apiVerifier
self.deepLinkVerifier = deepLinkVerifier
self.mode = mode
Expand All @@ -53,7 +55,7 @@ internal class DebugViewController: UIViewController {
apiVerifier: apiVerifier,
deepLinkVerifier: deepLinkVerifier,
viewModel: viewModel
))
).environmentObject(logger))
addChild(panelViewController)
debugView.panelWrapperView.addSubview(panelViewController.view)
panelViewController.didMove(toParent: self)
Expand Down
58 changes: 58 additions & 0 deletions Sources/AppcuesKit/Presentation/Debugger/Panel/DebugLogUI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// DebugLogUI.swift
// AppcuesKit
//
// Created by Matt on 2023-10-25.
// Copyright © 2023 Appcues. All rights reserved.
//

import SwiftUI

@available(iOS 13.0, *)
internal enum DebugLogUI {
struct LoggerView: View {
@EnvironmentObject var logger: DebugLogger

var body: some View {
List {
ForEach(logger.log.suffix(20).reversed()) { log in
NavigationLink(destination: DetailView(log: log)) {
VStack(alignment: .leading) {
Text("\(log.level.description): \(log.timestamp.description)")
.fontWeight(.bold)
Text(log.message)
.lineLimit(10)
}
.font(.system(size: 14, design: .monospaced))
.foregroundColor(log.level.color)
}
}
}
.navigationBarTitle("", displayMode: .inline)
}
}

private struct DetailView: View {
let log: DebugLogger.Log

var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Level: \(log.level.description)")
.fontWeight(.bold)
Text("Timestamp: \(log.timestamp.description)")
.fontWeight(.bold)
Divider()
Text(log.message)
}
.font(.system(size: 14, design: .monospaced))
.foregroundColor(log.level.color)
.padding()
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing: Button("Copy Log") {
UIPasteboard.general.string = log.message
})
}
}
}
3 changes: 3 additions & 0 deletions Sources/AppcuesKit/Presentation/Debugger/Panel/DebugUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ internal enum DebugUI {
NavigationLink(destination: DebugFontUI.FontListView(), isActive: $viewModel.navigationDestinationIsFonts) {
Text("Available Fonts")
}
NavigationLink(destination: DebugLogUI.LoggerView()) {
Text("Detailed Log")
}
}

Section(header: EventsSectionHeader(selection: $viewModel.filter)) {
Expand Down
9 changes: 9 additions & 0 deletions Sources/AppcuesKit/Presentation/Debugger/UIDebugger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,13 @@ internal class UIDebugger: UIDebugging {
return
}

let debugLogger = DebugLogger(previousLogger: config.logger)
config.logger = debugLogger

analyticsPublisher.register(subscriber: self)
let rootViewController = DebugViewController(
viewModel: viewModel,
logger: debugLogger,
apiVerifier: APIVerifier(networking: networking),
deepLinkVerifier: DeepLinkVerifier(applicationID: config.applicationID),
mode: mode
Expand All @@ -147,6 +151,11 @@ internal class UIDebugger: UIDebugging {
debugWindow?.isHidden = true
debugWindow = nil
cancellable.removeAll()

// Reset the logger back to the way it was
if let oldLogger = (config.logger as? DebugLogger)?.previousLogger {
config.logger = oldLogger
}
}

func showToast(_ toast: DebugToast) {
Expand Down

0 comments on commit 36c9c05

Please sign in to comment.