更新

  • 2020/06/13 修正 Unity 进程启动失败时日志线程未正确退出导致 Jenkins 卡死
  • 2019/05/19 初次发布

介绍

Unity 编辑器在 macOS 平台上可以使用 -logfile 将日志重定向到标准输出中,即可以在终端中看到日志。但是在 Windows 平台上此参数无效。

原理

因此在 Windows 平台上需要使用辅助工具,由于整个构建流程需要支持跨平台,Python 成为最佳选择,因此使用 Python 编写一个 tail 线程输出日志。

环境

  • Windows 7
  • Python 3.7

需求

编码

支持 Windows 命令行的 GB18030 编码,输出中文无乱码。

在实现时需要将标准输出的编码改为系统 GB18030 编码。

完整

在 Unity 编辑器进程出错退出时,可以完整输出所有日志,而不是正好缺少崩溃部分。

在实现时需要使用 Thread.join 等待日志线程读取完成,可以设置超时时间如 5 秒。

兼容性

正确处理换行符,保证在与其他持续集成软件结合时换行符不会多或少,如 Jenkins。

在实现时需要将从日志文件中读取到的换行符都删除掉,然后使用 Python 自己的换行符输出。有两点好处:

  1. 统一换行符,Unity 内部在输出时有时会混合使用不同换行符,导致日志看起来很奇怪。
  2. 可以根据需要设置统一换行符为任意字符,甚至删除换行符。

代码

Python 启动线程记录日志,主要参考下面的项目实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os
import time
import sys
import io
from threading import Thread

class Tail(Thread):
    def __init__(self, filename):
        self._filename = filename
        self._stop_reading = False
        Thread.__init__(self)

    def run(self):
        while not self._stop_reading and not os.path.exists(self._filename):
            time.sleep(0.1)
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='gb18030')
        with self.open_default_encoding(self._filename, mode='r') as file:
            while True:
                where = file.tell()
                line = file.readline()
                if self._stop_reading and not line:
                    break
                if not line:
                    time.sleep(1)
                    file.seek(where)
                else:
                    if sys.stdout.closed:
                        return
                    print(line.rstrip())
                    sys.stdout.flush()

    def stop(self):
        self._stop_reading = True
        # Wait for thread read the remaining log after process quit in 5 seconds
        self.join(5)

    @staticmethod
    def open_default_encoding(file, mode):
        return open(file, mode=mode, encoding='utf-8-sig')

使用方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
log_file = os.path.join(project_path, 'UnityEditor.log')

cmd = [
    'C:\Program Files\Unity\Editor\Unity.exe',
    '-batchmode',
    '-quit',
    '-nographics',
    '-silent-crashes',
    '-projectpath', project_path,
    '-logfile', log_file,
]

tail = lib.tail.Tail(log_file)
tail.start()
status = os.system(' '.join(cmd))
tail.stop()