Python3 日志记录:UnicodeEncodeError:当从 Apache/PHP 调用时,“ascii”编解码器无法编码字符“\u20ac”。

3

问题

我有以下简单的脚本:

test.py

import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logging.info("€")

根据调用脚本的上下文,可能会产生以下错误:

UnicodeEncodeError: 'ascii'编解码器无法将字符'\u20ac'编码到位置10:超出范围(128)

为什么会出现这种情况?我该如何解决?

我已经发现的问题

观察

当我“正常”调用此脚本时,没有问题:

$ python3 test.py 
INFO:root:€

然而,当我创建一个 PHP 文件 /var/www/html/test.php:

<?php
echo "# locale\n\n";
passthru("locale");
echo "\n\n# python\n\n";
passthru("python3 /var/www/html/test.py 2>&1");

然后通过Apache调用此文件时,会出现错误:

$ curl localhost/test.php
# locale

LANG=C
LANGUAGE=de_DE.UTF-8
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=


# python

--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib/python3.5/logging/__init__.py", line 983, in emit
    stream.write(msg)
UnicodeEncodeError: 'ascii' codec can't encode character '\u20ac' in position 10: ordinal not in range(128)
Call stack:
  File "/var/www/html/test.py", line 5, in <module>
    logging.info("\u20ac")
Message: '\u20ac'
Arguments: ()

与此相比,如果我直接调用locale,我得到的结果如下:

$ locale
LANG=de_DE.UTF-8
LANGUAGE=de_DE.UTF-8
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_MESSAGES="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"
LC_ALL=de_DE.UTF-8

如果我将 PHP 中的 passthru 调用更改为以下内容:
passthru("LANG=de_DE.UTF-8 python3 /var/www/html/openWB/test.py 2>&1");

那么一切正常工作。

LANG=C 是从哪里来的? 不是从这里来的:

cat /etc/default/locale
#  File generated by update-locale
LANG=de_DE.UTF-8
LC_ALL=de_DE.UTF-8
LANGUAGE=de_DE.UTF-8

系统是Raspbian GNU/Linux 9(stretch)。

解释

显然,脚本的成功取决于我的用户设置。我曾经认为Python脚本在系统之间大多是可移植的。现在我知道它们甚至不能从一个用户移植到另一个用户 ;-)。当然,环境变量改变了相关应用程序的外观,这是可以接受的,但不是整个应用程序都会崩溃的保证。

我猜我需要更改Python脚本以强制使用UTF-8(如果其他任何操作失败,不太确定为什么这不是默认行为),或者我需要为PHP脚本设置LANG变量。对于这两个选项,问题是:最简单/最短/最有效的方法是什么?最好的情况是有一个单一的选项,我可以全局更改以解决整个系统的问题。具备Root访问权限。

请注意,我目前卡在Python 3.5.3上,不能轻松升级。

2个回答

3

如果您无法升级到Python3.7+(即使使用 LANG=C,也可以默认启用UTF-8模式),请执行以下操作:

$ LANG=C python3.7 script.py
INFO:root:€

当脚本被调用时,无法控制环境(在那里,您可以设置LANG=C.UTF-8(一种独立的UTF-8语言环境)或其他更具体的语言国家对应的语言环境):

$ LANG=C.UTF-8 python3.5 script.py
INFO:root:€

同时无法通过PYTHONIOENCODING强制编码:

$ LANG=C PYTHONIOENCODING=UTF-8 python3.5 script.py
INFO:root:€

那么你有几个粗略的选择,比如重新打开标准流:

$ cat script.py
import locale
import logging
import sys

sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf8', buffering=1)
sys.stderr = open(sys.stderr.fileno(), mode='w', encoding='utf8', buffering=1)

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logging.info("€")
$ LANG=C python3.5 script.py
INFO:root:€

或者进行某种重定向 / 重新执行操作,使用适当的环境 / 区域设置(尽管当然,exec 不具可移植性,只能在类 posix 平台上表现得相当好,在 Windows 上根本不起作用):

import os
import locale
import logging
import sys

if os.getenv('PYTHONIOENCODING') != 'UTF-8':
    cmd = [sys.executable, *sys.argv]
    os.execvpe(cmd[0], cmd, {**os.environ, 'PYTHONIOENCODING': 'UTF-8'})

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logging.info("€")
$ LANG=C python3.5 script.py
INFO:root:€

[...]并且在脚本被调用时无法控制环境[...]我不一定无能为力。我确实有访问所涉系统的root权限,只是不知道“LANG=C”从何而来。也许我可以修复它... 我更喜欢这样的“全局”解决方案,因为多个Python脚本会受到影响,如果我分别修复每个调用,就有忘记一个的危险。 - yankee
我编辑了我的问题,使其更具体,并添加了/etc/default/locale的内容。 - yankee

0

此答案所示,Apache使用的LANG环境变量设置在/etc/apache2/envvars中。该文件包含以下行:

## The locale used by some modules like mod_dav
export LANG=C
## Uncomment the following line to use the system default locale instead:
#. /etc/default/locale

export LANG

默认值为C,但是通过取消注释所提到的行,您可以使用系统语言环境。

完成后,Python脚本将继承正确的语言环境。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接