How to output the name of the calling method using XCGLogger? - swift5

I have created a MyLogger class and it passes parameters to XCGLogger to output logs. I have specified true for the XCGLogger's showFileName and dateshowFunctionName, but it always outputs MyLogger's own file name and method name. Is there any way to make it output the caller's file name and method name?
This is the output now.
2022/06/17,11:44:54.054 [Debug] [MyLogger.swift] logWrite(level:message:) > New user is comming.
2022/06/17,11:44:54.054 [Warning] [MyLogger.swift] logWrite(level:message:) > Invaild login_name or password. [login_name:marverick] failed 1 times.
2022/06/17,11:44:58.058 [Info] [MyLogger.swift] logWrite(level:message:) > Login success. Hello maverick!
What I want is this.
2022/06/17,11:44:54.054 [Debug] [Login.swift] top() > New user is comming.
2022/06/17,11:44:54.054 [Warning] [Auth.swift] authCheck(login_name:password:) > Invaild login_name or password. [login_name:marverick] failed 1 times.
2022/06/17,11:44:58.058 [Info] [Main.swift] main(param:) > Login success. Hello maverick!
My code is this. It seems a bit long, but I don't know what is causing the problem so I haven't abbreviated much.
import Foundation
import SSZipArchive
import XCGLogger
publc class MyLogger: NSObject, URLSessionTaskDelegate {
static public func logWrite(level: LOGLEVEL, message:String) {
let logSetUp = settings.logger()
let fileName = createFileName()
let log = XCGLogger.default
do {
let folderName = FileManager.default.urls(for:.libraryDirectory,in:.userDomainMask).first?.appendingPathComponent("Logs",isDirectory:true)
try FileManager.default.createDirectory(at: folderName!, withIntermediateDirectories: true, attributes: nil)
}
catch{
MyLogger.logWrite(level: .ERROR, message: error.localizedDescription)
}
let path = FileManager.default.urls(for: .libraryDirectory,in: .userDomainMask).first?.appendingPathComponent("Logs", isDirectory: false).appendingPathComponent(fileName + ".log")
log.dateFormatter?.dateFormat = logSetUp.dateformat
let autorotateFileDestination = AutoRotatingFileDestination(writeToFile: path, identifier: log.identifier, shouldAppend: logSetUp.shouldappend, maxTimeInterval: logSetUp.targetmaxtimeinterval, targetMaxLogFiles: logSetUp.targetmaxlogfiles)
autorotateFileDestination.showLogIdentifier = logSetUp.showlogidentifier
autorotateFileDestination.showFunctionName = logSetUp.showfunctionname
autorotateFileDestination.showThreadName = logSetUp.showthreadname
autorotateFileDestination.showLevel = logSetUp.showlevel
autorotateFileDestination.showFileName = logSetUp.showfilenames
autorotateFileDestination.showLineNumber = logSetUp.showlinenumbers
autorotateFileDestination.showDate = logSetUp.showdata
log.add(destination: autorotateFileDestination)
switch level {
case .DEBUG:
log.debug(message)
case .INFO:
log.info(message)
case .WARNING:
log.warning(message)
case .ERROR:
log.error(message)
}
}
//other methods here
}

I did not get an answer, but this code gave me the expected results. I got the caller's information from #file, #line, and #function
parameters and added it as a string.
import Foundation
import SSZipArchive
import XCGLogger
publc class MyLogger: NSObject, URLSessionTaskDelegate {
static public func logWrite(level: LOGLEVEL, message:String, file: String = #file, line: Int = #line, function: String = #function) {
//
// no change here
//
let callerFileName :String = String(file.split(separator: "/").last ?? "")
let customMessage = "[\(callerFileName):\(line)] \(function) >> \(message)"
switch level {
case .DEBUG:
log.debug(customMessage)
case .INFO:
log.info(customMessage)
case .WARNING:
log.warning(customMessage)
case .ERROR:
log.error(customMessage)
}
}
//other methods here
}

Related

The test runner exited with code 9 before finishing running tests

What does code 9 signify ( and how to fix the issue ) when Xcode crashes during the execution of WatchOS tests?
Scenario:
I have an XCTestCase that reads around 100 CSV test resource files. These files are comma-delimited, have approximately 6,000 lines, and have an average size of 64K. During my test case, I read these files into memory, and one my one, I verify that the input file ( after some processing ) matches the output file. Here is some code to demonstrate the flow:
import XCTest
#testable import MyWatch_Extension
class MyTest: XCTestCase {
func testMyAlgo() throws {
let testingData = discoverAvailableTestFiles(filter: "dataset_1");
XCTAssertGreaterThan(testingData.count, 0, "have data to process")
for (_, testingEntry) in testingData.enumerated() {
var input : [Double] = [];
var expectations : [Double] = [];
readInputOutputTestData(entries: &input, fileName: testingEntry + "_input");
readInputOutputTestData(entries: &expectations, fileName: testingEntry + "_expected_output");
// do something with the input, and store it into results
let results = MyAglo().doSomething();
compareResultsAndExpectations(testingEntry: testingEntry, results: results, expectations: expectations);
}
}
func discoverAvailableTestFiles(filter: String) -> Set<String> {
let bundle = Bundle(for: type(of: self))
let paths = bundle.paths(forResourcesOfType: "csv", inDirectory: nil);
var results = Set<String>()
for path in paths {
if (path.contains(filter)) {
let fileNameSubstring = path[path.index(path.lastIndex(of: "/")!, offsetBy: 1)...]
let qaFileName = fileNameSubstring[...fileNameSubstring.index(fileNameSubstring.lastIndex(of: "_")!, offsetBy: -1)]
results.insert(String(qaFileName))
}
}
return results;
}
func readInputOutputTestData(entries : inout [Double], fileName : String) {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: fileName, ofType: "csv")!
do {
let data = try String(contentsOfFile: path, encoding: .utf8)
let myStrings = data.components(separatedBy: .newlines);
for idx in 0..<myStrings.count {
let parts = myStrings[idx].split(separator: ",");
if (parts.count > 0) {
for part in parts {
entries.append((part as NSString).doubleValue);
}
}
}
} catch {
print(error)
}
}
func compareResultsAndExpectations(testingEntry: String, results: [Double], expectations: [Double]) {
print("## testing \(testingEntry)");
XCTAssertEqual(results.count, expectations.count / 3, "mismatch in data count \(testingEntry)")
var counter = 0;
for idx in stride(from: 0, to: expectations.count, by: 3) {
XCTAssertEqual(results[counter], expectations[idx], accuracy: 0.5, "\(idx + 1) value mismatch")
counter += 1;
}
}
}
When I execute the testMyAlgo testcase, I might read the first 20 files, and I get the error message :
The test runner exited with code 9 before finishing running tests.
If I run each file individually or in smaller batches ( maybe only 20 of them, as opposed to the entire loop of 100 ), everything is fine. This leads me to believe that I am exhausting the memory space of the watch or should be executing the test case differently. Any idea what the problem is, or perhaps, how I should re-structure the test case to get away from this error? ( Maybe free resources or something similar before each test case )
You can create a dumb Xcode project that runs on an iPhone and copy/paste only this test to it. If it runs OK, that means you're exceeding the limits on the watch. If that's the case, you can create a framework with your algorithm and run the tests on the framework, and import the framework on your watch extension

Swift Command Line Tool utilizing Process() and multithreading crashes after a certain number of execution rounds (~3148)

I implemented a password generator script in Swift which utilizes Process() to execute Mac OS X command line tasks. The passwords themselves are just random Strings which then are encrypted (bcrypt) by the command line task as follows:
/usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
Also multiple threads are used to generate passwords with their hashes in parallel.
Note: Both the multithreading and the command line task (compared to serveral other native Swift libraries I tried) improved performance in terms of execution time drastically.
The Problem
The Programm runs fine for the first ~3148 rounds and always crashes around this number (problably correlated to the number of threads running).
For example, if I configure 2000 passwords the code executes as expected terminates without any errors.
Common Error Messages
Setting a breakpoint in Process+Pipe.swift in the catch block of the execute(...) function at BREAKPOINT_1 results in:
Thread 1: signal SIGCHLD
po error.localizedDescription
"The operation couldn\\U2019t be completed. (NSPOSIXErrorDomain error 9 - Bad file descriptor)"
When uncommenting the four //return self.hash(string, cost: cost) code snippets to ignore the error the following errors finally crash the execution (again in execute(...), but not necessarily in the catch block):
Program stops ...
Thread 32: EXC_BAD_ACCESS (code=2, address=0x700003e6bfd4)
... on manual continue ...
Thread 2: EXC_BAD_ACCESS (code=2, address=0x700007e85fd4)
po process
error: Trying to put the stack in unreadable memory at: 0x700003e6bf40.
Code
The relevant code componets are the Main.swift which initialized and starts (and later stops) the PasswordGenerator and then loops n times to get passwords via nextPassword() from the PasswordGenerator. The PasswordGenerator itself utilized execute(...) from the Process extension to run commandline tasks which generate the hash.
Main.swift
class Main {
private static func generate(...) {
...
PasswordGenerator.start()
for _ in 0..<n {
let nextPassword = PasswordGenerator.nextPassword()
let readablePassword = nextPassword.readable
let password = nextPassword.hash
...
}
PasswordGenerator.stop()
...
}
}
PasswordGenerator.swift
The PasswordGenerator runs multiple Threads in parallel.
nextPassword() tries to get a password (if there is one in the passwords Array) or else waits for 100 seconds.
struct PasswordGenerator {
typealias Password = (readable: String, hash: String)
private static let semaphore = DispatchSemaphore(value: 1)
private static var active = false
private static var passwords: [Password] = []
static func nextPassword() -> Password {
self.semaphore.wait()
if let password = self.passwords.popLast() {
self.semaphore.signal()
return password
} else {
self.semaphore.signal()
sleep(100)
return self.nextPassword()
}
}
static func start(
numberOfWorkers: UInt = 32,
passwordLength: UInt = 10,
cost: UInt = 10
) {
self.active = true
for id in 0..<numberOfWorkers {
self.runWorker(id: id, passwordLength: passwordLength, cost: cost)
}
}
static func stop() {
self.semaphore.wait()
self.active = false
self.semaphore.signal()
}
private static func runWorker(
id: UInt,
passwordLength: UInt = 10,
cost: UInt = 10
) {
DispatchQueue.global().async {
var active = true
repeat {
// Update active.
self.semaphore.wait()
active = self.active
print("numberOfPasswords: \(self.passwords.count)")
self.semaphore.signal()
// Generate Password.
// Important: The bycrypt(cost: ...) step must be done outside the Semaphore!
let readable = String.random(length: Int(passwordLength))
let password = Password(readable: readable, hash: Encryption.hash(readable, cost: cost))
// Add Password.
self.semaphore.wait()
self.passwords.append(password)
self.semaphore.signal()
} while active
}
}
}
Encryption.swift
struct Encryption {
static func hash(_ string: String, cost: UInt = 10) -> String {
// /usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
let command = "/usr/sbin/htpasswd"
let arguments: [String] = "-bnBC \(cost) '' \(string)".split(separator: " ").map(String.init)
let result1 = Process.execute(
command: command,//"/usr/sbin/htpasswd",
arguments: arguments//["-bnBC", "\(cost)", "''", string]
)
let errorString1 = String(
data: result1?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),
encoding: String.Encoding.utf8
) ?? ""
guard errorString1.isEmpty else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed with error: \(errorString1)")
}
guard let output1 = result1?.output else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed! No output.")
}
let command2 = "/usr/bin/tr"
let arguments2: [String] = "-d ':\n'".split(separator: " ").map(String.init)
let result2 = Process.execute(
command: command2,
arguments: arguments2,
standardInput: output1
)
let errorString2 = String(
data: result2?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),
encoding: String.Encoding.utf8
) ?? ""
guard errorString2.isEmpty else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed with error: \(errorString2)")
}
guard let output2 = result2?.output else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed! No output.")
}
guard
let hash = String(
data: output2.fileHandleForReading.readDataToEndOfFile(),
encoding: String.Encoding.utf8
)?.replacingOccurrences(of: "$2y$", with: "$2a$")
else {
fatalError("Hash: String replacement failed!")
}
return hash
}
}
Process+Pipe.swift
extension Process {
static func execute(
command: String,
arguments: [String] = [],
standardInput: Any? = nil
) -> (output: Pipe?, error: Pipe?)? {
let process = Process()
process.executableURL = URL(fileURLWithPath: command)
process.arguments = arguments
let outputPipe = Pipe()
let errorPipe = Pipe()
process.standardOutput = outputPipe
process.standardError = errorPipe
if let standardInput = standardInput {
process.standardInput = standardInput
}
do {
try process.run()
} catch {
print(error.localizedDescription)
// BREAKPOINT_1
return nil
}
process.waitUntilExit()
return (output: outputPipe, error: errorPipe)
}
}
Question(s)
Why does the program crash?
Why does it not crash for also huge numbers like 2000 passwords?
Is the multithreading implemented correct?
Is there a problem in the execute(...) code?
I have found a fix while researching this bug. It seems that, despite what the documentation claims, Pipe will not automatically close its reading filehandle.
So if you add a try outputPipe.fileHandleForReading.close() after reading from it, that will fix the issue.
It seems this is a Swift bug. I did some testing and could reproduce it by just running a lot of Process.run(). Filed an issue against Swift:
https://bugs.swift.org/browse/SR-15522

In Swift, how do you get the fully-qualified name of a variable?

Consider this code in an app called 'MyApp'...
class Foo{
class Laa{
static let laaVar = "I am laaVar"
}
}
I know I can get the fully-qualified name of Laa, like so...
let laaName = String(reflecting: Foo.Laa.self)
// Returns 'MyApp.Foo.Laa'
but how can I get the fully-qualified name of laaVar (e.g. "MyApp.Foo.Laa.laaVar")?
Is that even possible?
Bonus Question
Given the above code, and a variable containing the string "MyApp.Foo.Laa.laaVar", how can I get the value "I am laaVar"?
I'm guessing the answer to both has something to do with reflection/mirroring.
I think I found a solution for your question. You might use the Objective-c runtime, especially the function class_copyPropertyList which describes the properties declared by a class. Example:
import Foundation
class Foo {
class Laa: NSObject {
#objc static let laaName = "I am laaVar"
}
}
var count: CUnsignedInt = 0
let methods = class_copyPropertyList(object_getClass(Foo.Laa.self), &count)!
for i in 0 ..< count {
let selector = property_getName(methods.advanced(by: Int(i)).pointee)
if let key = String(cString: selector, encoding: .utf8) {
let res = Foo.Laa.value(forKey: key)
print("name: \(key), value: \(res ?? "")")
}
}
the result is:
name: laaName, value: I am laaVar

How to get the string representation of a nested class

In my reflection library EVReflection I have the following problem when class definitions are nested (class within a class). Below is a worked out case which can be found as a unit test here and The location in the library itself where the code needs to change is Here
I need to get the Internal Swift string representation of a nested
class for a property which is an array of that nested class.
Below you can see a unit test where I am able to get the correct type for the property company that is an other object. It will output _TtCC22EVReflection_iOS_Tests13TestIssue114b10Company114 instead of Company114
When I try the same for the friends property my goal is that it outputs something like: Swift.Array<_TtCC22EVReflection_iOS_Tests13TestIssue114b7User114>
What do I have to do to get that?
As you can see in the test I have various assignments to the value valueType. None of these assignments work. I am only able to get an Array<User114> or an Swift._EmptyArrayStorage.
As you also can see in the test is that if I set a breakpoint and do a po in the output window I am able to get the correct output. So what code will accomplish the same in my code?
class TestIssue114b: XCTestCase {
class User114: EVObject {
var company: Company114 = Company114()
var friends: [User114] = []
}
class Company114: EVObject {
var name: String = ""
var address: String?
}
func testIssueNestedObjects() {
let x = User114()
print("type 1 = \(NSStringFromClass(type(of: x.company)))") // output = type 2 = _TtCC22EVReflection_iOS_Tests13TestIssue114b10Company114
print("type 2 = \(testIssueNestedObjects(x.friends))")
}
func testIssueNestedObjects(_ theValue: Any) -> String {
var valueType = ""
let mi = Mirror(reflecting: theValue)
valueType = NSStringFromClass(type(of: (theValue as! [NSObject]).getTypeInstance() as NSObject)) // NSObject
valueType = "\(type(of: theValue))" // Array<User114>
valueType = "\(mi.subjectType)" // Array<User114>
valueType = ObjectIdentifier(mi.subjectType).debugDescription //"ObjectIdentifier(0x0000000118b4a0d8)"
valueType = (theValue as AnyObject).debugDescription // <Swift._EmptyArrayStorage 0x10d860b50>
valueType = NSStringFromClass(type(of: theValue as AnyObject)) // Swift._EmptyArrayStorage
// set breakpont en enter this in output window: (lldb) po type(of: theValue)
// Ouput will be: Swift.Array<EVReflection_iOS_Tests.TestIssue114b.User114>
return valueType
}
}
Background info:
Actually the end goal is that I have to be able to create instances of the object that I can add to the array. Since the array property is only available as a result from a Mirror command the variable will be of type Any. I do have an extension for arrays in place that will return a new array element. however I am only able to get that when the Any is casted to Array<NSObject> and because of that my extension will return an NSObject. So I would like to get a string like Swift.Array<_TtCC22EVReflection_iOS_Tests13TestIssue114b7User114> I can then get the parts between <> and then create an instance for that using NSClassFromString.
String(reflecting: type(of: theValue))
update by Edwin Vermeer:
For the required conversion to the internal string representation i now have the following function (still in draft)
public class func convertToInternalSwiftRepresentation(type: String) -> String {
if type.components(separatedBy: "<").count > 1 {
// Remove the Array or Set prefix
let prefix = type.components(separatedBy: "<") [0] + "<"
var subtype = type.substring(from: prefix.endIndex)
subtype = subtype.substring(to: subtype.characters.index(before: subtype.endIndex))
return prefix + convertToInternalSwiftRepresentation(type: subtype) + ">"
}
if type.contains(".") {
var parts = type.components(separatedBy: ".")
if parts.count == 2 {
return parts[1]
}
let c = String(repeating:"C", count: parts.count - 1)
var rv = "_Tt\(c)\(parts[0].characters.count)\(parts[0])"
parts.remove(at: 0)
for part in parts {
rv = "\(rv)\(part.characters.count)\(part)"
}
return rv
}
return type
}

String concatenation issue with Swift

I'm attempting to use string concatenation to create a URL for a network call. However, I'm getting intermittent results when trying to concatenate strings.
Attached image shows it concatenates to just "h". Relevant code added below.
Am I missing something?
Thank you.
/* Constants */
private let baseURL = "https://someurl.com/"
private let URLSuffix = "anotherString"
private let typeItem = "item/"
class HITNetworkCoordinator: NSObject {
class var sharedInstance : HITNetworkCoordinator {
return _HITNetworkCoordinatorInstance
}
func downloadItem (itemID: Int) {
let taskURLString = baseURL + typeItem + String(itemID) + URLSuffix
let taskURL = NSURL.URLWithString(taskURLString)
let task = NSURLSession.sharedSession().dataTaskWithURL(taskURL, completionHandler: { (data, response, error) -> Void in
var responseDict : NSDictionary? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? NSDictionary
println(responseDict)
})
task.resume()
}
Try this - let taskURLString = "(baseURL) (typeItem) + String(itemID) + (URLSuffix) " and see what happens
String concatenation seems to get flakier as you increase the number of concat operators (+) in an expression. I gave up a while ago, and started using evaluators instead. E.g.
let taskURLString = "\(baseURL)\(typeItem)\(itemID)\(URLSuffix)"