首页 > 系统相关 >Linux下含有中文日志输出到终端显示不出来

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

时间:2024-11-08 23:17:50浏览次数:3  
标签:中文 zh LC setlocale locale gdb Linux 日志 root

问题描述:

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

用户要升级操作系统,由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, 你好]

标签:中文,zh,LC,setlocale,locale,gdb,Linux,日志,root
From: https://blog.csdn.net/qq_39827740/article/details/143602385

相关文章

  • Linux基础练习题
    1、截取登录成功界面2、进入用户主目录后切换root用户suroot//输入管理员密码切换到root用户3、在该目录下新建名为teacher和students的文件夹mkdirteacherstudents//使用mkdir创建文件夹ls-a/home/roots//ls-a用于查看是否创建成功4、在teache......
  • ethereum.FilterQuery 日志查询处理自定义事件
    前言:在开发中也是遇到这个问题了,并非常见的Transfer,Approve等在ERC20中定义的事件,只要你的事件在sol文件中存在,那还好处理,但是如果不存在,刚开始接触的时候,你可能就有点懵,我也是找了两天,查阅了很多资料,并没什么niao用,偶然看见了区块浏览器中的log才恍然大悟。问题:Uniswa......
  • 2024/11/8日 日志 关于Servlet ----(上)
    简介与快速入门点击查看代码--Servlet是Java提供的一门动态web资源开发技术--Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servet接口,并由web服务器运行Servlet--publicinterfaceServlet--Definesmethodsthatallservletsmustimpl......
  • Linux基础(2)
    学习地点(泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频(bilibili.com))LInux目录介绍   Linux常见目录及作用        /:操作系统的根路径      /bin:存储二进制可执行目录,普通用户和管理员都可以执行的命令      /etc:系统管理和配置文件......
  • Linux磁盘存储
    磁盘存储设备文件设备文件是类Unix操作系统(包括Linux)中一种特殊的文件类型,它代表了设备接口,使得用户空间的程序可以通过标准的文件操作来访问和控制硬件设备。设备文件为周边设备提供了简单的接口,如打印机、硬盘等,也可以访问没有连接到任何真实设备的系统资源,如随机数生成......
  • linux文件系统挂载
    文件系统文件系统基础定位文件系统是对文件存储设备的空间进行组织和分配组成用户空间的管理工具:mkfs.ext4,mkfs.xfs,mkfs.vfatlinux的虚拟文件系统:vfs内核的模块:ext4,xfs,vfat信息查看内核支持的文件系统/lib/modules/$(uname-r)/kernel/fs可用的文件系统cat......
  • 学习日志007--python函数 学完再练习练
    一、函数的概念1.定义函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。2.作用函数能提高应用的模块性,和代码的重复利用率3.定义函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用......
  • linux基础——详细篇
    免责声明学习视频来自B站up主泷羽sec,如涉及侵权马上删除文章。笔记的只是方便各位师傅学习知识,以下代码、网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。linux基础命令重现cd(切换目录)ls(展示所有目录)clear(清屏)——不好截图,不做演示pwd......
  • linux基础命令
    本文来自泷羽sec-哔哩哔哩_bilibili查看权限:ls-la/etc/passwd-rw-r--r--:表示文件的权限。第一个字符 -:表示这是一个普通文件(如果是目录则会显示为d)。接下来的三个字符 rw-:表示文件拥有者(在这里是root)的权限,表示可读(r)和可写(w),但不可执行(-)。再接下来的三个字符 r--:表示与......
  • 【linux内核】 BCC Tools命令汇总
    什么是BCCTools直接通过BPF指令编写BPF程序是非常繁琐的事情,尤其对于运维人员来说,花70%的时间来编写一个BPF程序来实现一个检查点,不如花70%的时间来熟悉别人已经写好的BPF程序,并且把别人的BPF程序有效的串联起来形成自己分析问题的套路,等有了闲暇时间再去尝试突破自己,写一些现......