How to log objects to a console with AppleScript

Applescript

Applescript Problem Overview


I am just trying to log the state of objects throughout the life of my applescript. In other languages, object's toString() methods will render the text equivalent and I can use those. In AppleScript, this does not seem to be the case.

convert applescript object to string (similar to toString)

Will output the finder object (and its properties) to the "Results" window of AppleScript Editor, but only if it is the last statement which gets executed.

If I have a trace() statement (which takes a message for logging purposes):

on trace(message)
do shell script "cat >>~/log/applescript.txt <<END_OF_THE_LOG
" & (message as text) & "
END_OF_THE_LOG"
end trace

and try to log the same object, I get

Can’t make properties of application "Finder" into type text.

I'm open to better ways of logging to a console, but would like to find out how to write an object's properties (like AppleScript Editor does) in the middle of a script for testing either way.

Applescript Solutions


Solution 1 - Applescript

AppleScript doesn't make it easy:

  • log only logs while running in AppleScript Editor or when running via osascript (to stderr in that case) - the output will be lost in other cases, such as when applications run a script with the NSAppleScript Cocoa class.

  • log only accepts one argument; while it does accept any object type, it doesn't make it easy to get a meaningful representation of non-built-in types: try log me to get information about the script itself, for instance; frequently, log (get properties of <someObj>) must be used to get meaningful information; note the cumbersome syntax, which is required, because just using log properties of <someObj> typically merely prints the name of the reference form instead of the properties it points to (e.g, log properties of me uselessly outputs just (*properties*)).

  • In general, AppleScript makes it very hard to get meaningful text representations of objects of non-built-in types: <someObj> as text (same as: <someObj> as string) annoyingly breaks - throws a runtime error - for such objects; try me as text.

Below are helper subroutines that address these issues:

  • dlog() is a subroutine that combines deriving meaningful text representations of any objects with the ability to write to multiple log targets (including syslog and files) based on a global config variable.
  • toString() (effectively embedded in dlog()) is a subroutine that takes a single object of any type and derives a meaningful text representation from it.

Tip of the hat to @1.61803; his answer provided pointers for implementing the various logging targets.

Examples:

  # Setup: Log to syslog and a file in the home dir.
  #        Other targets supported: "log", "alert"
  #        Set to {} to suppress logging.
set DLOG_TARGETS to { "syslog", "~/as.log" } 
  # Log properties of the front window of frontmost application.
dlog(front window of application (path to frontmost application as text))
  # Log properties of own front window; note the *list* syntax for multiple args.
dlog({"my front window: ", front window})

  # Get properties of the running script as string.
toString(me) # ->, e.g.: [script name="sandbox"] {selection:insertion point after character 2475 of text of document "sandbox2.scpt", frontmost:true, class:application, name:"AppleScript Editor", version:"2.6"}

See the source-code comments above each subroutine for details.


dlog() source code

	# Logs a text representation of the specified object or objects, which may be of any type, typically for debugging.
	# Works hard to find a meaningful text representation of each object.
	# SYNOPSIS
	#   dlog(anyObjOrListOfObjects)
	# USE EXAMPLES
	#   dlog("before")  # single object
	#	  dlog({ "front window: ", front window }) # list of objects
	# SETUP
	#   At the top of your script, define global variable DLOG_TARGETS and set it to a *list* of targets (even if you only have 1 target).
	#     set DLOG_TARGETS to {} # must be a list with any combination of: "log", "syslog", "alert", <posixFilePath>
	#   An *empty* list means that logging should be *disabled*.
	#   If you specify a POSIX file path, the file will be *appended* to; variable references in the path
	#   are allowed, and as a courtesy the path may start with "~" to refer to your home dir.
	#   Caveat: while you can *remove* the variable definition to disable logging, you'll take an additional performance hit.
	# SETUP EXAMPLES
	#    For instance, to use both AppleScript's log command *and* display a GUI alert, use:
	#       set DLOG_TARGETS to { "log", "alert" }
	# Note: 
	#   - Since the subroutine is still called even when DLOG_TARGETS is an empty list, 
	#     you pay a performancy penalty for leaving dlog() calls in your code.
	#   - Unlike with the built-in log() method, you MUST use parentheses around the parameter.
	#   - To specify more than one object, pass a *list*. Note that while you could try to synthesize a single
	#     output string by concatenation yourself, you'd lose the benefit of this subroutine's ability to derive
	#     readable text representations even of objects that can't simply be converted with `as text`.
	on dlog(anyObjOrListOfObjects)
		global DLOG_TARGETS
		try
			if length of DLOG_TARGETS is 0 then return
		on error
			return
		end try
		# The following tries hard to derive a readable representation from the input object(s).
		if class of anyObjOrListOfObjects is not list then set anyObjOrListOfObjects to {anyObjOrListOfObjects}
		local lst, i, txt, errMsg, orgTids, oName, oId, prefix, logTarget, txtCombined, prefixTime, prefixDateTime
		set lst to {}
		repeat with anyObj in anyObjOrListOfObjects
			set txt to ""
			repeat with i from 1 to 2
				try
					if i is 1 then
						if class of anyObj is list then
							set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}} # '
							set txt to ("{" & anyObj as string) & "}"
							set AppleScript's text item delimiters to orgTids # '
						else
							set txt to anyObj as string
						end if
					else
						set txt to properties of anyObj as string
					end if
				on error errMsg
					# Trick for records and record-*like* objects:
					# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
					try
						set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
					end try
				end try
				if txt is not "" then exit repeat
			end repeat
			set prefix to ""
			if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
				set prefix to "[" & class of anyObj
				set oName to ""
				set oId to ""
				try
					set oName to name of anyObj
					if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
				end try
				try
					set oId to id of anyObj
					if oId is not missing value then set prefix to prefix & " id=" & oId
				end try
				set prefix to prefix & "] "
				set txt to prefix & txt
			end if
			set lst to lst & txt
		end repeat
		set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {" "}} # '
		set txtCombined to lst as string
		set prefixTime to "[" & time string of (current date) & "] "
		set prefixDateTime to "[" & short date string of (current date) & " " & text 2 thru -1 of prefixTime
		set AppleScript's text item delimiters to orgTids # '
		# Log the result to every target specified.
		repeat with logTarget in DLOG_TARGETS
			if contents of logTarget is "log" then
				log prefixTime & txtCombined
			else if contents of logTarget is "alert" then
				display alert prefixTime & txtCombined
			else if contents of logTarget is "syslog" then
				do shell script "logger -t " & quoted form of ("AS: " & (name of me)) & " " & quoted form of txtCombined
			else # assumed to be a POSIX file path to *append* to.
				set fpath to contents of logTarget
				if fpath starts with "~/" then set fpath to "$HOME/" & text 3 thru -1 of fpath
				do shell script "printf '%s\\n' " & quoted form of (prefixDateTime & txtCombined) & " >> \"" & fpath & "\""
			end if
		end repeat
	end dlog

toString() source code

	# Converts the specified object - which may be of any type - into a string representation for logging/debugging.
	# Tries hard to find a readable representation - sadly, simple conversion with `as text` mostly doesn't work with non-primitive types.
	# An attempt is made to list the properties of non-primitive types (does not always work), and the result is prefixed with the type (class) name
	# and, if present, the object's name and ID.
	# EXAMPLE
	#		toString(path to desktop)  # -> "[alias] Macintosh HD:Users:mklement:Desktop:"
	# To test this subroutine and see the various representations, use the following:
	#   repeat with elem in {42, 3.14, "two", true, (current date), {"one", "two", "three"}, {one:1, two:"deux", three:false}, missing value, me,  path to desktop, front window of application (path to frontmost application as text)}
	#	    log my toString(contents of elem)
	#   end repeat
	on toString(anyObj)
		local i, txt, errMsg, orgTids, oName, oId, prefix
		set txt to ""
		repeat with i from 1 to 2
			try
				if i is 1 then
					if class of anyObj is list then
						set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}}
						set txt to ("{" & anyObj as string) & "}"
						set AppleScript's text item delimiters to orgTids # '
					else
						set txt to anyObj as string
					end if
				else
					set txt to properties of anyObj as string
				end if
			on error errMsg
				# Trick for records and record-*like* objects:
				# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
				try
					set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
				end try
			end try
			if txt is not "" then exit repeat
		end repeat
		set prefix to ""
		if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
			set prefix to "[" & class of anyObj
			set oName to ""
			set oId to ""
			try
				set oName to name of anyObj
				if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
			end try
			try
				set oId to id of anyObj
				if oId is not missing value then set prefix to prefix & " id=" & oId
			end try
			set prefix to prefix & "] "
		end if
		return prefix & txt
	end toString

Solution 2 - Applescript

Just use the log statement in AppleScript Editor. When you view the results in Applescript Editor, at the bottom of the window press the "Events" button. Normally the "Results" button is pressed and then you only see the result of the last statement as you mention. So change the button to "Events". This will show you everything that is happening as the script runs, and additionally all of the log statements that you put throughout the code too. Note that the log statements do not have to be text. You can log any object.

This is the best way to debug your script and see what is happening. As an example try this and look at the "Events". Realistically thought you don't need a lot of log statements if you view the Events because everything is already logged!

set someFolder to path to desktop
log someFolder

tell application "Finder"
	set someItem to first item of someFolder
	log someItem
	
	set itemProps to properties of someItem
	log itemProps
end tell

Solution 3 - Applescript

Try any of the following:

# echo to file
do shell script "echo " & quoted form of (myObj as string) & ¬
    " > ~/Desktop/as_debug.txt"

# write to file
set myFile to open for access (path to desktop as text) & ¬
    "as_debug2.txt" with write permission
write myObj to myFile
close access myFile

# log to syslog
do shell script "logger -t 'AS DEBUG' " & myObj

# show dialog
display dialog "ERROR: " & myObj

If what you're trying to log is not text, you might try:

quoted form of (myObj as string)

Solution 4 - Applescript

Here are examples of console log:

set my_int to 9945
log my_int
set my_srt to "Hamza"
log my_srt 
set my_array ["Apple","Mango","Banana","Gava"]
log my_array
set my_obj to {"Ali"} as string
log my_obj

Solution 5 - Applescript

do shell script "echo '" & (current date) & ": Found " & Thisfilename & "' >> ~/logs/MyGreatAppleScript.log"

Solution 6 - Applescript

Similar to toString()...

on TextOf(aVariable)
	try
		return "" & aVariable
	on error errm
		if errm begins with "Can’t make " ¬
	   and errm ends with " into type Unicode text." then ¬
			return text 12 through -25 of errm
		return "item of class " & (class of aVariable) & return & errm
	end try
end TextOf

Solution 7 - Applescript

Easiest way to know your values-

display dialog "my variable: " & myVariableName

Solution 8 - Applescript

For scripts that run long and I’m not looking at the screen, I like to have applescript say out loud what it’s doing.

Ie.

say “This is a log statement.”

If inside a tell statement:

tell me to say “This is a log statement.”

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionumopView Question on Stackoverflow
Solution 1 - Applescriptmklement0View Answer on Stackoverflow
Solution 2 - Applescriptregulus6633View Answer on Stackoverflow
Solution 3 - Applescript1.61803View Answer on Stackoverflow
Solution 4 - ApplescriptM. Hamza RajputView Answer on Stackoverflow
Solution 5 - Applescriptuser2548919View Answer on Stackoverflow
Solution 6 - ApplescriptGil DawsonView Answer on Stackoverflow
Solution 7 - ApplescriptmeMadhavView Answer on Stackoverflow
Solution 8 - ApplescriptWayland ChinView Answer on Stackoverflow