Skip to content

Instantly share code, notes, and snippets.

@richdrummer33
Created August 11, 2023 23:44
Show Gist options
  • Save richdrummer33/2b5ede2b5878e5264a92f182a5916912 to your computer and use it in GitHub Desktop.
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)
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 = "sentTOthisemail@whosmail.com"; // anyone
private static string _fromEmail = "sentFROMthisemail@whosmail.com"; // 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