4

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"

3

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>
  • Thanks @Daniel for the answere. Could you please add the XML from the CALLOUT_REQUEST you mention in th last part? Would make it much easier to test. Thx – Uwe Heim Jun 30 '14 at 4:02
  • So in the end, its my approach #B with better XML, right? – Uwe Heim Jun 30 '14 at 4:06
  • @UweHeim It appears sending XML to the Apex web service is your best option if you want to directly read the log back in the response. Otherwise you can call the web service via wsdl2apex and get the Log via a follow SOQL query to the ApexLog and a download request. – Daniel Ballinger Jun 30 '14 at 9:06
  • Thanks @DanielBallinger, could you paste the XML from the CALLOUT_REQUEST so that I can skip the part with wsdl2apex? The XML for that should be the same for all orgs. As I understood, you have done it already, so it would save me (and others) from doing it for that sole purpose again. Then I'll try to create a reusable static function, test it and publish here for simple reuse for everyone. – Uwe Heim Jun 30 '14 at 9:12
  • Thanks to @DanielBallinger once again, I see it's already there... All good! I'll give it a try and come back here again! – Uwe Heim Jun 30 '14 at 9:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.