Created
August 11, 2023 23:44
-
-
Save richdrummer33/2b5ede2b5878e5264a92f182a5916912 to your computer and use it in GitHub Desktop.
A Unity script that automatically emails player logs when the game is exited (build and editor compatible)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Net; | |
using System.Net.Mail; | |
using System.IO; | |
using UnityEngine; | |
using Sirenix.OdinInspector; | |
using System.Linq; | |
using System.Collections; | |
#if UNITY_EDITOR | |
using UnityEditor; | |
#endif | |
/// <summary> | |
/// | |
/// WHO? Richard Beare 2023 | |
/// WHAT? This script copies the player log file and sends it to an email address. | |
/// WHERE? This script should be attached to a gameobject in the scene that the game exits from. | |
/// WHEN? When the game exits. | |
/// WHY? Ever struggled to get a player log file from a user? Say goodbye to that problem! This script will deliver it right to your inbox! | |
/// HOW? This script copies the player log file and then sends it to an email address. | |
/// | |
/// NOTE: You will need to enable less secure apps on the "from" gmail account for this to work: https://myaccount.google.com/lesssecureapps | |
/// RECOMMENDATION: Make a new gmail account for this purpose. | |
/// | |
/// </summary> | |
public class SendPlayerLogs : MonoBehaviour | |
{ | |
private static string _toEmail = "[email protected]"; // anyone | |
private static string _fromEmail = "[email protected]"; // the gmail account that needs rights to use this app. It has rights. But you can remove them: https://myaccount.google.com/lesssecureapps | |
private static string _fromEmailPassword = "abcdefgwhospeepinmypassword"; | |
[SerializeField][ReadOnly] static string _logDirPath = ""; | |
[SerializeField][ReadOnly] static string _logFilePath = ""; | |
[SerializeField][ReadOnly] static string _appZipName = ""; | |
[SerializeField][ReadOnly] static string _pathToBuild = ""; | |
[SerializeField][ReadOnly] static string _appCreationDate = ""; | |
[SerializeField][ReadOnly] string _companyName = ""; | |
[SerializeField][ReadOnly] string _productName = ""; | |
[SerializeField][ReadOnly] static string _gameVersion = ""; | |
[SerializeField][ReadOnly] static string _unityVersion = ""; | |
static int _maxNumberOfCopies = 20; | |
static bool _sendAttempted; | |
#if UNITY_EDITOR | |
private void OnValidate() | |
{ | |
if (Application.isPlaying) return; | |
if (string.IsNullOrEmpty(_companyName)) | |
{ | |
_companyName = Application.companyName; | |
_productName = Application.productName; | |
} | |
SetLogPath(); | |
} | |
#endif | |
void Start() | |
{ | |
_companyName = Application.companyName; | |
_productName = Application.productName; | |
_sendAttempted = false; | |
SetLogPath(); | |
} | |
void SetLogPath() | |
{ | |
if (Application.platform == RuntimePlatform.WindowsEditor) | |
{ | |
string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | |
_logDirPath = Path.Combine(userProfilePath, "AppData", "LocalLow", _companyName, _productName); | |
_logFilePath = Path.Combine(userProfilePath, "AppData", "LocalLow", _companyName, _productName, "Player.log"); | |
} | |
else if (Application.platform == RuntimePlatform.WindowsPlayer) | |
{ | |
string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | |
_logDirPath = Path.Combine(userProfilePath, "AppData", "LocalLow", _companyName, _productName); | |
_logFilePath = Path.Combine(userProfilePath, "AppData", "LocalLow", _companyName, _productName, "Player.log"); | |
} | |
else if (Application.platform == RuntimePlatform.OSXPlayer) | |
{ | |
_logFilePath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "Library/Logs/" + _companyName + "/" + _productName + "/Player.log"); | |
} | |
else | |
{ | |
Debug.LogError("[BackupLogFile] Unsupported platform"); | |
} | |
try | |
{ | |
_appZipName = Application.dataPath.Substring(Application.dataPath.LastIndexOf('/') + 1); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogWarning("[BackupLogFile] Failed to get exe directory name: " + e.Message); | |
} | |
string exePath = ""; | |
try | |
{ | |
exePath = Path.GetDirectoryName(Application.dataPath) + Path.DirectorySeparatorChar + Application.productName + ".exe"; | |
} | |
catch (Exception e) | |
{ | |
Debug.LogWarning("[BackupLogFile] Failed to get exe path: " + e.Message); | |
} | |
try | |
{ | |
if (exePath.Length != 0 && File.Exists(exePath)) | |
{ | |
_pathToBuild = exePath; | |
_appCreationDate = File.GetCreationTime(exePath).ToString("yyyy-MM-dd HH:mm:ss"); | |
} | |
} | |
catch (Exception e) | |
{ | |
Debug.LogWarning("[BackupLogFile] Failed to get app creation date: " + e.Message); | |
} | |
// game and unity version | |
_gameVersion = Application.version; | |
_unityVersion = Application.unityVersion; | |
Debug.Log("[BackupLogFile] logfile path:\n" + _logFilePath); | |
#if UNITY_EDITOR | |
UnityEditor.EditorApplication.playModeStateChanged += EditorPlayModeChanged; | |
#else | |
Application.wantsToQuit += HandleAppWantsToQuit; | |
#endif | |
} | |
#if UNITY_EDITOR | |
private static void EditorPlayModeChanged(UnityEditor.PlayModeStateChange state) | |
{ | |
// if this is not the unity player, then don't send the log file | |
if (Application.platform != RuntimePlatform.WindowsPlayer && Application.platform != RuntimePlatform.OSXPlayer) | |
{ | |
return; | |
} | |
if (state == UnityEditor.PlayModeStateChange.ExitingPlayMode) | |
{ | |
StartAsyncEmail(); | |
} | |
} | |
#else | |
static bool HandleAppWantsToQuit() | |
{ | |
if(_sendAttempted) return true; | |
Debug.Log("[BackupLogFile] HandleAppWantsToQuit"); | |
Application.wantsToQuit -= HandleAppWantsToQuit; | |
StartAsyncEmail(); | |
return false; | |
} | |
#endif | |
// button that sends the log file for debug testing | |
[Button] | |
public void _Button_TestSendLogFile() | |
{ | |
try | |
{ | |
// example output: 2019-12-31 23.59.59 | |
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss"); | |
SendEmailWithLogFile(CopyLog(), timestamp); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError("[BackupLogFile] Failed to send email: " + e.Message); | |
} | |
} | |
static async void StartAsyncEmail() | |
{ | |
try | |
{ | |
// When are we!? Now!... What time is it!? Now!... Where are we!? ... Just send the log file! | |
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); | |
// Makes a copy of the logfile | |
Debug.Log("[BackupLogFile] Copying logfile as backup at time " + timestamp); | |
string c_log = CopyLog(); | |
Debug.Log("[BackupLogFile] Posting log at time " + timestamp); | |
// 7.5 second timeout | |
await Task.Run(() => SendEmailWithLogFile(c_log, timestamp), new CancellationTokenSource(7500).Token); | |
_sendAttempted = true; | |
// Either it was sent, or it timed out | |
Application.Quit(); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError("[BackupLogFile] Failed to send email: " + e.Message); | |
} | |
} | |
IEnumerator PostLogTimeout() | |
{ | |
yield return new WaitForSeconds(5f); | |
Debug.Log("[BackupLogFile] Copying logfile as backup: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); | |
string c_log = CopyLog(); | |
SendEmailWithLogFile(c_log, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); | |
} | |
static string CopyLog() | |
{ | |
try | |
{ | |
string logDirectory = Path.GetDirectoryName(_logFilePath); | |
string logFileName = Path.GetFileNameWithoutExtension(_logFilePath); | |
string logExtension = ".log"; | |
// Get all log files in the log path | |
string[] logFiles = Directory.GetFiles(logDirectory, $"{logFileName}.log") | |
.Concat(Directory.GetFiles(logDirectory, $"{logFileName}_*.log")) | |
.Concat(Directory.GetFiles(logDirectory, $"{logFileName} (*).log")) | |
.ToArray(); | |
// Count the number of log files | |
int logCount = logFiles.Length; | |
// Check if the log count exceeds 15 | |
if (logCount > 15) | |
{ | |
// Find the oldest log file based on the date of creation | |
string oldestLogPath = logFiles.Select(f => new FileInfo(f)).OrderBy(f => f.CreationTime).First().FullName; | |
// Delete the oldest log file | |
File.Delete(oldestLogPath); | |
} | |
// Generate a new log file name with the current system date-time in PST | |
string currentDateTime = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss"); | |
string logCopyPath = Path.Combine(logDirectory, $"{logFileName} ({currentDateTime}){logExtension}"); | |
// Create a copy of the log file | |
File.Copy(_logFilePath, logCopyPath, true); | |
return logCopyPath; | |
} | |
catch (Exception e) | |
{ | |
Debug.LogError("[SendLogFile] Failed to copy log file: " + e.Message); | |
return null; | |
} | |
} | |
static void SendEmailWithLogFile(string logPath, string timestamp) | |
{ | |
MailMessage mail = new MailMessage(_fromEmail, _toEmail); | |
// E.g. "Log File (USER: user | BUILD: 1.0.0 | ZIP_NAME: GameName)" | |
mail.Subject = "Unseen Player Log (USER: " + Environment.UserName + " | BUILD: " + _gameVersion + " | ZIP_NAME: " + _appZipName + ")"; | |
mail.Body = "See attached log file.\n\nSystem Info:"; | |
mail.Body += "\n\nUsername: " + Environment.UserName; | |
mail.Body += "\nPath to Build: " + _pathToBuild; | |
mail.Body += "\nBuild Creation Date: " + _appCreationDate; | |
mail.Body += "\nGame Version: " + _gameVersion; | |
mail.Body += "\nUnity Version: " + _unityVersion; | |
Attachment attachment = new Attachment(logPath); | |
mail.Attachments.Add(attachment); | |
SmtpClient smtpServer = new SmtpClient("smtp.gmail.com"); | |
smtpServer.Port = 587; | |
smtpServer.Credentials = new NetworkCredential(_fromEmail, _fromEmailPassword) as ICredentialsByHost; | |
smtpServer.EnableSsl = true; | |
ServicePointManager.ServerCertificateValidationCallback = | |
delegate (object s, System.Security.Cryptography.X509Certificates.X509Certificate certificate, | |
System.Security.Cryptography.X509Certificates.X509Chain chain, | |
System.Net.Security.SslPolicyErrors sslPolicyErrors) | |
{ return true; }; | |
smtpServer.Send(mail); | |
Debug.Log("[BackupLogFile] ^^^ Log posted ^^^ " + timestamp); | |
} | |
// OnDisable is called when the object is destroyed | |
private void OnDisable() | |
{ | |
#if UNITY_EDITOR | |
UnityEditor.EditorApplication.playModeStateChanged -= EditorPlayModeChanged; | |
#endif | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment