日志:打印技巧

news2025/2/26 9:54:46

一、概览

Unity日志打印技巧

  • 常规日志打印
  • 彩色日志
  • 日志存储与上传
  • 日志开关
  • 日志双击溯源

二、常规日志打印

1、打印Hello World

调用堆栈可以很好的帮助我们定位问题,特别是报错的Error日志

Debug.Log("Hello World");

Debug.Log("This is a log message.");
Debug.LogWarning("This is a warning message!");
Debug.LogError("This is an error message!");

注:这里特别说一下,Console窗口有个Error Pause按钮,意思是如果输出了Error日志,则暂停运行,有时候策划会跑过来说他的Unity运行游戏的时候突然卡死了,感觉就像发现了什么惊天大BUG,其实是他点了Error Pause,然后游戏中输出了一句Error日志。

2、打印任意类型的数据

事实上,Debug.Log的参数是object(即System.Object),

// Debug.cs

public static void Log(object message);

现在,我们自定义一个类,比如:

public class TestClass
{
    public int a;
    public string b;
    public bool c;

    public TestClass(int a, string b, bool c)
    {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}
Debug.Log(new TestClass(1, "HaHa", true)); //TestClass

事实上,它是先执行了对象的ToString()方法,然后再输出日志的,我们override(重写)类的ToString()方法,就可以自定义输出啦,比如:

public class TestClass
{
    public int a;
    public string b;
    public bool c;

    public TestClass(int a, string b, bool c)
    {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    
	// 重写ToString方法
    public override string ToString()
    {
        return string.Format("a:{0}\nb:{1}\nc:{2}", a, b, c);
    }
}
Debug.Log(new TestClass(1, "HaHa", true));
//它输出的就是

a:1
b:HaHa
c:True

3、context参数干嘛的

如果你看Debug类的源码,就会发现,它有一个接收两个参数的Debug.Log方法,这第二个参数context可以帮我们定位到物体的实例

// Debug.cs

public static void Log(object message, Object context);
GameObject go = new GameObject("go");
Debug.Log("Test", go); 

如果你的物体是一个还没实例化的预设的引用,则它会直接定位到Project视图中的资源,我们来做下实验。NewBehaviourScript.cs脚本代码如下:

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public GameObject cubePrefab;

    void Start()
    {
        Debug.Log("Test", cubePrefab);
    }
}

挂到Main Camera上,把Cube.prefab预设拖给脚本的cubePrefab成员,如下:

运行,测试效果如下:

4、格式化输出

int a = 100;
float b = 0.6f;
Debug.LogFormat("a is: {0}, b is {1}", a, b);

二、彩色日志打印

我们上面打印出来的日志都是默认的颜色(白色),事实上,我们可以打印出彩色的日志,
格式<color=#rbg颜色值>xxx</color>,例:

Debug.LogFormat("This is <color=#ff0000>{0}</color>", "red");
Debug.LogFormat("This is <color=#00ff00>{0}</color>", "green");
Debug.LogFormat("This is <color=#0000ff>{0}</color>", "blue");
Debug.LogFormat("This is <color=yellow>{0}</color>", "yellow");

三、日志存储与上传

实际项目中,我们一般是需要把日志写成文件,方便出错时通过日志来定位问题。Unity提供了一个事件:Application.logMessageReceived,方便我们来监听日志打印,这样我们就可以把日志的文本内容写到文件里存起来啦~

1、打印日志事件

我们一般在游戏启动的入口脚本的Awake函数中去监听Application.logMessageReceived事件,如下:

// 游戏启动的入口脚本

void Awake()
{
	// 监听日志回调
    Application.logMessageReceived += OnLogCallBack;
}

/// <summary>
/// 打印日志回调
/// </summary>
/// <param name="condition">日志文本</param>
/// <param name="stackTrace">调用堆栈</param>
/// <param name="type">日志类型</param>
private void OnLogCallBack(string condition, string stackTrace, LogType type)
{ 
    // TODO 写日志到本地文件
}

2、写日志到本地文件

Unity提供了一个可读写的路径给我们访问:Application.persistentDataPath,我们可以把日志文件存到这个路径下。

注:Application.persistentDataPath在不同平台下的路径:
Windows:C:/Users/用户名/AppData/LocalLow/CompanyName/ProductName
Android:Android/data/包名/files
Mac:/Users/用户名/Library/Caches/CompanyName/ProductName
iOS:/var/mobile/Containers/Data/Application/APP名称/Documents
需要注意,iOS的需要越狱并且使用Filza软件才能查看文件路径哦

例:

using System.IO;
using System.Text;
using UnityEngine;

public class Main: MonoBehaviour
{
	// 使用StringBuilder来优化字符串的重复构造
    StringBuilder m_logStr = new StringBuilder();
    // 日志文件存储位置
    string m_logFileSavePath;

    void Awake()
    {
    	// 当前时间
        var t = System.DateTime.Now.ToString("yyyyMMddhhmmss");
        m_logFileSavePath = string.Format("{0}/output_{1}.log", Application.persistentDataPath, t);
        Debug.Log(m_logFileSavePath);
        Application.logMessageReceived += OnLogCallBack;
        Debug.Log("日志存储测试");
    }

    /// <summary>
    /// 打印日志回调
    /// </summary>
    /// <param name="condition">日志文本</param>
    /// <param name="stackTrace">调用堆栈</param>
    /// <param name="type">日志类型</param>
    private void OnLogCallBack(string condition, string stackTrace, LogType type)
    {
        m_logStr.Append(condition);
        m_logStr.Append("\n");
        m_logStr.Append(stackTrace);
        m_logStr.Append("\n");

        if (m_logStr.Length <= 0) return;
        if (!File.Exists(m_logFileSavePath))
        {
            var fs = File.Create(m_logFileSavePath);
            fs.Close();
        }
        using (var sw = File.AppendText(m_logFileSavePath))
        {
            sw.WriteLine(m_logStr.ToString());
        }
        m_logStr.Remove(0, m_logStr.Length);
    }
}

我们可以在Application.persistentDataPath路径下看到日志文件,

3、日志上传到服务器

实际项目中,我们可能需要把日志上传到服务端,方便进行查询定位。
上传文件我们可以使用UnityWebRequest来处理,这里需要注意,我们的日志文件可能很小也可能很大,正常情况下都比较小,但是有时候报错了是会循环打印日志的,导致日志文件特别大,所以我们要考虑到大文件读取的情况,否则读取日志文件时会很卡,建议使用字节流读取。

例:

// 读取日志文件的字节流
byte[] ReadLogFile()
{
	byte[] data = null;
	
	using(FileStream fs = File.OpenRead("你的日志文件路径")) 
	{
		int index = 0;
		long len = fs.Length;
		data = new byte[len];
		// 根据你的需求进行限流读取
		int offset = data.Length > 1024 ? 1024 : data.Length;
		while (index < len) 
		{
			int readByteCnt = fs.Read(data, index, offset);
			index += readByteCnt;
			long leftByteCnt = len - index;
			offset = leftByteCnt > offset ? offset : (int)leftByteCnt;
		}
		Debug.Log ("读取完毕");
	}
	return data;
}

// 将日志字节流上传到web服务器
IEnumerator HttpPost(string url, byte[] data)
{
	WWWForm form = new WWWForm();
	// 塞入描述字段,字段名与服务端约定好
	form.AddField("desc", "test upload log file");
	// 塞入日志字节流字段,字段名与服务端约定好
	form.AddBinaryData("logfile", data, "test_log.txt", "application/x-gzip");
	// 使用UnityWebRequest
	UnityWebRequest request = UnityWebRequest.Post(url, form);
	var result = request.SendWebRequest();
    while (!result.isDone)
    {
        yield return null;
        //Debug.Log ("上传进度: " + request.uploadProgress);
    }
    if (!string.IsNullOrEmpty(request.error))
    {
        GameLogger.LogError(request.error);
    }
    else
    {
        GameLogger.Log("日志上传完毕, 服务器返回信息: " + request.downloadHandler.text);
    }
    request.Dispose();
}

调用:

byte[] data = ReadLogFile();
StartCoroutine(HttpPost("http://你的web服务器", data));

四、日志开关

实际项目中,我们可能需要做日志开关,比如开发阶段日志开启,正式发布后则关闭日志。
Unity并没有给我们提供一个日志开关的功能,那我们就自己封装一下吧~
例:

using UnityEngine;

public class GameLogger 
{
    // 普通调试日志开关
    public static bool s_debugLogEnable = true;
    // 警告日志开关
    public static bool s_warningLogEnable = true;
    // 错误日志开关
    public static bool s_errorLogEnable = true;

    public static void Log(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(message, context);
    }

    public static void LogWarning(object message, Object context = null)
    {
        if (!s_warningLogEnable) return;
        Debug.LogWarning(message, context);
    }

    public static void LogError(object message, Object context)
    {
        if (!s_errorLogEnable) return;
        Debug.LogError(message, context);
    }
}

我们所有使用Debug打印日志的地方,都是改用GameLogger来打印,这样就可以统一通过GameLogger来开关日志的打印了~不过,这里会有一个问题,就是我们在Console日志窗口双击日志的时候,它只会跳转到GameLogger里,而不是跳转到我们调用GameLogger的地方。
比如我们在Test脚本中调用GameLogger.Log,如下:

// Test.cs 

using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        GameLogger.Log("哈哈哈哈哈");
    }
}

看,它是跳到GameLogger里,而不是跳到我们的Test脚本了,这个是显然的,但我们能不能让它跳到Test脚本里呢?

下面我就来给大家表演一下!请往下看~

五、日志双击溯源问题

要解决上面的问题,有两种方法:
方法一:把GameLogger编译成dll放在工程中,把源码删掉;
方法二:通过反射分析日志窗口的堆栈,拦截Unity日志窗口的打开文件事件,跳转到我们指定的代码行处。
下面我来说下具体操作。

方一、GameLogger编译为dll

事实上,我们写的C#代码都会被Unity编译成dll放在工程目录的Library/ScriptAssemblies目录中,默认是Assembly-CSharp.dll

我们可以使用ILSpy.exe反编译一下它,

注:ILSpy反编译工具可以从GitHub下载:https://github.com/icsharpcode/ILSpy

不过我们看到,这个dll包含了其他的C#代码,我们能不能专门只为GameLogger生成一个dll呢?可以滴,只需要把GameLogger.cs单独放在一个子目录中,并创建一个Assembly Definition,如下,

Assembly Definition重命名为GameLogger,如下,

我们再回到Library/ScriptAssemblies目录中,就可以看到生成了一个GameLogger.dll啦,

把它剪切到Unity工程的Plugins目录中,把我们的GameLogger.cs脚本删掉或是移动到工程外备份(Assembly Definition文件也删掉),如下:

这样,就大功告成了,我们测试一下日志双击溯源,如下,可以看到,现在可以正常跳转到我们想要的地方了。

方二、详见

六、日志上传服务器案例

1、界面制作

使用UGUI简单做下界面,

保存为MainPanel.prefab预设,

2、C#代码

三个脚本,如下

  • GameLogger.cs:封装Debug日志打印,实现日志开关控制、彩色日志打印、日志文件存储等功能;
  • LogUploader.cs:实现日志上传到服务器的功能;
  • Main.cs:程序入口脚本,同时实现UI界面交互。

原理我上文都有讲,这里就不赘述代码细节了,可以看注释,我都写得比较详细。

2.1、GameLogger.cs代码

/// <summary>
/// 封装Debug日志打印,实现日志开关控制、彩色日志打印、日志文件存储等功能
/// 作者:林新发 博客:https://blog.csdn.net/linxinfa
/// </summary>

using UnityEngine;
using System.Text;
using System.IO;

public class GameLogger
{
    // 普通调试日志开关
    public static bool s_debugLogEnable = true;
    // 警告日志开关
    public static bool s_warningLogEnable = true;
    // 错误日志开关
    public static bool s_errorLogEnable = true;

    // 使用StringBuilder来优化字符串的重复构造
    private static StringBuilder s_logStr = new StringBuilder();
    // 日志文件存储位置
    private static string s_logFileSavePath;

    /// <summary>
    /// 初始化,在游戏启动的入口脚本的Awake函数中调用GameLogger.Init
    /// </summary>
    public static void Init()
    {
        // 日期
        var t = System.DateTime.Now.ToString("yyyyMMddhhmmss");
        s_logFileSavePath = string.Format("{0}/output_{1}.log", Application.persistentDataPath, t);
        Application.logMessageReceived += OnLogCallBack;
    }

    /// <summary>
    /// 打印日志回调
    /// </summary>
    /// <param name="condition">日志文本</param>
    /// <param name="stackTrace">调用堆栈</param>
    /// <param name="type">日志类型</param>
    private static void OnLogCallBack(string condition, string stackTrace, LogType type)
    {
        s_logStr.Append(condition);
        s_logStr.Append("\n");
        s_logStr.Append(stackTrace);
        s_logStr.Append("\n");

        if (s_logStr.Length <= 0) return;
        if (!File.Exists(s_logFileSavePath))
        {
            var fs = File.Create(s_logFileSavePath);
            fs.Close();
        }
        using (var sw = File.AppendText(s_logFileSavePath))
        {
            sw.WriteLine(s_logStr.ToString());
        }
        s_logStr.Remove(0, s_logStr.Length);
    }

    public static void UploadLog(string desc)
    {
        LogUploader.StartUploadLog(s_logFileSavePath, desc);
    }

    /// <summary>
    /// 普通调试日志
    /// </summary>
    public static void Log(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(message, context);
    }

    /// <summary>
    /// 格式化打印日志
    /// </summary>
    /// <param name="format">例:"a is {0}, b is {1}"</param>
    /// <param name="args">可变参数,根据format的格式传入匹配的参数,例:a, b</param>
    public static void LogFormat(string format, params object[] args)
    {
        if (!s_debugLogEnable) return;
        Debug.LogFormat(format, args);
    }

    /// <summary>
    /// 带颜色的日志
    /// </summary>
    /// <param name="message"></param>
    /// <param name="color">颜色值,例:green, yellow,#ff0000</param>
    /// <param name="context">上下文对象</param>
    public static void LogWithColor(object message, string color, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(FmtColor(color, message), context);
    }

    /// <summary>
    /// 红色日志
    /// </summary>
    public static void LogRed(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(FmtColor("red", message), context);
    }

    /// <summary>
    /// 绿色日志
    /// </summary>
    public static void LogGreen(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(FmtColor("green", message), context);
    }

    /// <summary>
    /// 黄色日志
    /// </summary>
    public static void LogYellow(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(FmtColor("yellow", message), context);
    }

    /// <summary>
    /// 青蓝色日志
    /// </summary>
    public static void LogCyan(object message, Object context = null)
    {
        if (!s_debugLogEnable) return;
        Debug.Log(FmtColor("#00ffff", message), context);
    }

    /// <summary>
    /// 带颜色的格式化日志打印
    /// </summary>
    public static void LogFormatWithColor(string format, string color, params object[] args)
    {
        if (!s_debugLogEnable) return;
        Debug.LogFormat((string)FmtColor(color, format), args);
    }

    /// <summary>
    /// 警告日志
    /// </summary>
    public static void LogWarning(object message, Object context = null)
    {
        if (!s_warningLogEnable) return;
        Debug.LogWarning(message, context);
    }

    /// <summary>
    /// 错误日志
    /// </summary>
    public static void LogError(object message, Object context = null)
    {
        if (!s_errorLogEnable) return;
        Debug.LogError(message, context);
    }

    /// <summary>
    /// 格式化颜色日志
    /// </summary>
    private static object FmtColor(string color, object obj)
    {
        if (obj is string)
        {
#if !UNITY_EDITOR
            return obj;
#else
            return FmtColor(color, (string)obj);
#endif
        }
        else
        {
#if !UNITY_EDITOR
            return obj;
#else
            return string.Format("<color={0}>{1}</color>", color, obj);
#endif
        }
    }

    /// <summary>
    /// 格式化颜色日志
    /// </summary>
    private static object FmtColor(string color, string msg)
    {
#if !UNITY_EDITOR
        return msg;
#else
        int p = msg.IndexOf('\n');
        if (p >= 0) p = msg.IndexOf('\n', p + 1);// 可以同时显示两行
        if (p < 0 || p >= msg.Length - 1) return string.Format("<color={0}>{1}</color>", color, msg);
        if (p > 2 && msg[p - 1] == '\r') p--;
        return string.Format("<color={0}>{1}</color>{2}", color, msg.Substring(0, p), msg.Substring(p));
#endif
    }

    #region 解决日志双击溯源问题
#if UNITY_EDITOR
    [UnityEditor.Callbacks.OnOpenAssetAttribute(0)]
    static bool OnOpenAsset(int instanceID, int line)
    {
        string stackTrace = GetStackTrace();
        if (!string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("GameLogger:Log"))
        {
            // 使用正则表达式匹配at的哪个脚本的哪一行
            var matches = System.Text.RegularExpressions.Regex.Match(stackTrace, @"\(at (.+)\)",
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            string pathLine = "";
            while (matches.Success)
            {
                pathLine = matches.Groups[1].Value;

                if (!pathLine.Contains("GameLogger.cs"))
                {
                    int splitIndex = pathLine.LastIndexOf(":");
                    // 脚本路径
                    string path = pathLine.Substring(0, splitIndex);
                    // 行号
                    line = System.Convert.ToInt32(pathLine.Substring(splitIndex + 1));
                    string fullPath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));
                    fullPath = fullPath + path;
                    // 跳转到目标代码的特定行
                    UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(fullPath.Replace('/', '\\'), line);
                    break;
                }
                matches = matches.NextMatch();
            }
            return true;
        }
        return false;
    }

    /// <summary>
    /// 获取当前日志窗口选中的日志的堆栈信息
    /// </summary>
    /// <returns></returns>
    static string GetStackTrace()
    {
        // 通过反射获取ConsoleWindow类
        var ConsoleWindowType = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
        // 获取窗口实例
        var fieldInfo = ConsoleWindowType.GetField("ms_ConsoleWindow",
            System.Reflection.BindingFlags.Static |
            System.Reflection.BindingFlags.NonPublic);
        var consoleInstance = fieldInfo.GetValue(null);
        if (consoleInstance != null)
        {
            if ((object)UnityEditor.EditorWindow.focusedWindow == consoleInstance)
            {
                // 获取m_ActiveText成员
                fieldInfo = ConsoleWindowType.GetField("m_ActiveText",
                    System.Reflection.BindingFlags.Instance |
                    System.Reflection.BindingFlags.NonPublic);
                // 获取m_ActiveText的值
                string activeText = fieldInfo.GetValue(consoleInstance).ToString();
                return activeText;
            }
        }
        return null;
    }
#endif
    #endregion 解决日志双击溯源问题
}

2.2、LogUploader.cs代码

/// <summary>
/// 实现日志上传到服务器的功能
/// 作者:林新发 博客:https://blog.csdn.net/linxinfa
/// </summary>

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

public class LogUploader : MonoBehaviour
{
    private static string LOG_UPLOAD_URL = "http://127.0.0.1:7890/upload_log.php";

    public static void StartUploadLog(string logFilePath, string desc)
    {
        var go = new GameObject("LogUploader");
        var bhv = go.AddComponent<LogUploader>();
        bhv.StartCoroutine(bhv.UploadLog(logFilePath, LOG_UPLOAD_URL, desc));
    }

    /// <summary>
    /// 上报日志到服务端
    /// </summary>
    /// <param name="url">http接口</param>
    /// <param name="desc">描述</param>
    private IEnumerator UploadLog(string logFilePath, string url, string desc)
    {
        var fileName = Path.GetFileName(logFilePath);
        var data = ReadLogFile(logFilePath);
        WWWForm form = new WWWForm();
        // 塞入描述字段,字段名与服务端约定好
        form.AddField("desc", desc);
        // 塞入日志字节流字段,字段名与服务端约定好
        form.AddBinaryData("logfile", data, fileName, "application/x-gzip");
        // 使用UnityWebRequest
        UnityWebRequest request = UnityWebRequest.Post(url, form);
        var result = request.SendWebRequest();

        while (!result.isDone)
        {
            yield return null;
            //Debug.Log ("上传进度: " + request.uploadProgress);
        }
        if (!string.IsNullOrEmpty(request.error))
        {
            GameLogger.LogError(request.error);
        }
        else
        {
            GameLogger.Log("日志上传完毕, 服务器返回信息: " + request.downloadHandler.text);
        }
        request.Dispose();
    }

    private byte[] ReadLogFile(string logFilePath)
    {
        byte[] data = null;

        using (FileStream fs = File.OpenRead(logFilePath))
        {
            int index = 0;
            long len = fs.Length;
            data = new byte[len];
            // 根据你的需求进行限流读取
            int offset = data.Length > 1024 ? 1024 : data.Length;
            while (index < len)
            {
                int readByteCnt = fs.Read(data, index, offset);
                index += readByteCnt;
                long leftByteCnt = len - index;
                offset = leftByteCnt > offset ? offset : (int)leftByteCnt;
            }
        }
        return data;
    }
}

2.3 Main.cs代码

using UnityEngine;
using UnityEngine.UI;

public class Main : MonoBehaviour
{
    /// <summary>
    /// 日志开关
    /// </summary>
    public Toggle logEnableTgl;
    /// <summary>
    /// 打印日志按钮
    /// </summary>
    public Button logBtn;
    /// <summary>
    /// 上传日志按钮
    /// </summary>
    public Button uploadBtn;
    /// <summary>
    /// 日志文本Text
    /// </summary>
    public Text logText;

    void Awake()
    {
        GameLogger.Init();

        // 监听日志,输出到logText中
        Application.logMessageReceived += (string condition, string stackTrace, LogType type) => 
        {
            switch(type)
            {
                case LogType.Log:
                    {
                        if (!GameLogger.s_debugLogEnable) return;
                    }
                    break;
                case LogType.Warning:
                    {
                        if (!GameLogger.s_warningLogEnable) return;
                    }
                    break;
                case LogType.Error:
                    {
                        if (!GameLogger.s_errorLogEnable) return;
                    }
                    break;
            }
            logText.text += condition + "\n";
        };
    }

    private void Start()
    {
        logText.text = "";
        uploadBtn.onClick.AddListener(() =>
        {
            GameLogger.UploadLog("上传日志测试");
        });
        logBtn.onClick.AddListener(() =>
        {
            GameLogger.Log("打印一行日志");
        });

        logEnableTgl.onValueChanged.AddListener((v) => 
        {
            GameLogger.s_debugLogEnable = v;
        });

        GameLogger.Log("大家好,我是林新发");
        GameLogger.LogCyan("我的CSDN博客:https://blog.csdn.net/linxinfa");
        GameLogger.LogYellow("欢迎关注、点赞,感谢支持~");
        GameLogger.LogRed("❤❤❤❤❤❤❤❤❤❤❤");
    }
}

3、挂Main脚本

MainPanel.prefab预设挂上Main.cs脚本,并赋值UI成员,如下:

4、Web服务器

我们的日志要上传到Web服务器,所以我们需要搭建一个Web服务器。简单的做法是使用PHP小皮~

4.1、Web服务器

关于小皮,可以看我之前写的这篇文章:https://blog.csdn.net/linxinfa/article/details/103033142
我们设置一下端口号,比如7890,

启动Apache

这样,我们就已经启动了一个Web服务器了,通过http://127.0.0.1:7890即可访问

4.2、PHP脚本:upload_log.php

我们打开Web服务器的根目录,

在根目录中创建一个upload_log.php

php代码如下:

<?php
// 获取描述
$desc = $_POST["desc"];
// 获取临时文件路径
$tmp = $_FILES["logfile"]["tmp_name"];
// 文件保存位置
$savePath = "upload/" . $_FILES["logfile"]["name"];
// 判断文件是否已存在
if (file_exists($savePath))
{
	// 文件已存在,删除它
	unlink($savePath);
}
// 保存文件到savePath的路径下
move_uploaded_file($tmp, $savePath);
echo "文件上传成功";
?>

我们再创建一个upload文件夹,用于存放上传上来的日志,

5、运行测试

运行Unity,测试效果如下:

日志成功上传到了Web服务器的upload目录中,

日志文件内容如下:

大家好,我是林新发
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main:Start () (at Assets/Scripts/Main.cs:70)


<color=#00ffff>我的CSDN博客:https://blog.csdn.net/linxinfa</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogCyan (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:131)
Main:Start () (at Assets/Scripts/Main.cs:71)


<color=yellow>欢迎关注、点赞,感谢支持~</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogYellow (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:122)
Main:Start () (at Assets/Scripts/Main.cs:72)


<color=red>❤❤❤❤❤❤❤❤❤❤❤</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogRed (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:104)
Main:Start () (at Assets/Scripts/Main.cs:73)


打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)


打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)


打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)


打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)


打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)

6、工程源码

本文工程源码我一上传到CODE CHINA,感兴趣的同学可自行下载下来学习。
地址:林新发 / UnityLoggerDemo · GitCode
注:我使用的Unity版本为:2021.1.7f1c1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1674315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】线程机制解析:理解、优势与Linux系统应用

文章目录 前言&#xff1a;1. 线程概念1.1. 什么是线程1.2. 线程得优点&#xff1a;1.3. 线程的缺点线程异常线程的用途 2. 线程的理解&#xff08;Linux 系统为例&#xff09;2.1. 为什么要设计Linux“线程"&#xff1f;2.2. 什么是进程&#xff1f;2.3. 关于调度的问题2…

图像归一化处理

归一化 归一化是一种简化计算的方式&#xff0c;即将有量纲的表达式&#xff0c;经过变换&#xff0c;化为无量纲的表达式&#xff0c;成为标量。 在多种计算中都经常用到这种方法。 简单介绍 归一化是一种无量纲处理手段&#xff0c;使物理系统数值的绝对值变成某种相对值关…

IDEA报错:java 找不到符号

IDEA报错:java 找不到符号,代码没问题,IDEA缓存也清理了也重新构建了就是不行 最后使用终极大法 -Djps.track.ap.dependenciesfalse

Eclipse 里如何建立SAP应用服务层的CDS

关于Core Data Service(CDS) CDS:Core Data Ser vice.核心数据服务。CDS 是使用基于 SQL的数据定义语言(DDL)定义的&#xff0c;该语言基于标准 SQL 并带有一些附加概念。使用类似 SQL的灵活表达式可以进行复杂的数据建模。有两种类型的 CDS:ABAP CDS 和 HANA CDS。 S/4 HANA…

Unity Pixels Per Unit 与 Sprite Renderer Scale的逻辑关系,为什么平铺的Sprite Renderer会变形?

SpriteRenderer之前用的比较基础&#xff0c;没遇到过什么问题&#xff0c;这几天使用SpriteRenderer的平铺时发现平铺变形了&#xff0c;研究了一下&#xff0c;原来有这么多在逻辑在里面。 当我们导入图片选择Texture Type为Sprite时表示我们的图片用途是UI或者SpriteRendere…

【刷题(2)】矩阵

一、矩阵问题基础 遍历&#xff1a; for i in range(len(matrix[0])): for j in range(len(matrix): while 倒序遍历&#xff1a; for i in range(right,left,-1) 临时存储&#xff1a;temp w,h:len(matrix[0])-1 len(matrix)-1 left,right,top,bottom:0 len(matrix[0])-1 0 l…

NMACDR:基于邻居交互增强和多头注意力机制的跨域推荐模型

基于邻居交互增强和多头注意力机制的跨域推荐模型 湖北民族大学学报-孙克雷、汪盈盈-2023 思路 针对基于映射的跨域推荐模型没有充分关注源域中数据稀疏的用户,导致用户偏好的迁移效率降低的问题,提出本文。 首先,利用邻居用户的交互来增强源域中数据稀疏用户的交互序列,…

热搜榜小工具,摸鱼必备NO.99

本文一共:188 个字,需要阅读:1 分钟,更新时间:2024年5 月14日,部分内容具有时效性,如有失效请留言,阅读量:0 这个小工具集成了微博热搜、百度热搜、今日头条热搜、抖音热搜&#xff0c;随意切换 右上角有个设置&#xff0c;可以设置自动刷新时间、监控关键词、透明度、靠边隐藏…

鸿蒙内核源码分析 (编码方式篇) | 机器指令是如何编码的?

本篇说清楚 ARM指令是如何被编码的&#xff0c;机器指令由哪些部分构成&#xff0c;指令有哪些类型&#xff0c;每种类型的语法又是怎样的 ? 代码案例 | C -> 汇编 -> 机器指令 看一段C语言编译(clang)成的最后的机器指令(armv7) int main(){int a 0;if( a ! 1) a …

dataframe数据常用python操作

dataframe数据常用python操作 dataframe数据常用知识点1.创建dataframe1.1使用字典创建DataFrame&#xff1a;1.2使用列表创建DataFrame&#xff1a;1.3使用numpy数组创建DataFrame&#xff1a;1.4从TXT文件中创建DataFrame&#xff1a;1.5从CSV文件中创建DataFrame&#xff1a…

卡巴斯基:2024年Q1漏洞和利用报告

近日&#xff0c;卡巴斯基发布了《2024年Q1漏洞和利用报告》&#xff0c;提供了一系列有洞察力的统计和分析快照&#xff0c;揭示了新漏洞和利用的发展趋势&#xff0c;以及攻击者最常利用的漏洞概述。为组织获悉和应对相关威胁提供了有价值的见解。 已注册漏洞统计数据 为了…

大企业总部与分部组网方案

在全球化的经济环境中&#xff0c;大企业往往设有总部和多个地理分散的分部。为了确保信息的快 速流通、资源的优化配置以及管理的高效运作&#xff0c;构建一个稳定、安全且高效的组网方案显 得尤为重要。本文将探讨大企业如何通过技术手段和管理策略&#xff0c;实现总部与分…

常见加解密算法03 - RC4逆向认识

各位聪明绝顶&#xff0c;才高八斗的读者们你们好&#xff01;今天我们主要讨论编译之后的RC4算法识别。 题外话&#xff0c;之前看到一个蛋疼的小知识&#xff0c;说“势”这个字最好不好查词典释义。我是很好奇的&#xff0c;果然后来无法直视势不可挡这个成语。 言归正传&am…

Python tensor向量维度转换,不同维度的向量转化为相同的维度,经过全连接层MLP的维度转换,代码实战

问题&#xff1a;在机器学习特征工程中&#xff0c;假如每类特征需要转化为相同的维度进行拼接&#xff0c;那该怎么办呢&#xff1f;接一个全连接层MLP就可以了。 例子&#xff1a;将&#xff08;128,64&#xff09; 维度的向量转化为&#xff08;128,32&#xff09;维。 impo…

安装ps提示找不到msvcp140.dll,无法继续执行此代码如何修复

MSVCP140.dll&#xff0c;作为Windows操作系统中的一个关键组件&#xff0c;扮演着不可或缺的角色&#xff0c;尤其对于基于C开发的应用程序而言。本文旨在深入探讨这一动态链接库文件的功能、重要性、常见问题及解决方案&#xff0c;为您提供全面的MSVCP140.dll指南。 一、MSV…

zookeeper集群部署以及zookeeper原理

文章目录 简介工作原理特性官网地址准备节点准备环境准备JAVA主机映射 部署 简介 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服…

触摸播放视频,并用iframe实现播放外站视频

效果&#xff1a; html: <div:style"{ height: homedivh }"class"rightOne_content_div_div"mouseenter"divSeenter(i)"mouseleave"divLeave(i)"click"ItemClick(i)"><!-- isUser是否是用户上传 --><divv-if…

react18【系列实用教程】组件 (2024最新版 | 含父子组件传值、兄弟组件传值、越层组件传值、“插槽“)

什么是组件&#xff1f; 一个组件就是用户界面的一部分&#xff0c;它可以有自己的逻辑和外观。 组件之间可以互相嵌套&#xff0c;也可以复用多次 为什么要用组件&#xff1f; 组件能让开发者像搭积木一样快速构建一个完整的庞大应用&#xff0c;大大提升了开发效率&#xff…

刚刚OpenAI发布ChatGPT-4o模型,免费使用GPT4o并免费提供更多功能

就在今日凌晨1点&#xff0c;OpenAI举行了春季发布会&#xff0c;发布了GPT-4o 并免费提供更多功能。 亲测GPT-4o已经可以免费试用&#xff0c;每个人都可以使用它并从中受益&#xff0c;GPT4终于不再是少部分人的玩物。 点击加入ChatGPT4交流群&#xff1a;https://www.aijour…

vue3专栏项目 -- 五、权限管理(上)

一、登录部分 1、第一部分&#xff1a;获取token 前面我们主要是在获取数据上下功夫&#xff0c;到目前为止我们已经能获取首页和详情页的数据了&#xff0c;现在我们将数据转移到权限管理上来&#xff0c;也就是说我们要处理用户登录、注册等一系列的行为&#xff0c;在这部…