Script: Create mail with attached log files

Reading the Finding Crash and Hang logs thread made me recognize that I almost never report an issue when it occurs (and most often fail to do it later). While this …

… of course is true, it always seemed too much interruption (and until yesterday I didn’t know that the log files exist, thank you very much @mwra!).

This AppleScript

  • gets
    • the .crash file
    • the .ips file
    • the .hang file
    • the .diag file
  • creates a sample
  • creates a mail with a zip of all files

Now it’s just

  • running the script (e.g. via Alfred or Keyboard Maestro)
  • adding in the mail
    • what happened before the issue occurred
    • whether it’s reproducible
    • the steps to reproduce
  • sending the mail

That’s it!

-- Create mail with attached log files

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

property theApp_Name : "Tinderbox"
property theAddress : "tinderbox@eastgate.com"
property launchAppToCreateSample : true -- If app isn't running (e.g. after force quit) launch it and create sample. 
property theSeconds : 3600

------------------------------------------------------------- Get display name --------------------------------------------------------------

set theApp_DisplayName to do shell script "appPath=$(find \"/Applications\" -name \"" & theApp_Name & "*.app\" -maxdepth 1 | sort --version-sort | tail -1) ; mdls -name kMDItemDisplayName -raw \"${appPath}\""

------------------------------------------------------------------ Get paths -------------------------------------------------------------------

set thePaths to {}

set theLogCrash_Path to do shell script "find ${HOME}\"/Library/Logs/DiagnosticReports\" -name \"" & theApp_DisplayName & "_*.crash\" | sort | tail -1"
if theLogCrash_Path ≠ "" and my checkCreationDate(theLogCrash_Path, theSeconds) then set end of thePaths to theLogCrash_Path

set theLogIps_Path to do shell script "find ${HOME}\"/Library/Logs/DiagnosticReports\" -name \"" & theApp_DisplayName & "*.ips\" | sort | tail -1"
if theLogIps_Path ≠ "" and my checkCreationDate(theLogIps_Path, theSeconds) then set end of thePaths to theLogIps_Path

set theLogHang_Path to do shell script "find \"/Library/Logs/DiagnosticReports\" -name \"" & theApp_DisplayName & "_*.hang\" | sort | tail -1"
if theLogHang_Path ≠ "" and my checkCreationDate(theLogHang_Path, theSeconds) then set end of thePaths to theLogHang_Path

set theLogDiag_Path to do shell script "find \"/Library/Logs/DiagnosticReports\" -name \"" & theApp_DisplayName & "_*.diag\" | sort | tail -1"
if theLogDiag_Path ≠ "" and my checkCreationDate(theLogDiag_Path, theSeconds) then set end of thePaths to theLogDiag_Path


if thePaths ≠ {} then
	
	--------------------------------------------------------------- Create sample ----------------------------------------------------------------
	
	set theSample_ShellPath to "${HOME}/Desktop/" & theApp_DisplayName & " - Sample " & (do shell script "date \"+%Y-%m-%d% %H.%M.%S\"") & ".txt"
	
	if (my isRunning(theApp_DisplayName)) then
		display notification "Creating sample …"
		set theSample_Path to do shell script "samplePath=\"" & theSample_ShellPath & "\" ; sample " & quoted form of theApp_DisplayName & " -file \"${samplePath}\" >/dev/null 2>&1 ; echo \"${samplePath}\""
		set end of thePaths to theSample_Path
	else if launchAppToCreateSample then
		display notification "Creating sample …"
		set theSample_Path to do shell script "samplePath=\"" & theSample_ShellPath & "\" ; open -a " & quoted form of theApp_DisplayName & " ; sample " & quoted form of theApp_DisplayName & " -wait -file \"${samplePath}\" ; echo \"${samplePath}\""
		set end of thePaths to theSample_Path
	end if
	
	------------------------------------------------------------------ Create zip -------------------------------------------------------------------
	
	set theName to theApp_DisplayName & " - Report - " & (do shell script "date \"+%Y-%m-%d% %H.%M.%S\"")
	set theZIP_Path to (POSIX path of (path to desktop)) & theName & ".zip"
	set thePaths_string to "'" & my tid(thePaths, "'" & space & "'") & "'"
	do shell script "zip -j " & quoted form of theZIP_Path & space & thePaths_string
	
	----------------------------------------------------------------- Create mail ------------------------------------------------------------------
	
	tell application "Mail"
		try
			activate
			set theMessage to make new outgoing message with properties {subject:theName, visible:true}
			tell theMessage
				make new to recipient at end of to recipients with properties {address:theAddress}
				make new attachment with properties {file name:(POSIX file theZIP_Path as alias)}
			end tell
			
		on error error_message number error_number
			if the error_number is not -128 then display alert "Mail" message error_message as warning
			return
		end try
	end tell
	
else
	
	tell application "SystemUIServer"
		activate
		display alert "No " & theApp_DisplayName & " logs found!" buttons {"Ok"} message "" as critical
		return
	end tell
end if

on isRunning(theProcessName)
	try
		tell application "System Events"
			try
				set theProcess to first application process whose name = theProcessName
				return true
			on error
				return false
			end try
		end tell
	on error error_message number error_number
		activate
		display alert "Error: Handler \"isRunning\"" message error_message as warning
		error number -128
	end try
end isRunning

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

on checkCreationDate(thePath, theSeconds)
	try
		set {success, theCreationDate, theError} to (current application's |NSURL|'s fileURLWithPath:thePath)'s getResourceValue:(reference) forKey:(current application's NSURLCreationDateKey) |error|:(reference)
		if theError ≠ missing value then error (theError's localizedDescription() as string)
		if (theCreationDate's compare:(current application's NSDate's dateWithTimeIntervalSinceNow:-theSeconds)) ≥ 0 then
			true
		else
			false
		end if
	on error error_message number error_number
		activate
		if the error_number is not -128 then display alert "Error: Handler \"checkCreationDate\"" message error_message as warning
		error number -128
	end try
end checkCreationDate


(@eastgate, not sure whether all files are actually useful, especially as the script simply uses the newest files it finds, i.e. it will probably often send old files that are not related to the current issue. Also not sure about the sample, e.g. duration etc., never used that before.)

1 Like

Crash logs are really helpful. The others are occasionally helpful.

A a rule of thumb, copy the text of the crash message whenever it appears and email it to tinderbox@eastgate.com. Ideally, begin with a sentence about what you were doing when it happened. I don’t think that’s that severe an interruption, given that you’ve already been interrupted.

The problem is: in years of using Tinderbox I can’t recall ever seeing a crash report. If something goes wrong then Tinderbox beachballs until I finally force quit it, or it quits immediately.

A few minutes ago it crashed after using a shortcut (which might very well have something to do with my mac setup). There was no crash message. So I used the script for the first time and before sending I unzipped the archive to see whether it really works. There was only the script generated sample file included (because I deleted old log files a few hours ago), but I also checked the log folders and found a brand new .ips file. No idea what that is, but it clearly was created after the crash. Already added it in the script.

It of course wouldn’t be, but I don’t get any crash messages. Without any info it always seemed pointless to me to write “I did x. Then y happened. Can’t reproduce it.”. That probably wouldn’t have been useful.

In the abstruse vernacular of software, this isn’t a crash. It’s a hang.

Crashes happen when the program tries to do something it is not permitted to do — typically, referencing memory or executing instructions in places it is not permitted to go — or when an exception occurs that the program cannot handle. After this happens, you’ll see a dialog window that says “XXXX has terminated unexpectedly.” That’s window has the crash log.

What you describe is a hang — the program is “hung up” and has stopped responding. If this persists for eight seconds, the cursor changes to the “spinning pizza of doom.” After ~ 30 seconds, the system writes a “spin log”, which is the .ips file you saw. These are sometimes useful, but they’re often intractable.

Hangs in Tinderbox today are typically deadlocks: two parts of Tinderbox want the same resource, and they are both waiting for the other to finish its work. Many of these are very hard to reproduce, simply because they only happen if two things occur at exactly the right time.

1 Like

This gave me pause for thought. Both as a beta tester and one prone to trying things that with hindsight I should not have, I definitely see crashes where the crash reporter dialog is never invoked.

Often if the dialog appears there’s a noticeable delay before the OS decides to show it. My presumption is sometimes we saw of the branch upon which we are sitting so quickly the OS doesn’t ‘see’ the event and show the dialog. Conversely, I do get a crash log (in the described location) regardless.

This is not to gainsay @eastgate’s general description, but I do find the OS reporting to be a bit sleepy. As this has happened with other apps (and not on an overloaded/slow Mac) I’m guessing its an OS and not app issue.

Bottom line: if Tinderbox suddenly dies, go look for the crash log folder (now you know where) if the carsh reporter dialog doesn’t show!

Also don’t wonder “should I send a log?”. @eastgate will politely inform you ‘enough logs!’ if the source of he issue has been identified. Conversely, not giving the developer possible insight on an unintended outcome is not in the user’s interest. :slight_smile:

Also, IIRC, the default button on the crash reporter sends the info to Apple but not necessarily directly to the developer. Thus I normally cancel the dialog and go get the .IPS file via the Finder.

1 Like

Updated the script. It now only sends log files whose creation date is in the last hour. And samples are only created if a new log file was found. This way it will either create a mail (if there’s a new log file) or it shows a dialog “No Tinderbox 9 logs found!”.

1 Like