Skip to main content

Logging

Setting the Log Level

SDK Logging is started automatically when the MasterController is initialized. The default log level is KSMLogInfo or INFO. To get a more detailed log during debugging, it can sometimes be helpful to switch it to KSMLogTrace or TRACE, but this is way to verbose to use it by default. Also note that the choice of log level can have an influence on timing, so if you are e.g. investigating a bug related to a race condition, it is possible that the problem will not occur when switching to a different log level.

⚠️ IMPORTANT: Do NOT use TRACE log level in production/release applications! Otherwise you risk confidential data being written to disk. Even though the logs are encrypted we strongly advice against using TRACE level outside of testing/debugging!

We suggest to not use the log levels that are less verbose than INFO as those do give only information on errors when they actually happen, but in practice it turns out that they almost never provide sufficient context to diagnose the reason for an error.

iOS/Swift

Under iOS, choosing a different log level may look as follows:

Setting the log level (Swift)
var logSink: KSMLogSink?

func setLogLevel(logLevel: KSMLogLevel) {
if logSink == nil {
logSink = KSMasterControllerFactory.getLogSink()
}
logSink?.setSeverityLevel(logLevel)
}

Android/Kotlin

For Android, we suggest setting the log level in the onCreate of your Application Class, as shown in this code snippet:

Setting the log level (Kotlin)
import com.kobil.wrapper.logger.Log
...
...
override fun onCreate() {
super.onCreate()
Log.setSeverity(Severity.TRACE)
}

Exporting Logs

The logs are written to the app's DocumentsDirectory on iOS, while on Android the app's cache dir (for example /data/data/com.kobil.mcwmpgettingstarted/cache/logs_mPower/) is used. Those directories are not accessible in release builds (at least not easily) so you will want to export the logs to an accessible dir.

iOS/Swift

For iOS, we suggest to either enable sharing the data with your PC via the Info.plist with an entry such as

<key>UIFileSharingEnabled</key>
<true/>

or to share the files via Email or the Filesharing mechanism. Anyway, in both these cases we recommend to save the files as zip before sharing. This can be done by the following snippets:

Sharing Log files via Email (Swift)
extension BaseViewController: MFMailComposeViewControllerDelegate {
func sendMail() {
/*
Get logs file
*/
guard let zippedLogsLocation = zipLogs() else {
return
}
/*
convert the zipLogs folder location into url
*/
let url = URL(fileURLWithPath: zippedLogsLocation)
/*
Check mail services enable on your device or not
*/
if !MFMailComposeViewController.canSendMail() {
print("Mail services are not available")
self.showAlert(title: "Mail Service", message: "Mail services are not available")
return
}
/*
Create instance for mailComposer
*/
let composeVC = MFMailComposeViewController()
/*
confirm MailComposer delegate
*/
composeVC.mailComposeDelegate = self

/*
Configure the fields of the interface.
*/
composeVC.setToRecipients([self.globalConst.appConfiguration?.logFileReceiverMailAddress?.iOS?.first ?? ""])
composeVC.setSubject("Ast SDK logs file!")
composeVC.setMessageBody("Please look into the attached log files.", isHTML: false)

/*
convert the logs file content into data
*/

guard let data = NSData(contentsOf: url) else {
return
}
/*
Add logs data as an attachment file.
*/
composeVC.addAttachmentData(data as Data, mimeType: "application/zip", fileName: "exportLogs.zip")

/*
Present the view controller modally.
*/

self.present(composeVC, animated: true, completion: nil)


}

func mailComposeController(_ didFinishWithcontroller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
/*
Dismiss mail composer
*/
self.dismiss(animated: true, completion: nil)
}
}
Sharing Log files via Filesharing (Swift)
extension BaseViewController {
func exportLogs() {
guard let zippedLogsLocation = zipLogs() else {
return
}
let url = URL(fileURLWithPath: zippedLogsLocation)
let documentInteractor = UIDocumentInteractionController()
documentInteractor.url = url
documentInteractor.delegate = self
DispatchQueue.main.async {
documentInteractor.presentPreview(animated: true)
}
}
}
Saving Log files as zip (Swift)
extension BaseViewController {
func zipLogs() -> String? {
var logFiles: [String] = []

guard let logFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.relativePath else {
return nil
}
let fileMgr = FileManager.default
fileMgr.changeCurrentDirectoryPath(logFolder)
let enumerator = fileMgr.enumerator(atPath: logFolder)
while ((enumerator?.nextObject() as? String) != nil) {
if let entry = enumerator?.nextObject() as? String {
if fileMgr.fileExists(atPath: entry){
if entry.contains("logs_mPower"){
logFiles.append(entry)
}
}
}
}
guard let documentFolder = documentsFolder()else {
return nil
}
let zippedFile = URL(fileURLWithPath: documentFolder).appendingPathComponent("exportedLogs_MCWMP.zip").path

SSZipArchive.createZipFile(atPath: zippedFile, withFilesAtPaths: logFiles)

return zippedFile
}

func documentsFolder() -> String? {
let documentsFolderPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.relativePath
return documentsFolderPath
}
}

Android/Kotlin

On Android the SDK will try to export the logs to your app's externalFilesDir, from there they can be easily retrieved through any file explorer (for example the Device Explorer in Android Studio). If externalFilesDir is not writable for some reason the SDK will try to write the logs to your app's filesDir and finally to the cacheDir. The logs will always be in a folder called logs_mPower. If you want to add some logic in your app to copy/export the logs or share them via e-mail be sure to check the following paths for a folder called logs_mPower in the giving order: getExternalFilesDir() > getFilesDir() > getCacheDir().

For sending the logs via mail we recommend zipping them first because they might consist of multiple files.

Below is an example how one could copy the SDK logs from cache dir to external storage.

Exporting logs to local storage (Kotlin)
/***
* This function will copy SDK logs files under [/data/data/com.companyname.appname/cache/logs_mPower/]
* into [/sdcard/Android/data/com.companyname.appname/files/...]
* If the target folder exists, contents will be deleted before copy process
*/
@JvmStatic
suspend fun exportLogsToExternalStorage(
context: Context,
onLogsReady: (filesCopied: Boolean, destinationFile: File?) -> Unit
) = withContext(Dispatchers.IO) {
val sourceLocation =
File(context.cacheDir.path + File.separator + "logs_mPower" + File.separator)
val destinationFolderName = AppConstants.APP_NAME

val targetLocation =
File(
getExternalFilesDirs(
context.applicationContext,
null
)[0].path + File.separator + destinationFolderName
)

if (targetLocation.exists()) {
targetLocation.listFiles()?.forEach { if (it.exists()) it.delete() }
}

try {
if (sourceLocation.exists()) {
val result = sourceLocation.copyRecursively(
target = targetLocation,
overwrite = true,
onError = { file, exception ->
Log.e(TAG, "exportLogsToExternalStorage -> File = [${file}]\n-> Exception = [${exception.message}]")
SKIP
})
onLogsReady.invoke(result, targetLocation)
} else {
Log.e(TAG, "exportLogsToExternalStorage -> Copy file failed. sourceLocation [${sourceLocation.path}] doesn't exists. sourceLocation = [$sourceLocation]")
onLogsReady.invoke(false, null)
}
} catch (e: Exception) {
Log.e(TAG, "exportLogsToExternalStorage -> Copy file failed : " + e.message)
onLogsReady.invoke(false, null)
}
}

// example use of the above function
fun onExportLogsBtnClick(view: View) {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
LogsManager.exportLogsToExternalStorage(application) { filesCopied, destinationFile ->
Log.d(tag, "onExportLogsBtnClick -> Operation was ${if (filesCopied) "successful" else "unsuccessful"}",)
}
}
}