如何将systemd服务的输出重定向到文件

290
我尝试将systemd服务的输出重定向到文件中,但似乎无法正常工作:
[Unit]
Description=customprocess
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/binary1 agent -config-dir /etc/sample.d/server
StandardOutput=/var/log1.log
StandardError=/var/log2.log
Restart=always

[Install]
WantedBy=multi-user.target

请纠正我的方法。


我投票关闭此问题,因为从标签来看:systemd问题应该是关于使用systemd或其库的编程问题。有关配置守护程序(包括编写单元文件)的问题最好转向Unix&Linux:https://unix.stackexchange.com。请删除此内容。 - Rob
2
如果这个问题是新的,我会同意Rob的看法,但它已经存在了7年,并且有几个高赞的答案;在我看来,现在删除它为时已晚。 - Guntram Blohm
10个回答

364

我认为有一种更优雅的方法来解决这个问题:使用标识符将标准输出/错误输出发送到syslog,并指示syslog管理器按程序名称拆分其输出。

在您的systemd服务单元文件中使用以下属性:

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=<your program identifier> # without any quote

假设您的发行版使用rsyslog来管理syslog,那么请在/etc/rsyslog.d/<new_file>.conf中创建一个文件,并添加以下内容:

if $programname == '<your program identifier>' then /path/to/log/file.log
& stop

现在通过syslog将日志文件设为可写:

# ls -alth /var/log/syslog 
-rw-r----- 1 syslog adm 439K Mar  5 19:35 /var/log/syslog
# chown syslog:adm /path/to/log/file.log

重新启动rsyslog (sudo systemctl restart rsyslog),然后就可以愉快地使用了!您的程序标准输出/标准错误仍然可以通过journalctl (sudo journalctl -u <your program identifier>) 获得,但它们也会在您选择的文件中可用。

来源自archive.org


12
在Ubuntu 16.04上无法运行。journalctl -u仍然可以工作,但是没有内容被发送到指定的文件。 - Duncan Calvert
2
这在Debian stretch上运行得很好,但它抱怨~已被弃用,应该使用stop代替。另外请注意,如果两个命令在彼此之后,则第二行可以缩短为& stop - jlh
87
从Systemd 236或更新版本开始,您也可以使用StandardOutput=file:/some/path直接将输出写入文件。 https://github.com/systemd/systemd/pull/7198 - leezu
11
我通过更改/etc/rsyslog.d/<newfile>.conf的内容来使它工作::programname, isequal, "<你的程序标识符>" /var/log/somelog.log这是rsyslog过滤器的文档:https://www.rsyslog.com/doc/v8-stable/configuration/filters.html这里有关于诸如“programname”之类的属性的文档:https://www.rsyslog.com/doc/master/configuration/properties.html - mbil
5
在使用这个配置时,我遇到了问题,直到发现 rsyslog 有自己的用户 syslog ,并且它必须具有对日志位置的写访问权限。 因此请相应地使用 chown 。希望这能帮助到某些人。 - Imaskar
显示剩余17条评论

134
我建议在systemd的service文件中添加stdout和stderr文件。
参考:https://www.freedesktop.org/software/systemd/man/systemd.exec.html#StandardOutput= 根据您的配置,它不应该像这样:
StandardOutput=/home/user/log1.log
StandardError=/home/user/log2.log

应该是这样的:
StandardOutput=file:/home/user/log1.log
StandardError=file:/home/user/log2.log

当你不想一遍又一遍地重新启动服务时,这个方法很有效。

这将创建一个新文件,而不是追加到现有文件中。

使用以下方法代替:

StandardOutput=append:/home/user/log1.log
StandardError=append:/home/user/log2.log

注意:确保您已经创建了目录。我猜它不支持创建目录。这个功能是在systemd 240中引入的,但在早期版本(如RHEL8)上进行了回溯。

24
我认为我将其变得更加直接易懂了。 - Rajat jain
1
对我来说,file: 路径在服务第一次加载时可以工作,但在后续重启时不再写入文件。我尝试了文档中的 append:,但完全没有起作用。 - rb-
5
请注意,文档明确指出 file: 每次都写入文件的开头,并且不截断...此外,append: 似乎是一个新的添加项(即在Ubuntu 18.04上的 man systemd.exec 页面中没有出现)。 - cole
12
append:在systemd 240版本中被引入。为了解决file:将新的日志输出放在目标文件开头的问题,可以尝试像这样做:ExecStartPre=/bin/bash -c 'mv /var/log/my/logs.log /var/log/my/$$(date +%%T)-logs.log'。这样可以保持干净的日志并以某种方式模拟日志轮换效果。 - SVUser
3
对于任何想了解的人而言,目前Ubuntu 20.04的systemd版本为245,而Ubuntu 18.04为237。因此,不幸的是您必须使用20.04才能使用“append:”。 - nonrectangular
显示剩余3条评论

127

如果你使用的是较新的发行版和较新版本的systemd (systemd 版本236或更高),你可以将StandardOutputStandardError的值设置为file:YOUR_ABSPATH_FILENAME,从而实现重定向输出到文件。


详细情况:

在较新版本的systemd中,有一个比较新的选项(Github请求大约是2016年,增强功能在2017年合并/关闭),可以将StandardOutputStandardError的值设置为file:YOUR_ABSPATH_FILENAMEfile:path选项在最新的systemd.exec手册页面上有文档说明。

这个新特性相对较新,因此在旧的发行版(如centos-7或之前的任何centos版本)中不可用。


12
2018-03-20时在Ubuntu 16.04上无法工作。Ubuntu 16.04中的systemd版本仅为229。 - bronze man
谢谢,你说得非常清楚。我只是无法相信Ubuntu 1604中的systemd不能通过配置将输出重定向到文件。我不得不使用sh方式来解决这个问题。 - bronze man
@bronzeman,该功能请求直到2017年才关闭,而Ubuntu 16.04是在2016年发布的。在Ubuntu的一个主要版本发布中(例如16.04、16.10、17.04等),Ubuntu会在其核心系统包中维护ABI兼容性。因此,除非它保持与Ubuntu版本首次发布时相同的ABI,否则它们不会升级systemd(或Linux内核、glibc或任何其他内容)。 - villapx
3
值得一提的是:我已经搜索了一下,但是这个功能似乎没有提供日志轮换的选项,例如重新打开日志文件的函数,而必须使用类似于logrotate中的copytruncate - antak
2
问题是,无论单元的用户和组如何,该文件始终以root:root的身份创建... - bviktor
我正在使用Ubuntu 18.04,它的systemd版本是237,但它不起作用。文件已创建,但是它是空的! - SuB

62

您可能会遇到此错误:

Failed to parse output specifier, ignoring: /var/log1.log

来自 systemd.exec(5) 手册页面:

StandardOutput=

控制执行进程的标准输出文件描述符 1 (STDOUT) 的连接位置。可选值有 inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+consolesocket

systemd.exec(5) 手册页面还解释了与日志记录相关的其他选项。请参阅 systemd.service(5)systemd.unit(5) 手册页面。

或者您可以尝试像这样的操作(全部在一行上):

ExecStart=/bin/sh -c '/usr/local/bin/binary1 agent -config-dir /etc/sample.d/server 2>&1 > /var/log.log' 

7
在多种选项中,推荐登录到systemd日志。您可以使用journalctl -u your-unit-name命令在日志中查看您的进程的日志记录。 - Mark Stosberg
6
为了指定一个文件,文档中提到了另一种更清晰的选项:fd 选项将输出流连接到由套接字单元提供的单个文件描述符。可以在此选项后面指定自定义命名的文件描述符,格式为 fd:foobar - orion
5
很棒的回答,解决了我的问题。我只想扩展一下,因为当前如果服务重新启动会覆盖旧日志,所以必须将这部分内容替换为:2>&1 >> /var/log.log。谢谢。 - PumpkinSeed
10
坦白说,在ExecStart中使用命令字符串调用shell听起来像是一种非常错误的做法。 - David Tonhofer
2
“/bin/sh”是一个很好的解决方法,但你必须使用“exec”,否则服务将无法正确重启,因为SIGTERM信号将无法传递给子进程。请参阅http://veithen.io/2014/11/16/sigterm-propagation.html。 - Rich
显示剩余5条评论

27
如果由于某种原因无法使用rsyslog,则可以采用以下方式: ExecStart=/bin/bash -ce "exec /usr/local/bin/binary1 agent -config-dir /etc/sample.d/server >> /var/log/agent.log 2>&1"

1
Bash 的 -e 选项是什么意思? - Lamp
在我的情况下,唯一有效的解决方案是不丢失日志文件,因为不幸的是,在我的当前systemd版本中没有可用的追加选项。 - pragmatic_programmer
1
@Lamp,我怀疑你现在不需要这个了,但我也对bash中的-e选项感到好奇。我找到了这个链接:https://dev59.com/vZ7ha4cB1Zd3GeqPiWL6#41760745 - Allen

26

简短回答:

StandardOutput=file:/var/log1.log
StandardError=file:/var/log2.log
如果你不想每次运行服务时清除文件,请使用“append”。
StandardOutput=append:/var/log1.log
StandardError=append:/var/log2.log

9
以下是 此答案 的简化版翻译:原文:In JavaScript, the spread operator (...) is used to expand an iterable (like an array or a string) into individual elements. For example: const arr = [1, 2, 3]; console.log(...arr); // Output: 1 2 3 It can also be used to concatenate arrays: const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const arr3 = [...arr1, ...arr2]; console.log(arr3); // Output: [1, 2, 3, 4, 5, 6]翻译:在 JavaScript 中,展开运算符(...)用于将可迭代对象(例如数组或字符串)扩展为单个元素。例如:const arr = [1, 2, 3]; console.log(...arr); // 输出:1 2 3它也可以用于连接数组:const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const arr3 = [...arr1, ...arr2]; console.log(arr3); // 输出:[1, 2, 3, 4, 5, 6] - rustyx
2
非常有用。我使用了这种技术来恢复Ubuntu 18.04和20.04中/var/log/tomcatX/catalina.out的日志记录。 - Ted Cahall
关于"file"的理解是错误的。请查看文档以了解它的作用。实际上,"截断"模式会清除:每次运行服务时都会被清除。此外,在此处指定StandardError没有必要,因为默认行为是让错误流继承由StandardOutput设置的流。 - Blaine
它可以根据大小自动截断日志。是否有方法可以更改它?我似乎没有看到任何人谈论过这个问题。 - The Quantum Physicist

7
我们正在使用Centos7,spring boot应用程序与systemd。我以以下方式运行Java,并将StandardOutput设置为文件对我不起作用。
ExecStart=/bin/java -jar xxx.jar  -Xmx512-Xms32M

以下解决方案可在不设置StandardOutput的情况下运行java,如下所示通过sh运行。


ExecStart=/bin/sh -c 'exec /bin/java -jar xxx.jar -Xmx512M -Xms32M >> /data/logs/xxx.log 2>&1'

enter image description here


-1 表示 jvm 参数顺序定义错误。-Xmx512M 必须在 -jar 之前进行定义。此外,您所遇到的情况是预期的。Systemd 不使用 shell 调用服务。 - Sami Korhonen
4
@SamiKorhonen,在测试后,我添加了我的评论,证明这是可行的。我也在考虑-Xmx512M的顺序与你类似。在添加任何盲目评论之前,请进行测试。 - Santhosh Hirekerur

4
假设日志已经输出到标准输出/标准错误,并且系统单位的日志在/var/log/syslog中。
journalctl -u unitxxx.service

Jun 30 13:51:46 host unitxxx[1437]: time="2018-06-30T11:51:46Z" level=info msg="127.0.0.1
Jun 30 15:02:15 host unitxxx[1437]: time="2018-06-30T13:02:15Z" level=info msg="127.0.0.1
Jun 30 15:33:02 host unitxxx[1437]: time="2018-06-30T13:33:02Z" level=info msg="127.0.0.1
Jun 30 15:56:31 host unitxxx[1437]: time="2018-06-30T13:56:31Z" level=info msg="127.0.0.1

配置rsyslog(系统日志服务)

# Create directory for log file
mkdir /var/log/unitxxx

# Then add config file /etc/rsyslog.d/unitxxx.conf

if $programname == 'unitxxx' then /var/log/unitxxx/unitxxx.log
& stop

重新启动 rsyslog
systemctl restart rsyslog.service

0

让您的服务文件调用一个 shell 脚本而不是直接运行应用程序。这样您就可以获得额外的控制权。例如,您可以创建像 /var/log/ 中那样的输出文件。

创建一个类似于 /opt/myapp/myapp.sh 的 shell 脚本。

#!/bin/sh
/usr/sbin/logrotate --force /opt/myapp/myapp.conf --state /opt/myapp/state.tmp
logger "[myapp] Run" # send a marker to syslog
myapp > /opt/myapp/myapp.log 2>&1 &

而你的服务文件 myapp.service 包含以下内容:

...
[Service]
Type=forking
ExecStart=/bin/sh -c /opt/myapp/myapp.sh
...

日志配置文件示例 /opt/myapp/myapp.conf

/opt/myapp/myapp.log {
    daily
    rotate 20
    missingok
    compress
}

然后您将获得myapp.log和zipped myapp.log.1.gz...每次启动服务时都会有一个新的myapp.log文件,以及之前压缩过的文件。


0
在我的情况下,必须正确放置2>&1(标准输出和标准错误文件描述符符号),然后日志重定向才能按照我预期的方式工作。
[Unit]
Description=events-server

[Service]
User=manjunath
Type=simple
ExecStart=/bin/bash -c '/opt/events-server/bin/start.sh my-conf   2>&1 >> /var/log/events-server/events.log'

[Install]
WantedBy=multi-user.target

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