Developer

Add Logging to your iOS Application

Add logging to your native iOS OData app, upload logs to the server, and view the logs.

Logging

Use logging features provided by supportability and logging libraries.

  • Logger Creation and Setup
    The Supportability framework provides advanced logging capabilities. Instantiate dedicated and custom logger objects using the SAPLogManager factory method. The various SDK components use specific loggers internally.
    1. Add this to import the required public headers::
      #import "SAPSupportabilityFacade.h"
      #import "SAPClientLoggerDefault.h"
      #import "SAPClientLogManager.h"
       
    2. Query the default client log manager:
      // Get ClientLogManager instance
      id<SAPClientLogManager> logManager = [[SAPSupportabilityFacade sharedManager] getClientLogManager];
       
      By default, loggers persist their logs in a local database. If you need the logs to also appear in the console, you can override the default using the SAPClientLogManager setLogDestination: API. The API expects values defined in an E_CLIENT_LOG_DESTINATION enum, and can be combined using the OR operator:
      // Set the log destination for all loggers 
      // optional: default is FILESYSTEM
      [logManager setLogDestination:(CONSOLE | FILESYSTEM)];
      
    3. Set the log output destination and log level. Two log destinations are supported:
      • Console logs are displayed in Xcode debug console
      • FileSystem logs are persisted in the clients local storage (SQLite database in this case)
      The SAPClientLogManager setLogLevel: method allows setting of the global log level. The default log level is ERROR, which makes your logs less verbose; however, if you need more detailed log messages, you can lower the log level to WarningClientLogLevel or DebugLogLevel:
      // Set the desired log level on all the loggers
      [logManager setLogLevel:WarningClientLogLevel];
          
      // Set the desired log level for a particular logger // optional: default is ErrorClientLogLevel
      [logManager setLogLevel:InfoClientLogLevel forIdentifier:LOGGER_PARSER];
      [logManager setLogLevel:FatalClientLogLevel forIdentifier:LOGGER_SERVER];
          
      // Set the log destination for a particular logger
      [logManager setLogDestination:FILESYSTEM forIdentifier:LOGGER_SERVER];
      
      
      To instantiate a custom logger, provide a unique ID to every particular custom logger. Reusing the same logger ID produces the same logger instance:
      static NSString* kCustomLogger = @"CustomLogger";
      // instantiate a custom logger
      SAPClientLogger* customLogger = [logManager getLogger: kCustomLogger];
      
    4. Log a warning using the newly created logger:
      [customLogger logWarning:@"Warning message goes here"];
      In a production application, create a logger property to use throughout the given implementation file:
      @property (strong, nonatomic) SAPClientLogger* logger;
      …
      // instantiate the custom logger
      self.logger = [logManager getLogger: kCustomLogger];
      
      
  • Fine Tune Logger Settings

    The global setting applies for all existing logger instances, and provides default values for loggers to be created later. (If no global setting is made, the original defaults apply; ERROR for log level and FILESYSTEM for log destination.)

    Global settings can be overridden on a per logger basis, which is useful if you want to silence some components, while enabling logging for a group of selected loggers (for example, to identify issues in a specific part of the project, or focus on a small subset of components).
    // set log level to DEBUG for this very logger
    [logManager setLogLevel:DebugClientLogLevel forIdentifier:kCustomLogger];
    // do not persist logs, just display them in XCode's console window
    [logManager setLogDestination:CONSOLE forIdentifier:kCustomLogger];
    
  • Client Log Levels
    The default log level setting is ERROR. The following log levels are supported:
    FatalClientLogLevel > ErrorClientLogLevel > WarningClientLogLevel > InfoClientLogLevel > DebugClientLogLevel
    Lower levels automatically enable higher ones. For example, setting the log level to WARNING means that all WARNINGs, ERRORs and FATAL log messages are logged, while INFO and DEBUG messages are not:
    FATAL > ERROR > WARNING > INFO > DEBUG
    
    
    
    Setting the log level to FATAL silences all other log levels:
    FATAL > ERROR > WARNING > INFO > DEBUG
    
    
    Enabling the DEBUG log level is equivalent to enabling ALL log levels:
    FATAL > ERROR > WARNING > INFO > DEBUG
  • Retrieving Logs

    SAPClientLogManager provides stream-based APIs to retrieve the logs that have been produced by the app.

    Use SAPClientLogManager getLogEntries: outputStream: error: to sequentially retrieve all logs with the specified and more critical level. Alternatively, use getLogEntriesForLogger: withLevel: outputStream: error: to fetch logs for a given logger.

    A valid NSOutputStream should be passed to the APIs. This code shows a practical approach where the output stream is mapped to a file::
    NSError* error = nil;
    NSString* path = [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), @"logData.txt"];
    // map stream to file
    NSOutputStream* logStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
        
    BOOL hasLogs = [m_LogManager getLogEntries:ErrorClientLogLevel outputStream:&logStream error:&error];
    if( error )
        {
            // eror handling
        }
        else
        {
            if( [[NSFileManager defaultManager] fileExistsAtPath:path] )
            {
                // process file content ...
                // finally remove the file
                [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
            }
        }
    
    For cases when the expected amount of log entries is low, passing nil for NSOutputStream is an option; this maps the stream to memory rather than to a file. Use with caution, since large amounts of log data may lead to low memory conditions and might even crash the app. This code shows how to stream log data to memory:
    // If the expected log amount is moderate, pass nil for NSOutputStream
    // logs will be streamed to memory, and can be obtained from the stream via NSStreamDataWrittenToMemoryStreamKey key
    NSOutputStream* memoryMappedStream = nil;
    NSError* error = nil;
    if( [m_LogManager getLogEntries:ErrorClientLogLevel outputStream:&memoryMappedStream error:&error] )
        {
            NSData* logData = [memoryMappedStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
            // process the contents
        }
        else
        {
            NSLog(@"Error in getting log data: %@", [error description]);
        }
    
    
  • Client-side Log Persistence
    Since it is difficult to predict how many logs will be produced by an app, persisting them on the client is intentionally temporary. Logs are removed:
    • After being successfully uploaded to the server or
    • Once their expiation date is reached. By default log entries are purged after one week.
  • Uploading Logs to the Server

    SAP Mobile Platform Server supports uploading of client log files. Access is controlled by the security configuration associated with the given application. Therefore, prior to uploading logs, the Server Administer must set the appropriate settings. See http://help.sap.com/saphelp_smp307svr/helpdata/en/e6/46aa8668a64efb94ec30e198a0b009/content.htm (or perform a Web search for “SMP” “Defining Client Log and Trace Policies”). Additionally, the application connection ID must be passed as a “X-SMP-APPCID” header field.

    The following code attempts to upload client logs to SAP Mobile Platform Server. You must supply a valid SAP Mobile Platform Server log upload URL in the form “http(s)://host:port/clientlogs”, and the client log policy must be configured to accept log uploads on the Management Cockpit. See http://help.sap.com/saphelp_smp307svr/helpdata/en/e6/46aa8668a64efb94ec30e198a0b009/content.htm for details.
    NSURL* uploadURL = [NSURL URLWithString:@“<url>:<port#>”];
        
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%li/clientlogs", uploadURL.host, (long)uploadURL.port.integerValue]]];
            
    NSString* appCID = [Settings sharedInstance].appCID;
    [request setValue:appCID forHTTPHeaderField:@"X-SMP-APPCID"];
        
    SupportabilityUploader* uploader = [[SupportabilityUploader alloc] initWithHttpConversationManager:[SAPStoreManager sharedInstance].conversationManager urlRequest:request];
        
    [[[SAPSupportabilityFacade sharedManager] getClientLogManager] uploadClientLogs:uploader completion:^(NSError* error) {
        if ( !error )
        {
            LOGDEB( @"Log upload completed succesfully" );
            UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString( @"Log Upload", nil ) message:NSLocalizedString( @"Log upload completed succesfully", nil ) delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
                
            dispatch_async(dispatch_get_main_queue(), ^{
                [alertView show];
            });
        }
        else
        {
            LOGWAR( @"Log upload failed: %@", error.localizedDescription );
            UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString( @"Log Upload", nil ) message:[NSString stringWithFormat:@"Log upload failed: %@", error.localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
    
            dispatch_async(dispatch_get_main_queue(), ^{
                [alertView show];
            });
        }
            
        [sUploadLogLock unlock];
    }];
    
  • Viewing Logs on the Server

    Logs that are successfully uploaded to the server can be viewed and filtered to analyze and troubleshoot issues. See : http://help.sap.com/saphelp_smp307svr/helpdata/en/81/540a14ddb04e00a73ddaaa10c7260f/content.htm