The method of logging to a global was primarily to match the original use case.  Having a full class for logging including indexes would be a better option.

However, my personal belief is that suspending transactions, or even worse stopping and starting  journaling,  is not really a good option.  Even if the actually coding does not appear to be complex there is the potential for reach beyond the current transaction if you don't cover all bases.  Holding the logs in a temporary global that then gets written out after the transaction insures that the application logic is intact.  You could even encapsulate this functionality in classmethods.  If there is any failure the impact would be on the logging and not the actual logic of the transaction.

Not if we could perform an update/insert/save that could be intentionally and deliberately excluded from the transaction OR if the suspend was only for the current transaction so no lasting impact was even possible then I would feel more comfortable with that approach.

I will add that this is a matter of design and the programmer's approach.  Either method works the decision is where you wish to have any risk, no matter how small that risk is.  My preference is to be absolutely sure the integrity of the transaction  is maintained over the logging.

First let me state for clarity that the life of a transaction should be short.  That is a single logical transaction to the application.  I have seen cases where an entire batch update was treated as a single transaction.  This can create many problems that are not easy to diagnose.

I also agree with Fabian that suspending transactions is a path that can lead to complexity in the code that it best to avoiding.  The nature of a transaction is to track EVERYTHING that happens in the transaction.  Messing with that will lead to issues with maintainability and debugging later in the life of the application.

My suggestion is to do your logging to an in-memory global while in the transaction.  Then you can permanently write this after the transction commits or rolls back.   Two things to be sure of in this method: 

  1. always explicitly commit or rollback the transaction.   Don't rely on implied rollback that would occur from a process end
  2. enclose your transaction in a try/catch block.  This will insure that you have the opportunity to commit your logs in the case of a system fault.  This could be an existing try/catch in the program.  However for control and clarity I recommend a separate try/catch for the transaction.

How you commit your log updates will depend on your error handling.  I this example the logs are committed in two places, after a tcommit and in the catch block since the catch block will throw the exception up the stack.  If this did not occur then a simpler approach would be to write the logs out after the catch block.  

Example (just typed it here for example.  Not a running program) with exception passed up the stack

Try {
    set ^LOG($Increment(^LOG)) = "starting a transaction"
    // do some program logic
    // do some logging
    set tLog($Increment(^LOG)) = "some application trace" // note using the still using he ^LOG increment
    // if application error
    throw ##class(%Exception.StatusException).CreateFromStatus(tSC)

    Merge ^LOG = tLog
} catch except {
    Merge ^LOG = tLog
    Throw except

Example with internal error handling only

Try {
    set ^LOG($Increment(^LOG)) = "starting a transaction"
    // do some program logic
    // do some logging
    set tLog($Increment(^LOG)) = "some application trace" // note using the still using he ^LOG increment
    // if application error
    throw ##class(%Exception.StatusException).CreateFromStatus(tSC)

} catch except {
Merge ^LOG = tLog


This looks great.  However I am having a problem getting this to work with a recent pButtons report I received.  The error is a follows.  Any thoughts?

docker container run --rm -v "$(pwd)":/data yape/yape --mgstat --vmstat -c /data/dbmirror2-c_CACHE_20190131_161057_24hours.html
INFO:root:Profile run "24hours" started at 16:10:57 on Jan 31 19.
Traceback (most recent call last):
  File "/usr/local/bin/yape", line 11, in <module>
    load_entry_point('yape', 'console_scripts', 'yape')()
  File "/src/yape/yape/", line 6, in main
  File "/src/yape/yape/", line 248, in yape2
    parsepbuttons(args.pButtons_file_name, db)
  File "/src/yape/yape/", line 382, in parsepbuttons
    StartDateStr = datetime.strptime(StartDateStr, "%b %d %Y").strftime(
  File "/usr/local/lib/python3.7/", line 577, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
  File "/usr/local/lib/python3.7/", line 359, in _strptime
    (data_string, format))
ValueError: time data 'Jan 31 19' does not match format '%b %d %Y'

If that is the case you can use BPL to create a business process with greater control over the orchestration.  You would set it up so that it  sends to each end point synchronously so that the first send has to happen before the second.  Of course if this were the case then the receiving side, with separate end points, would have to be sure that the messages stayed in order.  That is out of your control however.

Well one thing is to be sure that the external database implements a FHIR server and that you have the necessary access information and credentials.   They have to be able to accept the REST call per the FHIR standard.  If this is not in place all is not lost.  You can still use other methods access the external database, the method depends on what options are provided.  You just could not use FHIR.

BTW, if I understand you correctly Health Connect would be acting as a FHIR client in this usage, not a server.


You state that Request.JSON is a simple string.  The %ToJSON() method only exists as part of the DynamicObject and DynamicArray classes.  Actually I am surprised that this is not failing completely before you even send the request because of this.  If your variable already has well formed JSON as the value then you can just write that into the EntityBody.

BTW, When you deal with JSON in the future you may find an advantage in using the Dynamic object handling for JSON.  For example take the following JSON {"NAME":"RICH","STATE":"MA"}

If this is in a variable, say jsonStr, I can load this into a Dynamic Object using the following command:

set obj = {}.%FromJSON(jsonStr)

Now you can use object references to work with the JSON

Write obj.NAME   -> displays RICH

I can add new properties dynamically

set obj.ZIPCODE = "99999"

Finally convert this back to a json string with:

write obj.%ToJSON()

which would display  {"NAME":"RICH","STATE":"MA","ZIPCODE":"99999"}

See the documentation at

I find the easiest way is to log into the CSP Gateway for the web server you are using.  If this is a development machine and you are using the stripped down web server internal to Cache you can access this from the management portal.  The path is System Adminstration -> Configuration -> CSP Gateway Management.  If you looking to do this against the traffic on an external web server then you need to the path to the  On my Windows VM this is

You will need the Web Gateway Management username and password.  The user is typically CSPSystem.  Once logged in look for the View HTTP Trace on the left hand menu.

Click on that and you will see a screen with 'Trace OFF' and 'Trace ON' at the top of the left hand menu.  You will also see options to refresh and clear.  Below that will appear any requests that have been traced. This is probably blank at this time.  Click Trace ON (it should change to RED.  Now go make a request that you want to trace.  Once your request is complete go back and turn off the trace so you don't get a bunch of requests that have nothing to do with what you want to examine.  I did this and made a request for the System Management Portal.  Here is the list I get.

Note that I see two requests.  Only one is what I want to look at which, in my case is the first.  When you select a trace you will see the request a the top followed by the response.  Note if the body of the response is not readable you likely have gzip compression on.  Go to the Application settings in the web gateway and turn this off to actually be able to see the body.  Remember to turn it back on later though.

Here is my results (truncated).  Hope this helps you


I thought I would add another example to this as  I have never needed to use the control wrapper solution.  I found I had to add gzip as an installed package in my docker file.  Below is a docker image built for centos.  The installation is HealthShare rather than Cache, but the that is just a change in the InterSystems installer referenced.   The overall process should work with value that make sense for your environment.  One  very important change is that the entry point should be ccontainermain.  The reason for this is the entry point needs to stay running to keep the container up and running.  Ccontrol will start Cache then end which will actually exit and shutdown the container.  There is a link to where to find this program at

# pull from this repository
# note that if you don't have the distribution you're after it will be automatically
# downloaded from Docker central hub repository (you'll have to create a user there)
FROM centos:latest

# setup vars section___________________________________________________________________
ENV TMP_INSTALL_DIR=/tmp/distrib

# vars for Caché silent install
    ISC_PACKAGE_INSTALLDIR="/opt/intersystems/healthshare" \

# distribution file________________________________________________________________
# set-up and install from distrib_tmp dir
RUN mkdir ${TMP_INSTALL_DIR} && \
    mkdir -p ${ISC_PACKAGE_INSTALLDIR}/mgr

# update OS + dependencies & run silent install___________________________________
RUN yum -y update && \
    yum -y install tar gzip which java-1.8.0-openjdk

ADD HealthShare-2018.1-Exchange_Insight_Index-b7718-lnxrhx64.tar.gz .

RUN ./HealthShare-*/cinstall_silent && \
    rm -rf ${TMP_INSTALL_DIR}/* && \
    ccontrol stop $ISC_PACKAGE_INSTANCENAME quietly 

# TCP sockets that can be accessed if user wants to (see 'docker run -p' flag)
EXPOSE 57772 1972

# container main process PID 1 (
ADD ccontainermain .

ENTRYPOINT  ["/ccontainermain","-cconsole","-i", "HSPI"]


I would go to the "Building Your First HL7 Produciton" learning path in the links I sent earlier.  There are several learning resources listed here.  If you are in  hurry you can skip the introduction components and go direct to the "Integration Architecture" course.  Then follow along with the other courses in order.  I would recommend at least the:

  • HL7 I/O course
  • all three courses under the message router section
  • Data Transformation Basics
  • Practice building data transformations
  • the two courses under troubleshooting would be advisable too
  • Do the Final Exercise.

You can always go back  and review other courses as needed.   Also search our Learning Services area (Learning on the top bar) for other courses and presentations.

You can contact me directly (my email is in my profile)) if you want to take this offline.

Yes that would be a good course. There is a date coming up on April 8th.  In the meantime the online learning can get you started.  You should be able to accomplish quite a bit without any programming. Where you will need to do some is in creating a message class to contain the extracted data that is to be sent to SQL Server and to create the actual SQL operation to update SQL server.  Neither is difficult to do.  I would definitely suggest engaging with your InterSystems Sales Engineer for guidance.

Not to worry.  You don't need to do C# programming or deal with obscure libraries.  With Ensemble you can accomplish much of what you need to do without alot of programming. I would start with the "Build your first HL7 production" learning path to discover how to create an HL7 integration.  There is also Classroom training that you can sign up for.  Check our learning services site for class schedules.

To update SQL Server you would create a Business operation (which will require some coding using ObjectScript) that uses our SQL Outbound adapter to update SQL Server.  Here is a link to that documentation.  Using the SQL Outbound Adapter

Finally, if you need more direct assistance I would recommend engaging with the Sale Engineer assigned to your account.   We're always here to help get you moving in the right direction.


First can I ask which InterSystems product you are working with?  Ensemble, Health Connect, and IRIS for Health all provide tools that make handling of HL7 much easier.  Further these are all interoperability platforms which provide tools for building these types of integrations efficiently and fast.

A further question is what are you intending to do with this HL7 message?  Just put the entire message file contents into SQL Server as a blob or are you looking to pull out specific data to put into columns in a SQL table?  From your further comments I believe it is the latter,  but clarity would help here.

For the moment I make an assumption that you are using either Ensemble or Health Connect and point you at some documentation that can help you.

Ensemble HL7 Version 2 Development Guide

Also some Learning Services links

Integration Architecture

Building Your First HL7 Production learning path

Hope this helps

You don't indicate which product you are using (IRIS, Cache, Ensemble) which may impact on some details.  However the basic code to load the form data and execute the Post call is going to be  the same.

You need an instance of %Net.HTTPRequest.  If you are in Ensemble then you would work through the adapter.  For Cache or IRIS create an instance of this class and configure it for the server end point.

Add your form data to the request by calling the InsertFormData method.

ex. req.InsertFormData("NameFld", "NameValue")

To cause the request to be performed call the Post method

ex.  set status = req.Post(REST resource URL)

If you assign the server and port properties of the request then do not include them in the URL here.  

You can access the response in the HTTPResponse property of the request.  This will be an instance of the %Net.HTTPResponse class.

You can read more on the %Net.Request class in the class reference at 

I see that the ID is not in the export.  However the import does figure out how to match an import to an existing item since it does not  allow you to overwrite that existing records.  Otherwise we would see a duplication of records under new ID's.  

As far as the methods to use I would have to disagree with this recommendation.  A system administrator should not have to write code to maintain the systems under their care.  Documentation is important, however that can be accomplished with a literal document.  Then, failing to have a export/import or enterprise management  function, those changes would manually be done to all effected systems.  Writing code is less clear to everyone and is no less work.   

Let me clarify.  this has to do with the ExportTasks and ImportTasks methods of %SYS.Task class.  I need to know the qspec options that have impact on these processes.

as to question 2. The process is that they are setting up a new server for backup and want to replicate what they have setup for the current server.   Exporting and importing what is currently present is the best way.  If they are going to write a program then they could just as well  compare each existing task and do the changes manually.   There is an ongoing maintenance side to this which would also be better done with an export an import.

So to the original question is there anyway to tell ImportTasks to override the tasks that exist?