[SalesForce] Get Debug Logs with specified log levels for Execute Anonyous via APEX ToolingAPI, SoapAPI or anyhow

A) via ToolingAPI

I'm trying to execute apex-code via

public ExecuteAnonymousResult executeAnonymousEncoded(String body)

of the ToolingAPI.cls from within APEX. So far it works. But I was not able to receive a proper debug log. Neither error-messages show up. I need the logs as a string to output them back to users. Somehow the Log-Level has to be set in the Header to get logs, but I was not able to do so using ToolingAPI.cls – any idea?

B) via SoapAPI

As an alternative, I tried to make a callout to the SoapAPI, which also fails.

Wanted to call that function http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#CSHID=sforce_api_calls_executeanonymous.htm|StartTopic=Content%2Fsforce_api_calls_executeanonymous.htm|SkinName=webhelp

But this way I found how to set the header. Here the code I used:

public with sharing class callout { 
public static string exec() {
    Http h = new Http();
    HttpRequest req = new HttpRequest();
    URL baseUrl = URL.getSalesforceBaseUrl();
    req.setEndpoint( baseUrl.toExternalForm() + '/services/Soap/m/'+'29.0');
    req.setMethod('POST');
    req.setHeader('Content-Type', 'text/xml'); 
    req.setHeader('SOAPAction', '""');
    req.setBody(''
        +'<?xml version="1.0" encoding="UTF-8"?>'
        +'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:apex="urn:partner.soap.sforce.com" >' 
            +'<soapenv:Header>'
                +'<apex:DebuggingHeader>'
                    +'<apex:debugLevel>Detail</apex:debugLevel>'
                +'</apex:DebuggingHeader>'
                +'<apex:SessionHeader>'
                    +'<apex:sessionId>'+UserInfo.getSessionId()+'</apex:sessionId>'
                +'</apex:SessionHeader>'
            +'</soapenv:Header>'
            +'<soapenv:Body>'
                +'<apex:executeAnonymous>'
                    +'<apex:String>'
                        +'Account a = new Account(Name=\'Test Account\', phone=\'111222111\');'
                        +'insert a;'
                        +'System.debug(\'New Account Name \' + a.Name);'
                    +'</apex:String>'
                +'</apex:executeAnonymous>'
            +'</soapenv:Body>'

        +'</soapenv:Envelope>'
    );
    HttpResponse res = h.send(req);
    return res.getBody();
}    

}

I got only:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Client</faultcode>
<faultstring>No operation available for request {urn:partner.soap.sforce.com}executeAnonymous</faultstring></soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>

C) via Javascript AJAX-Toolkit – Temporary Solution (very bad)

I temporarily solved it using a very hackish approach via the AJAX-Toolkit using javascript…. it works but I need to deeply hack into salesforce javascript to reroute the debug-output and a need a sessionId from

this specific SessionId is actually very hard to get after salesforce enabled clickjack-protection for non-setup-pages by default. If one is interested on how I did, let me know, I provide details. But it is ugly – as I said. But at least it works, and its very fast but will never pass a security review! 😉

Now, I want to get rid of that mess and do it the "right way"

Best Answer

I imported the Tooling API WSDL back into Apex as toolingSoapSforceCom and added a Remote Site setting from my Salesforce pod/domain.

I could then call the Tooling API with:

toolingSoapSforceCom.SforceService toolingService = new toolingSoapSforceCom.SforceService();
toolingService.SessionHeader = new toolingSoapSforceCom.SessionHeader_element();
toolingService.SessionHeader.SessionId = UserInfo.getSessionId();

toolingService.DebuggingHeader = new toolingSoapSforceCom.DebuggingHeader_element();
toolingService.DebuggingHeader.debugLevel = 'DEBUGONLY';
toolingService.DebuggingHeader.categories = new List< toolingSoapSforceCom.LogInfo>();
toolingSoapSforceCom.LogInfo apexLogInfo = new toolingSoapSforceCom.LogInfo();
apexLogInfo.category='Apex_code';
apexLogInfo.level='FINEST';
toolingService.DebuggingHeader.categories.add(apexLogInfo);

toolingSoapSforceCom.ExecuteAnonymousResult executeResponse = toolingService.executeAnonymous('System.debug(\'hello World\');');
System.assert(executeResponse.compiled);

System.debug(toolingService.DebuggingInfo)  

When doing similar calls the the Apex WSDL I would then retrieve the resulting Apex Log from the DebuggingInfo header. It would appear that the DebuggingInfo header either isn't coming back from the call or isn't being populated by wsdl2apex.


My second attempt was to use a direct Http request using the XML body generated by the above attempt.

Http h = new Http();
HttpRequest req = new HttpRequest();
URL baseUrl = URL.getSalesforceBaseUrl();
req.setEndpoint( baseUrl.toExternalForm() + '/services/Soap/T/'+'30.0');
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml'); 
req.setHeader('SOAPAction', '""');
req.setBody('<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Header><DebuggingHeader xmlns="urn:tooling.soap.sforce.com"><categories><category>Apex_code</category><level>FINEST</level></categories><debugLevel>DEBUGONLY</debugLevel></DebuggingHeader><SessionHeader xmlns="urn:tooling.soap.sforce.com"><sessionId>'+UserInfo.getSessionId+'</sessionId></SessionHeader></env:Header><env:Body><executeAnonymous xmlns="urn:tooling.soap.sforce.com"><String>System.debug(\'hello World\');</String></executeAnonymous></env:Body></env:Envelope>' );
HttpResponse res = h.send(req);
System.debug(res);
System.debug(res.getBody());

System.debug(res.getHeaderKeys())

Using HttpResonse.getHeaderKeys() I went looking for the DebuggingInfo SoapHeader in the response. It didn't appear to be there.

I could however query the ApexLog via SOQL and see new records being created that contained the logs.

SOQL query against Apex log showing the new logs


Attempt # 3 was to using the Apex web service rather than the tooling API. Again, I imported the WSDL using wsdl2apex.

soapSforceComApex.Apex apexService = new soapSforceComApex.Apex();
apexService.SessionHeader = new soapSforceComApex.SessionHeader_element();
apexService.SessionHeader.SessionId = UserInfo.getSessionId();

apexService.DebuggingHeader = new soapSforceComApex.DebuggingHeader_element();
apexService.DebuggingHeader.debugLevel = 'DEBUGONLY';
apexService.DebuggingHeader.categories = new List< soapSforceComApex.LogInfo>();
soapSforceComApex.LogInfo apexLogInfo = new soapSforceComApex.LogInfo();
apexLogInfo.category='Apex_code';
apexLogInfo.level='FINEST';
apexService.DebuggingHeader.categories.add(apexLogInfo);

soapSforceComApex.ExecuteAnonymousResult executeResponse = apexService.executeAnonymous('System.debug(\'hello World\');');
System.assert(executeResponse.compiled);

System.debug(apexService.DebuggingInfo);

This was partially successful. The CALLOUT_RESPONSE did have a <soapenv:Header><DebuggingInfo><debugLog> in the response. However, this wasn't populated into the DebuggingInfo property in Apex. However, you could copy the XML from the CALLOUT_REQUEST and send it via HttpRequest to https://na5.salesforce.com/services/Soap/s/30.0 so you can parse the log out of the response.

The CALLOUT_REQUEST XML:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Header>
    <DebuggingHeader xmlns="http://soap.sforce.com/2006/08/apex">
      <categories>
        <category>Apex_code</category>
        <level>FINEST</level>
      </categories>
      <debugLevel>DEBUGONLY</debugLevel>
    </DebuggingHeader>
    <SessionHeader xmlns="http://soap.sforce.com/2006/08/apex">
      <sessionId>00D700000000001!AQ_NOTAREALSESSIONID_HZIe</sessionId>
    </SessionHeader>
  </env:Header>
  <env:Body>
    <executeAnonymous xmlns="http://soap.sforce.com/2006/08/apex">
      <String>System.debug('hello World');</String>
    </executeAnonymous>
  </env:Body>
</env:Envelope>