-
Notifications
You must be signed in to change notification settings - Fork 18
Native crumbs #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Native crumbs #111
Changes from 4 commits
aaf7aa6
1d3d5b1
709c549
3d09e17
d66875a
d8ece9d
f592065
e851ab7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import Foundation | ||
|
|
||
| enum BacktraceBreadcrumbFileError: Error { | ||
| case invalidFormat | ||
| } | ||
|
|
||
| @objc class BacktraceBreadcrumbFile: NSObject { | ||
|
|
||
| private static let minimumQueueFileSizeBytes = 4096 | ||
| private let maximumIndividualBreadcrumbSize: Int | ||
| private let maxQueueFileSizeBytes: Int | ||
| private let queue: Queue<Any> | ||
| private let breadcrumbLogURL: URL | ||
| private let dispatchQueue = DispatchQueue(label: "io.backtrace.BacktraceBreadcrumbFile@\(UUID().uuidString)") | ||
|
|
||
| public init(_ breadcrumbSettings: BacktraceBreadcrumbSettings) throws { | ||
| self.breadcrumbLogURL = try breadcrumbSettings.getBreadcrumbLogPath() | ||
| self.queue = Queue<Any>() | ||
| self.maximumIndividualBreadcrumbSize = breadcrumbSettings.maxIndividualBreadcrumbSizeBytes | ||
| if breadcrumbSettings.maxQueueFileSizeBytes < BacktraceBreadcrumbFile.minimumQueueFileSizeBytes { | ||
| BacktraceLogger.warning("\(breadcrumbSettings.maxQueueFileSizeBytes) is smaller than the minimum of " + | ||
| "\(BacktraceBreadcrumbFile.minimumQueueFileSizeBytes)" + | ||
| ", ignoring value and overriding with minimum.") | ||
| self.maxQueueFileSizeBytes = BacktraceBreadcrumbFile.minimumQueueFileSizeBytes | ||
| } else { | ||
| self.maxQueueFileSizeBytes = breadcrumbSettings.maxQueueFileSizeBytes | ||
| } | ||
|
|
||
| super.init() | ||
| } | ||
|
|
||
| func addBreadcrumb(_ breadcrumb: [String: Any]) -> Bool { | ||
| do { | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Serialize breadcrumb: [String: Any] into Data | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let breadcrumbJsonData = try JSONSerialization.data(withJSONObject: breadcrumb) | ||
| // Serialize Data into a JSON string | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let breadcrumbJsonString = String(data: breadcrumbJsonData, encoding: .utf8) else { | ||
| BacktraceLogger.warning("Error when converting breadcrumb to string") | ||
| return false | ||
| } | ||
| // Calculate the size of the breadcrumb and add it to queue | ||
| let breadcrumbSize = breadcrumbJsonData.count | ||
| // Check if breadcrumb size is larger than the maximum specified | ||
| if breadcrumbSize > maximumIndividualBreadcrumbSize { | ||
| BacktraceLogger.warning( | ||
| "Discarding breadcrumb that was larger than the maximum specified (\(maximumIndividualBreadcrumbSize).") | ||
| return false | ||
| } | ||
| // Store breadcrumb Json String and size in Dictionary [String : Any] | ||
| let queueBreadcrumb = ["breadcrumbJson": breadcrumbJsonString, "size": breadcrumbSize] as [String : Any] | ||
| // Queue breacrumb | ||
| queue.enqueue(queueBreadcrumb) | ||
| // Iterate over the queue from newest to oldest breadcrumb and build an array of encoded strings | ||
| _ = dispatchQueue.sync { | ||
| let queuedBreadcrumbs = queue.allElements() | ||
| var breadcrumbsArray = [String]() | ||
| var size = 0 | ||
| for index in (0..<queue.count).reversed() { | ||
| guard let queueBreadcrumb = queuedBreadcrumbs[index] as? [String: Any] else { | ||
| BacktraceLogger.warning("Error when fetching breacrumbs from queue") | ||
| return false | ||
| } | ||
| guard let breadcrumbSize = queueBreadcrumb["size"] as? Int else { | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| BacktraceLogger.warning("Error when adding breadcrumbSize to array") | ||
| return false | ||
| } | ||
| // Pop last element if size is greater than maxQueueFileSizeBytes | ||
| if size + breadcrumbSize > maxQueueFileSizeBytes && !queue.isEmpty { | ||
| while (index != 0) { | ||
| _ = queue.pop(at: index) | ||
| } | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } else { | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let breadcrumbJsonData = queueBreadcrumb["breadcrumbJson"] as? String else { | ||
| BacktraceLogger.warning("Error when adding breadcrumbJson to array") | ||
| return false | ||
| } | ||
| breadcrumbsArray.append(breadcrumbJsonData) | ||
| size += breadcrumbSize | ||
| } | ||
| } | ||
| // Write breadcrumbs to file | ||
| let breadcrumbString = "[\(breadcrumbsArray.joined(separator: ","))]" | ||
| writeBreadcrumbToLogFile(breadcrumb: breadcrumbString, at: self.breadcrumbLogURL) | ||
| return true | ||
melekr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } catch { | ||
| BacktraceLogger.warning("Error when adding breadcrumb to file: \(error)") | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func clear() -> Bool { | ||
| dispatchQueue.sync { | ||
| queue.clear() | ||
| clearBreadcrumbLogFile(at:self.breadcrumbLogURL) | ||
| } | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| extension BacktraceBreadcrumbFile { | ||
|
|
||
| func writeBreadcrumbToLogFile(breadcrumb: String, at breadcrumbLogURL: URL) { | ||
| do { | ||
| try breadcrumb.write(to: breadcrumbLogURL, atomically: true, encoding: .utf8) | ||
| } catch { | ||
| BacktraceLogger.warning("Error writing breadcrumb to log file at: \(breadcrumbLogURL) - \(error.localizedDescription)") | ||
| } | ||
| } | ||
|
|
||
| func clearBreadcrumbLogFile(at breadcrumbLogURL: URL) { | ||
| do { | ||
| try "".write(to: breadcrumbLogURL, atomically: false, encoding: .utf8) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if someone deletes a file when you're generating breadcrumbs? |
||
| } catch { | ||
| BacktraceLogger.warning("Error clearing breadcrumb log file at: \(breadcrumbLogURL) - \(error.localizedDescription)") | ||
| } | ||
| } | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import Foundation | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it QueueFile? I believe its a generic queue instead. Can we rename the file to Queue or something like that?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Queue it is! |
||
|
|
||
| @objcMembers | ||
| public class Queue<T>: NSObject { | ||
| private var elements: [T] = [] | ||
|
|
||
| func enqueue(_ element: T) { | ||
| elements.append(element) | ||
| } | ||
|
|
||
| func dequeue() -> T? { | ||
| if elements.isEmpty { | ||
| return nil | ||
| } else { | ||
| return elements.removeFirst() | ||
| } | ||
| } | ||
|
|
||
| func peek() -> T? { | ||
| return elements.first | ||
| } | ||
|
|
||
| func remove(at index: Int) -> T? { | ||
| guard index < elements.count else { | ||
| return nil | ||
| } | ||
| return elements.remove(at: index) | ||
| } | ||
|
|
||
| func pop(at index: Int) -> T? { | ||
| guard !elements.isEmpty else { | ||
| return nil | ||
| } | ||
| return remove(at: index) | ||
| } | ||
|
|
||
| func pop() -> T? { | ||
melekr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| guard !elements.isEmpty else { | ||
| return nil | ||
| } | ||
| return elements.popLast() | ||
| } | ||
|
|
||
| public func allElements() -> [T] { | ||
melekr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return elements | ||
| } | ||
|
|
||
| func clear() { | ||
| elements.removeAll() | ||
| } | ||
|
|
||
| var isEmpty: Bool { | ||
| return elements.isEmpty | ||
| } | ||
|
|
||
| var count: Int { | ||
| return elements.count | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check if there's any superfluous
#if os(iOS) || os(OSX)left in prod/test code. I know there were a bunch of 'mThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look :)