1

UPDATE FOR THE READERS: the code discussed here is now available in this Codename One library (and it's in the CN1 Extensions Manager too...): https://github.com/jsfan3/CN1Libs-NativeLogsReader


Short question:

I wrote a working native Android code to get the native log of the device during the executing of my Codename One apps. I used the native interface functionality provided by Codename One. I need help to implement this interface also for iOS.

Long question...

First of all, I tried to get native logs (such as the ones provided by Android Studio and XCode) to ask help when I get odd behaviors in my apps... because the standard Codename One logs aren't enough in several cases (for example, I got issues with native components like using the Google Maps CN1Lib).

I'm new to Android Studio and I'm in trouble to get the Logcat of my Codename One apps, I also have serious difficulties to use the native sources provided by the build servers. Moreover, I haven't a Mac, so I cannot use XCode.

My language skills are restricted to the Codename One Java 8, I don't "speak" the Android native Java and I feel that the iOS native Objective-C is unreadable...

That's why, to help myself to provide accurate logs when I need to file some issues in the Github repository of Codename One, I tried to write native code to the get the native logs in a String (that I can manage in several easy ways, such as I can display it in a Form and I can send it by email to myself).

So... I was able to implement the code for Android: it works perfectly. I tested in several Android versions.

CallNativeCode.java

package ...;

import com.codename1.system.NativeInterface;

/**
 * Native Code interface
 */
public interface CallNativeCode extends NativeInterface {

    public void clearAndRestartLog();
    public String readLog();
    public String getFilePath();

}

CallNativeCodeImpl.java

package ...;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class CallNativeCodeImpl {

    public String getFilePath() {
        return null;
    }

    public void clearAndRestartLog() {
        // https://developer.android.com/studio/command-line/logcat
        try {
            Runtime.getRuntime().exec("logcat -b all -c");
            Runtime.getRuntime().exec("logcat");
        } catch (IOException ex) {
            // logcat non available?
        }
    }

    // original code: https://stackoverflow.com/q/12692103
    public String readLog() {
        try {
            Process process = Runtime.getRuntime().exec("logcat -d");
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));

            StringBuilder log = new StringBuilder();
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                log.append(line);
                log.append("\n");
            }
            return log.toString();
        } catch (IOException e) {
            return "Log is not available.";
        }
    }

    public boolean isSupported() {
        return true;
    }

}

It works so: in the init() of the main class of the Codename One app I added:

// Test of Native code
        CallNativeCode callNativeCode = NativeLookup.create(CallNativeCode.class);
        if (callNativeCode != null && callNativeCode.isSupported()) {
            Log.p("Native code can be executed");
            callNativeCode.clearAndRestartLog();
            Log.p("Native LOG cleared and restarted");
        } else {
            Log.p("Native code cannot be executed");
        }

then, when I want to get the native log of the app, I can execute:

String nativeLog = callNativeCode.readLog();

In this way, I get the same output of the Android Studio Logcat without the need to use Android Studio and without the need of a device connected to a computer.

I tried to replicate this functionality for iOS... but I'm in trouble, because I don't know Objective-C. I tried to redirect the native output log to a file and then to read that file (adapting some code that I found and trying to guess how it works)... but I'm not sure how to do it and my code doesn't compile on the iOS build server.

The following code is what I tried to do. How can it be fixed? Thanks

myPackageName_CallNativeCodeImpl.h

#import <Foundation/Foundation.h>

@interface cool_teammate_registration_CallNativeCodeImpl : NSObject {
}

-(NSString*)readLog;
-(NSString*)getFilePath;
-(void)clearAndRestartLog;
-(BOOL)isSupported;
@end

myPackageName_CallNativeCodeImpl.m

#import "myPackageName_CallNativeCodeImpl.h"

@implementation myPackageName_CallNativeCodeImpl

-(NSString*)getFilePath{
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString* documentsDirectory = [paths objectAtIndex:0];
    NSString* fileName =[NSString stringWithFormat:@"%@.log",[NSDate date]];
    NSString* logFilePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    return logFilePath;
}

-(NSString)readLog{
    NSString* logFilePath = [self getFilePath];
    NSString* content = [NSString stringWithContentsOfFile:logFilePath encoding:NSUTF8StringEncoding error:nil];
    return content;
}

-(void)clearAndRestartLog{
    // https://stackoverflow.com/questions/5179108/iphone-how-to-read-application-logs-from-device
    NSString* logFilePath = [self getFilePath];
    freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
}

-(BOOL)isSupported{
    return YES;
}

@end
Francesco Galgani
  • 6,137
  • 3
  • 20
  • 23
  • 1
    If you have a compilation error you don't understand click the chat button on the site and ask. Please provide the log link there so our engineer can help with the compilation error. About the premise of this, it's a very interesting idea but I'm not sure that's the right objective C code. Notice the fopen code is really just standard unix system code – Shai Almog May 22 '18 at 04:27
  • Thank you Shai for your feedback, I'm glad to hear that you consider this idea very interesting. I've just added an answer to this question with a working implementation of the code for iOS. In this way, joining the question and the answer, the readers can have a full implementation of a native interface for iOS and Android to get the native logs. I'm not sure if it can be transformed in a CN1Lib... – Francesco Galgani May 22 '18 at 12:07
  • 1
    I don't see anything that should be a problem for a cn1lib. Just create a new cn1lib and copy the native directories plus the relevant src files to the right locations. Then add some abstraction if you don't want to expose the full native interface. Commit it to github and submit a change to http://github.com/codenameone/CodenameOneLibs/ to add it to the extension manager. If there are any difficulties along the way we can walk you through it! – Shai Almog May 23 '18 at 04:27
  • UPDATE FOR THE READERS: the code discussed here is now available in this Codename One library: https://github.com/jsfan3/CN1Libs-NativeLogsReader – Francesco Galgani May 25 '18 at 10:09
  • 1
    And it's in the extensions manager too... – Shai Almog May 26 '18 at 04:01

1 Answers1

1

There was a typo error in my iOS code (NSString instead of NSStrings*).

I found useful information in this post: Log iOS Errors to file. I used that information to do a small optimization to the code.

Finally, this is my working code (tested successfully on iOS 8, iOS 9, iOS 10, iOS 11). In this way, I can get the console log without the need of connecting the device to a Mac with XCode installed :)

myPackageName_CallNativeCodeImpl.m

#import "myPackageName_CallNativeCodeImpl.h"

@implementation myPackageName_CallNativeCodeImpl

// Useful information:
// Log iOS Errors to File
// https://www.progressconcepts.com/blog/log-ios-errors-file/

-(NSString*)getFilePath{
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString* documentsDirectory = [paths objectAtIndex:0];
    NSString* fileName = @"device.log";
    NSString* logFilePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    return logFilePath;
}

-(NSString*)readLog{
    NSString* logFilePath = [self getFilePath];
    NSString* content = [NSString stringWithContentsOfFile:logFilePath encoding:NSUTF8StringEncoding error:nil];
    return content;
}

-(void)clearAndRestartLog{
    NSError *error = nil;
    NSString* logFilePath = [self getFilePath];
    [[NSFileManager defaultManager] removeItemAtPath:logFilePath error:&error];

    // wrap the code to check if the debugger was attached, and only write to the file when it wasn’t
    if (!isatty(STDERR_FILENO)) { 
        freopen([logFilePath cStringUsingEncoding:NSUTF8StringEncoding],"a+",stdout);
        freopen([logFilePath cStringUsingEncoding:NSUTF8StringEncoding],"a+",stderr);
    }
}

-(BOOL)isSupported{
    return YES;
}

@end
Francesco Galgani
  • 6,137
  • 3
  • 20
  • 23