Linux下含有中文日志输出到终端显示不出来

问题描述:

今天遇到一个中文日志输出到终端显示不出来的问题。

用户要升级操作系统,由redhat7.9升级到redhat8.6x86_64的环境。升级完后,交易服务端程序启动过程中,预期是会在终端输出一些标准输出标准错误的日志信息,用于提示服务端程序启动过程中的状态,日志信息中包含中文字符,程序中是通过类似于fprintf(stdout/stderr, "hh:mm:ss.ffffff <ErrNo> <LogLevel> <PID> <TID> %ls<日志正文,可能含有中文字段,宽字符> (<FuncName> <FILE:LINE> ...)\n", lpLogInfo)的方式输出的,中文编码是宽字符,使用%ls输出,预期的输出效果如图1所示,但实际输出效果如图2所示。

在这里插入图片描述图1
在这里插入图片描述
图2

之前对于中文字符的编码使用这块,没怎么研究过,因此借此机会,定位和记录下排查过程


原因分析:

1,启动参数检查

  • 我们的启动脚本中,会有对LANG环境变量的设置,如:export LANG="zh_CN.UTF8",问题环境上,该设置也是有的。

2,strace跟踪

  • 既然是屏幕输出,那就涉及系统调用,使用strace命令捕捉一下系统调用,看下传给内核的入参是否有问题:strace -o strace_out.txt -T -tt -e trace=all -f -v -s 1024 TradeServer start ......
  • 过滤write(1, write(2, (忽略时间点的差异,与图2不是同一次启动所截的图)
  • 如图3所示,系统调用write(1, 时,传入的原串就已不包含中文的宽字符,说明在构造原串时,就已经出现了问题。
    在这里插入图片描述
    图3

用一个简单的demo,模拟问题场景,好在问题场景很容易复现

#include <wchar.h>
#include <stdio.h>

int main() 
{
    auto str = L"Hello, 你好";
    auto len = wcslen(str);
    fprintf(stdout, "Length of the string is %u, [%ls]\n", len, str);

    return 0;
}
[root@null trade]# g++ test.cpp
[root@null trade]# export LANG=zh_CN.UTF8
[root@null trade]# ./a.out
Length of the string is 9, [[root@null trade]#
  • 的确,输出到了中文处,文本就被截断了,为什么会这样?

3,locale

  • 首先要搞清楚locale环境,之前遇到过这个配置命令,我起初咋一看还以为是locate这个单词…

“Locale” 这个术语来源于拉丁语 “locus”,意思是 “地点” 或 “位置”。在计算机科学和编程中的具体上下文中,“locale” 用来表示特定的地域、语言和文化环境,它影响了程序在这些环境中的行为,特别是数据和信息的展示方式。

  • 那它影响哪些方面呢?在程序中,这和以下环境变量有关

    • LC_COLLATE:定义字符串的排序顺序,比如按字母顺序排列字符串的方法。这在各种语言中可能有所不同。
    • LC_CTYPE:定义字符分类和处理规则,比如字母字符、数字字符、空白字符等分类,以及字符转换函数的行为(如 tolower, toupper)。
    • LC_MONETARY:定义货币符号、格式和显示方式。这对于不同的国家和货币单位来说是不同的。
    • LC_NUMERIC:定义非货币数字的格式,包括小数点和千位分隔符的位置。这在各国可能有很大不同。
    • LC_TIME:定义日期和时间的格式,包括年月日的顺序、星期的显示方式、时间的表示等。
    • LC_MESSAGES:定义用户界面消息和响应的语言和格式,使应用程序能够使用本地语言向用户显示消息。
    • LC_ADDRESS/LC_IDENTIFICATION/LC_MEASUREMENT/LC_NAME/LC_PAPER/LC_TELEPHONE:这些不常用的,咱就先不关心。
    • LC_ALL用来覆盖所有其他 LC_* 设置的超级变量。如果设置了 LC_ALL,它将优先于所有其他 LC_* 及 LANG 设置。
    • LANG用于设置系统的默认语言环境。除非明确设置了其他 LC_* 环境变量,否则它将应用到所有类别。
  • 咱们重点关注LANGLC_ALL这两个环境变量

    • LANG:是在启动脚本中指定的export LANG=zh_CN.UTF-8
    • LC_ALL:是框架在程序启动之初设置的setlocale(LC_ALL, "");
      • 可以看到程序中设置的LC_ALL是一个空串,当传递的locale是空字符串 "" 时,setlocale将尝试从环境变量(如:LC_ALL,LANG,LC_CTYPE 等)中读取并设置 locale
    • 所以预期程序中使用的locale应该是zh_CN.UTF-8
  • 既然使用setlocale设置语言环境标识,那就看看是否有类似于getlocale的接口,确定下set有没有生效。因为原交易程序中没有对setlocale的系统调用成功与否进行判断,因此这里选择gdb进行观察

(gdb) b setlocale
Breakpoint 1 at 0x7ffff6ae3520
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: ......
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, 0x00007ffff6ae3520 in setlocale () from /lib64/libc.so.6
(gdb) disassemble
Dump of assembler code for function setlocale:
=> 0x00007ffff6ae3520 <+0>:     endbr64
   0x00007ffff6ae3524 <+4>:     push   %r15
   0x00007ffff6ae3526 <+6>:     push   %r14
   0x00007ffff6ae3528 <+8>:     push   %r13
   0x00007ffff6ae352a <+10>:    push   %r12
   0x00007ffff6ae352c <+12>:    push   %rbp
   0x00007ffff6ae352d <+13>:    movslq %edi,%rbp
   0x00007ffff6ae3530 <+16>:    push   %rbx
   0x00007ffff6ae3531 <+17>:    sub    $0x118,%rsp
   0x00007ffff6ae3538 <+24>:    mov    %fs:0x28,%rax
   0x00007ffff6ae3541 <+33>:    mov    %rax,0x108(%rsp)
   0x00007ffff6ae3549 <+41>:    xor    %eax,%eax
   0x00007ffff6ae354b <+43>:    cmp    $0xc,%rbp
   0x00007ffff6ae354f <+47>:    ja     0x7ffff6ae38a0 <setlocale+896>
   0x00007ffff6ae3555 <+53>:    mov    %rsi,%rbx
   0x00007ffff6ae3558 <+56>:    test   %rsi,%rsi
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) i registers edi
edi            0x6                 6
(gdb) i registers rsi
rsi            0x4d72aa            5075626
(gdb) p (const char *)0x4d72aa
$2 = 0x4d72aa ""
(gdb) finish
Run till exit from #0  0x00007ffff6ae3520 in setlocale () from /lib64/libc.so.6
main (argc=6, argv=0x7fffffffe028) at xxx.cpp:174
174     xxx.cpp: No such file or directory.
(gdb) i registers eax
eax            0x0                 0
(gdb) i registers rax
rax            0x0                 0
  • edi:是setlocale的第一个形参,即LC_ALL
  • rsi:是setlocale的第一个形参,即""
  • eax:是setlocale的返回值,是一个空指针nullptr
  • 直觉告诉我,这里肯定有问题!来看下接口返回值说明

RETURN VALUE
A successful call to setlocale() returns an opaque string that corresponds to the locale set. This string may be allocated in static storage. The string returned is such that a subsequent call with that string and its associated category will restore that part of the process’s locale. The return value is NULL if the request cannot be honored.

  • 看下实际生效的语言环境标识。
(gdb) p (const char *)setlocale(6, 0)
$1 = 0x7ffff72639cd <_nl_C_name> "C"
(gdb) q
Quit anyway? (y or n) y

[root@null trade]# locale -a
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
C
C.utf8
POSIX
en_AG
en_*
  • 真相大白了:redhat8.6上,居然没有生成zh_CN.UTF-8语言标识的配置文件

解决方案:

剩下的就是生成配置文件的事儿了,过程中还遇到了个小插曲,安装完,生成完后,就能正常输出中文了。

[root@null trade]# localedef -i zh_CN -f UTF-8 zh_CN.UTF-8
[error] character map file `UTF-8' not found: No such file or directory
[error] default character map file `ANSI_X3.4-1968' not found: No such file or directory
[root@null trade]# dnf install glibc-locale-source
Last metadata expiration check: 2:37:35 ago on Thu 07 Nov 2024 02:28:19 PM CST.
Dependencies resolved.
======================================================================================================================================================
 Package                                     Architecture                   Version                                Repository                    Size
======================================================================================================================================================
Installing:
 glibc-locale-source                         x86_64                         2.28-189.1.el8                         base                         4.2 M

Transaction Summary
======================================================================================================================================================
Install  1 Package

Total size: 4.2 M
Installed size: 15 M
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                              1/1
  Installing       : glibc-locale-source-2.28-189.1.el8.x86_64                                                                                    1/1
  Verifying        : glibc-locale-source-2.28-189.1.el8.x86_64                                                                                    1/1
Installed products updated.

Installed:
  glibc-locale-source-2.28-189.1.el8.x86_64

Complete!
[root@null trade]# localedef -i zh_CN -f UTF-8 zh_CN.UTF-8
[root@null trade]# locale -a
C
C.utf8
en_AG
en_xxx
POSIX
zh_CN.utf8
[root@null trade]# ./a.out
Length of the string is 9, [Hello, 你好]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值