The RED UI and the Scheduler have different conventions when it comes to script output and Exit Codes. Scripts generated via Enablement Pack templates already handle this difference in output protocol and switch between the two depending on the context of the run determined by the provided environment variables. When running scripts interactively you also generally do not want to write to the audit and detail log tables but instead only display the output. It is best practice in scripts to provide central logging functions and also a script exit function and in these functions handle the different output protocols. There are complete examples of these functions at the end of this section.
Determining the run context
To test at run-time whether the script run context is interactive the following method is used:
When a script is run via RED UI the environment variables for WSL_JOB_KEY and WSL_JOB_NAME will be set as ‘0’ and ‘Develop’ respectively. When run through the Scheduler these environment variables will match the actual Job ID and Job Name.
To test for interactive execution the following sample functions can be used:
Python
def is_red_interactive(): if os.environ.get('WSL_JOB_KEY','') == '0' and os.environ.get('WSL_JOB_NAME','') == 'Develop': return True else: return False
Powershell
function Test-IsInteractive { if (($ENV:WSL_JOB_KEY -eq '0') -and ($ENV:WSL_JOB_NAME -eq 'Develop')) { return $true } else { return $false } }
RED UI Script Output Protocol
For interactive scripts the Exit Code must be 0 always.
The interactive script result is determined by reading the status value from the first line of the standard-out, therefore all standard-out must be suppressed/collected until the final status value and status result messages are written. The second line of standard-out is taken as the status result message. Any further lines of standard-out are considered audit log messages. Any standard-error messages are considered detail/error logs and are printed after the audit logs in the results pane.
Output Channel | Purpose | ||||||||||
Exit Code | Must be ‘0’, non-zero will throw an error dialog and suppress the logging. | ||||||||||
Standard Output | Line 1 - Numeric result code (1,-1,-2,-3)
Line 2 - Result message Lines 3+ - Audit messages | ||||||||||
Standard Error | Lines 1+ - Detail messages |
Scheduler Script Output Protocol
For Scheduler execution of scripts the Exit Code is what determines the actual run status of any script. An Exit Code of ‘0’ (the default when nothing has gone wrong) signals a successful script execution.
Audit, Error, and Detail logging can be output at any time provided each line of output conforms to a certain JSON output protocol defined below. Any output not conforming to the JSON structure will be treated as error/detail logs.
The final result message must also conform to the JSON output protocol.
Output Channel | Purpose | Protocol | ||||||||||||||||
Exit Code | Result Code | 0 - Success 1 (or non-zero) - Error | ||||||||||||||||
Standard Output | Structured messages | UTF-8 Sequence of JSON objects (not a JSON array or objects, just many objects next to each other) Each object represents one message, either audit, detail, or result depending on the contents.
| ||||||||||||||||
Standard Error and Output | Unstructured detail messages | UTF-8 Each line is equivalent to { "type": "detail", "message": "LINE" } |
Example Scheduler Script Output
Standard Output | Standard Error |
{"type":"audit","message":"Starting Load of load_budget"} {"type":"audit","statusCode":"W",message:"Table load_budget is not empty" {"type":"detail","message":"Loading from file \"budget.txt\""} {"type":"audit","message":"Load completed"} {"type":"result","message":"Loaded 11 rows, woohoo!"} | Some message from Some message from other tools |
Example Python Logging Functions
def is_red_interactive(): if os.environ.get('WSL_JOB_KEY','') == '0' and os.environ.get('WSL_JOB_NAME','') == 'Develop': return True else: return False def write_audit(message = '', logType = 'audit', statusCode = 'I'): # statusCodes 'E' = error, 'W' Warning, 'I' information, 'S' Success global interactiveLog if is_red_interactive(): interactiveLog = '\n'.join([interactiveLog, message]) else: outputJson = json.dumps({"type": logType, "message": message, "statusCode": statusCode}) print(outputJson, flush=True) def write_error(message = ''): write_audit(message, 'audit', 'E') def write_detail(message = '', statusCode = 'I'): if debugMode: write_audit(message, 'detail', statusCode) def write_result(message = '', statusCode = 'S'): write_audit(message, 'result', statusCode) def exit_script(exitCode = 0, message = 'Executed the script'): if is_red_interactive(): if exitCode != 0: print(-2, flush=True) else: print(1, flush=True) print(message, flush=True) print(interactiveLog, flush=True) sys.exit(0) else: if exitCode != 0: write_result(message,'E') else: write_result(message,'S') sys.exit(exitCode)
Example PowerShell Logging Functions
function WriteAudit($message, $type="audit", $statusCode="I") { $outputJson = ConvertTo-Json @{message = $message; type = $type; statusCode = $statusCode} -Compress if(Test-IsInteractive){ $logStream.WriteLine($outputJson) } else { [Console]::WriteLine($outputJson) } } function Test-IsInteractive { # determines if the script is being run from RED or a Scheduler if ( ${env:WSL_JOB_KEY} -eq '0' -and ${env:WSL_JOB_NAME} -eq 'Develop' ) { $true } else { $false } } function Exit-Script([int]$scrResCode=1, $scrResMsg="Success") { if(Test-IsInteractive){ $scriptExitCode = 0 [Console]::WriteLine($scrResCode) [Console]::WriteLine($scrResMsg) } elseif($scrResCode -eq 1) { $scriptExitCode = 0 WriteAudit $scrResMsg } else{ $scriptExitCode = $scrResCode if ($scrResCode -eq -1) { WriteAudit $scrResMsg "audit" "W" } else { WriteAudit $scrResMsg "audit" "E" } } Print-Log Exit $scriptExitCode } function Print-Log { {%- br %} $logStream.Dispose(){%- br %} $logReader = New-Object IO.StreamReader($fileAud){%- br %} {%- br %} while( ! $logReader.EndOfStream) { {%- br %} [Console]::WriteLine($logReader.ReadLine()){%- br %} }{%- br %} {%- br %} $logReader.Dispose(){%- br %} }