XML Logging: Difference between revisions

From DataFlex Wiki
Jump to navigationJump to search
m fix typo
 
(40 intermediate revisions by 3 users not shown)
Line 1: Line 1:
With Service Oriented systems it can be very important to have a log of the XML that has been sent and received by the systems involved, for purposes of determining exactly what went on to cause any given situation (usually an error situation, otherwise there would be no need to look into it).  This article looks at how such things can be done and how the logged XML can then be used to investigate the problem.
With Service Oriented systems it can be very important to have a log of the XML that has been sent and received by the systems involved, for purposes of determining exactly what went on to cause any given situation (usually an error situation, otherwise there would be no need to look into it).  This article looks at how such things can be done.  For how to then use the logged XML to investigate the problem see the related article [[XML Replay]].


In general, once the raw XML has been obtained, one could log it in a number of ways, but for us the most natural would be to put it into a "Text" column in a database table.


==Web Service Client XML Logging==
==Web Service Client XML Logging==
Logging the XML sent by a web service client is relatively easy.  It simply involves, following the service invocation, getting the psXML property of the object pointed to by the phoSoapRequest property of your web service client object.  Something like this:
Logging the XML sent by a web service client is relatively easy.  It simply involves, following the service invocation, getting the psXML property of the object pointed to by the phoSoapRequest property of your web service client object.  Something like this:


<source lang="dataflex">
  tSomeInput Input
  tSomeInput Input
  tSomeOutput Output
  tSomeOutput Output
  String  sXML<br />
  String  sXML
  Get wsSomeOp of oXyzService Input to Output<br />
  Get wsSomeOp of oXyzService Input to Output
  Move (psXML(phoSoapRequest(oXyzService(Self)))) to sXML
  Move (psXML(phoSoapRequest(oXyzService(Self)))) to sXML
</source>


Or, more verbosely:
Or, more verbosely:


<source lang="dataflex">
  tSomeInput Input
  tSomeInput Input
  tSomeOutput Output
  tSomeOutput Output
  String  sXML
  String  sXML
  Handle  hoSoapReq<br />
  Handle  hoSoapReq
  Get wsSomeOp of oXyzService Input to Output<br />
  Get wsSomeOp of oXyzService Input to Output
  Get phoSoapRequest of oXyzService to hoSoapReq
  Get phoSoapRequest of oXyzService to hoSoapReq
  Get psXML of hoSoapReq to sXML
  Get psXML of hoSoapReq to sXML
</source>


Either way you now have the XML sent by your web service client and can log it in whatever way you require.
Either way you now have the XML sent by your web service client and can log it in whatever way you require.


==Web Service XML Logging==
==Web Service XML Logging==
There is a very real difficulty regarding accessing the XML which is passed it a VDF Web Service (at least so far as revisions up to VDF 12.1 are concerned).  The problem is that the original XML is nowhere available to the VDF program.  One possible solution would be to employ some kind of HTTP proxy on the server involved, which would receive the XML (actually the HTTP within which the XML is wrapped) ''before'' it reached IIS, log that, then pass it on to IIS.  However here we are going to concern ourselves only with the best that can be managed from within the VDF programming environment at the moment (November 2007).
There is a very real difficulty regarding accessing the XML which is passed to a VDF Web Service (at least so far as revisions up to VDF 12.1 are concerned).  The problem is that the original XML is nowhere available to the VDF program.  One possible solution would be to employ some kind of HTTP proxy on the server involved, which would receive the XML (actually the HTTP within which the XML is wrapped) ''before'' it reached IIS, log that, then pass it on to IIS.  However here we are going to concern ourselves only with the best that can be managed from within the VDF programming environment at the moment (November 2007).
 
The trick is to make use of a ''client'' of your service ''within'' the service itself.  This in turn makes doing it a multi-stage process, since one can only create a client once the service is published and accessible.


The trick is to make use of a ''client'' of you service ''within'' the service itself.  This in turn makes doing it a multi-stage process, since one can only create a client once the service is published and accessible.
''Note: the code below is in brief and omits several important property settings which would normally be created on a web service.''


====Step 1====
====Step 1: Define the Service====
First you define your service:
First you define your service (and get it running on IIS):


  Object oLogSample is a cWebService<br />
<source lang="dataflex">
  Object oLogSample is a cWebService
   { Published = True  }
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc<br />
   Procedure  SendSomething tSomeDocumentType Doc
       // Code that actually does stuff...<br />
       // Code that actually does stuff...
   End_Procedure  // SendSomething<br />
   End_Procedure  // SendSomething
  End_Object  // oLogSample<br />
  End_Object  // oLogSample
</source>


====Step 2====
====Step 2: Create a Client====
Next you need to run the Web Service Client Class Generator (in the VDF Studio: File -> New -> Class -> Clint Web Service Class) on the WSDL for your service (Test Page -> Service Name -> Service Description, then copy the browser's Address window to the Web Service Client Class Generator's WSDL URL window and click "Parse", "Generate Class" and "OK" - plus "Yes" to overwrite if it already exists).
Next you need to run the Web Service Client Class Generator (in the VDF Studio: File -> New -> Class -> Client Web Service Class) on the WSDL for your service (Test Page -> Service Name -> Service Description, then copy the browser's Address window to the Web Service Client Class Generator's WSDL URL window) and click "Parse", "Generate Class" and "OK" (plus "Yes" to overwrite if it already exists).


====Step 3====
====Step 3: Instantiate the client in the service====
Now you need to use and instanciate the client class you have just generated within your service:
Now you need to use and instantiate the client class you have just generated within your service:


  Object oLogSample is a cWebService<br />
<source lang="dataflex">
   ''Use cWSLogSample.pkg''
  Object oLogSample is a cWebService
   ''Object oOwnService is a cWSLogSample''
   Use cWSLogSample.pkg
   ''End_Object // oOwnService''<br />
   Object oOwnService is a cWSLogSample
   End_Object // oOwnService
   { Published = True  }
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc<br />
   Procedure  SendSomething tSomeDocumentType Doc
       // Code that actually does stuff...<br />
       // Code that actually does stuff...
   End_Procedure  // SendSomething<br />
   End_Procedure  // SendSomething
  End_Object  // oLogSample<br />
  End_Object  // oLogSample
</source>


====Step 4====
====Step 4: Use the client to transform the data back to XML====
Finally you need to create a procedure that will utilise that client to ''reconstitute'' the XML from the data that your published method receives and call that procedure as the first thing each of your published methods do:
Finally you need to create a procedure that will utilise that client to ''reconstitute'' the XML from the data that your published method receives and call that procedure as the first thing each of your published methods do:


  Object oLogSample is a cWebService<br />
<source lang="dataflex">
  Object oLogSample is a cWebService
   Use cWSLogSample.pkg
   Use cWSLogSample.pkg
   Object oOwnService is a cWSLogSample
   Object oOwnService is a cWSLogSample
   End_Object  // oOwnService<br />
   End_Object  // oOwnService
   ''// LogMsg: The arguments are - 1 Data passed to the method''
   // LogMsg: The arguments are - 1 Data passed to the method
   ''//                            2 Name of the method (literal)''
   //                            2 Name of the method (literal)
   ''//                            3 Name of the variable (literal)''
   //                            3 Name of the variable (literal)
   ''//                            4 Object handle for type - which will be named "oWS{typeName}"''
   //                            4 Object handle for type - which will be named "oWS{typeName}"
   ''Procedure LogMsg Variant Data String sOp String sName Handle hoObj''
   Procedure LogMsg Variant Data String sOp String sName Handle hoObj
       ''tSoapParameter Param''
       tSoapParameter Param
       ''Handle  hoXML hoDoc''
       Handle  hoXML hoDoc
       ''String  sXML
       String  sXML
       ''Boolean bOK''<br />
       Boolean bOK
       ''Get Create of Desktop U_cXmlDomDocument to hoXML''
       Get Create of Desktop U_cXmlDomDocument to hoXML
       ''Get CreateDocumentElement of hoXML sOp to hoDoc''<br />
       Get CreateDocumentElementNS of hoXML (psServiceURI(Self)) sOp to hoDoc
       ''Get DefineStructParameter of oOwnService hoObj 1 1 sName (psServiceURI(Self)) to Param.SoapParamDef''
       Get DefineStructParameter of oOwnService hoObj 1 1 sName (psServiceURI(Self)) to Param.SoapParamDef
       ''ValueTreeSerializeParameter Data to Param.ValueTree''
       ValueTreeSerializeParameter Data to Param.ValueTree
       ''Get ValueTreeToXml of oOwnService Param.SoapParamDef Param.ValueTree hoDoc to bOK''<br />
       Get ValueTreeToXml of oOwnService Param.SoapParamDef Param.ValueTree hoDoc to bOK
       ''Get psXML of hoXml to sXML
       Get psXML of hoXml to sXML
       ''Send Destroy of hoDoc
       Send Destroy of hoDoc
       ''Send Destroy of hoXML<br />
       Send Destroy of hoXML
       ''// Now you can log sXML in some way...<br />
       // Now you can log sXML in some way...
   ''End_Procedure // LogMsg''<br />
   End_Procedure // LogMsg
   { Published = True  }
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc
   Procedure  SendSomething tSomeDocumentType Doc
       ''Send LogMsg Doc "SendSomething" "Doc" (oWStSomeDocumentType(oOwnService(Self)))''<br />
       Send LogMsg Doc "SendSomething" "Doc" (oWStSomeDocumentType(oOwnService(Self)))
       // Code that actually does stuff...<br />
       // Code that actually does stuff...
   End_Procedure  // SendSomething<br />
   End_Procedure  // SendSomething
  End_Object  // oLogSample<br />
  End_Object  // oLogSample
</source>
 
'''Warning:''' Note that each time you change your service, you should regenerate the web service client (as in [[#Step_2:_Create_a_Client | Step 2]]) immediately afterwards and then recompile your WebApp... <u>again</u>!


'''Warning:''' Note that each time you change your service, you should regenerate the web service client (as in [[#Step_2 | Step 2]])immediately afterwards and then recompile your WebApp (again).
===Issues===
It is important to note that this mechanism '''does ''not'' retrieve the original XML''', but instead ''reconstitutes'' it to the best of its ability from the data passed to the program. There are circumstances where such ''reconstituted'' XML might not match that actually sent to the service, especially if that was mal-formed in terms of the published WSDL service description. A specific example of this is when more than one element is passed when only one is expected: the program will receive (and hence log) only the first (expected) one and will be totally unaware of (and hence unable to log) any additional ones.


==Replaying logged XML==
[[Category:Web Services]] [[Category:Cookbook]]

Latest revision as of 19:43, 7 December 2019

With Service Oriented systems it can be very important to have a log of the XML that has been sent and received by the systems involved, for purposes of determining exactly what went on to cause any given situation (usually an error situation, otherwise there would be no need to look into it). This article looks at how such things can be done. For how to then use the logged XML to investigate the problem see the related article XML Replay.

In general, once the raw XML has been obtained, one could log it in a number of ways, but for us the most natural would be to put it into a "Text" column in a database table.

Web Service Client XML Logging

Logging the XML sent by a web service client is relatively easy. It simply involves, following the service invocation, getting the psXML property of the object pointed to by the phoSoapRequest property of your web service client object. Something like this:

 
 tSomeInput Input
 tSomeOutput Output
 String  sXML
 Get wsSomeOp of oXyzService Input to Output
 Move (psXML(phoSoapRequest(oXyzService(Self)))) to sXML

Or, more verbosely:

 
 tSomeInput Input
 tSomeOutput Output
 String  sXML
 Handle  hoSoapReq
 Get wsSomeOp of oXyzService Input to Output
 Get phoSoapRequest of oXyzService to hoSoapReq
 Get psXML of hoSoapReq to sXML

Either way you now have the XML sent by your web service client and can log it in whatever way you require.

Web Service XML Logging

There is a very real difficulty regarding accessing the XML which is passed to a VDF Web Service (at least so far as revisions up to VDF 12.1 are concerned). The problem is that the original XML is nowhere available to the VDF program. One possible solution would be to employ some kind of HTTP proxy on the server involved, which would receive the XML (actually the HTTP within which the XML is wrapped) before it reached IIS, log that, then pass it on to IIS. However here we are going to concern ourselves only with the best that can be managed from within the VDF programming environment at the moment (November 2007).

The trick is to make use of a client of your service within the service itself. This in turn makes doing it a multi-stage process, since one can only create a client once the service is published and accessible.

Note: the code below is in brief and omits several important property settings which would normally be created on a web service.

Step 1: Define the Service

First you define your service (and get it running on IIS):

 
 Object oLogSample is a cWebService
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc
      // Code that actually does stuff...
   End_Procedure  // SendSomething
 End_Object  // oLogSample

Step 2: Create a Client

Next you need to run the Web Service Client Class Generator (in the VDF Studio: File -> New -> Class -> Client Web Service Class) on the WSDL for your service (Test Page -> Service Name -> Service Description, then copy the browser's Address window to the Web Service Client Class Generator's WSDL URL window) and click "Parse", "Generate Class" and "OK" (plus "Yes" to overwrite if it already exists).

Step 3: Instantiate the client in the service

Now you need to use and instantiate the client class you have just generated within your service:

 Object oLogSample is a cWebService
   Use cWSLogSample.pkg
   Object oOwnService is a cWSLogSample
   End_Object // oOwnService
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc
      // Code that actually does stuff...
   End_Procedure  // SendSomething
 End_Object  // oLogSample

Step 4: Use the client to transform the data back to XML

Finally you need to create a procedure that will utilise that client to reconstitute the XML from the data that your published method receives and call that procedure as the first thing each of your published methods do:

 Object oLogSample is a cWebService
   Use cWSLogSample.pkg
   Object oOwnService is a cWSLogSample
   End_Object  // oOwnService
   // LogMsg: The arguments are - 1 Data passed to the method
   //                             2 Name of the method (literal)
   //                             3 Name of the variable (literal)
   //                             4 Object handle for type - which will be named "oWS{typeName}"
   Procedure LogMsg Variant Data String sOp String sName Handle hoObj
      tSoapParameter Param
      Handle  hoXML hoDoc
      String  sXML
      Boolean bOK
      Get Create of Desktop U_cXmlDomDocument to hoXML
      Get CreateDocumentElementNS of hoXML (psServiceURI(Self)) sOp to hoDoc
      Get DefineStructParameter of oOwnService hoObj 1 1 sName (psServiceURI(Self)) to Param.SoapParamDef
      ValueTreeSerializeParameter Data to Param.ValueTree
      Get ValueTreeToXml of oOwnService Param.SoapParamDef Param.ValueTree hoDoc to bOK
      Get psXML of hoXml to sXML
      Send Destroy of hoDoc
      Send Destroy of hoXML
      // Now you can log sXML in some way...
   End_Procedure // LogMsg
   { Published = True  }
   { Description = "Interface for sending something to the system" }
   Procedure  SendSomething tSomeDocumentType Doc
      Send LogMsg Doc "SendSomething" "Doc" (oWStSomeDocumentType(oOwnService(Self)))
      // Code that actually does stuff...
   End_Procedure  // SendSomething
 End_Object  // oLogSample

Warning: Note that each time you change your service, you should regenerate the web service client (as in Step 2) immediately afterwards and then recompile your WebApp... again!

Issues

It is important to note that this mechanism does not retrieve the original XML, but instead reconstitutes it to the best of its ability from the data passed to the program. There are circumstances where such reconstituted XML might not match that actually sent to the service, especially if that was mal-formed in terms of the published WSDL service description. A specific example of this is when more than one element is passed when only one is expected: the program will receive (and hence log) only the first (expected) one and will be totally unaware of (and hence unable to log) any additional ones.